Recursos grá gr áficos do Delphi
3
tras prop riedades possí veis v eis de manip ular diretament e para alterar a saí da da é o array P i x e l s , que você pode usar para acessar (ler) ou alterar (gravar) a cor de qualquer ponto espec í fico f ico na superf í c ie do í cie formulário. Conforme veremos no exemplo BmpDraw, as opera çõ es por pixel sã o muito lentas na GDI, comparadas com o acesso à linha dispon í vel v el através da propriedade S c a n Li Li n e s . Finalmente, lembre-se de que os valores de T Co Co l o r do D elphi elphi nem sempre correspondem aos valores RGB puros da representação nativa do Windows (COLORREF ( COLORR EF ), devido às constantes de cor do Delphi. Você sempre pode converter uma cor do Delphi para o valor RGB, usando a fun ção C o l o r lor type da ajuda. T o R G B. B . Você pode encontrar os detalhes da representação do Delphi na entrada T C olor
Desenhando Figuras Geom étricas Agora, queremos estender o exemplo MouseOne constru í do d o no Cap í tulo t ulo 9 e transform á-lo no aplicativo Shapes. Nesse Nesse no vo programa, queremos usar a estrat égia de armazenar e desenhar com várias figuras, manipular atributos de cor e pena e fornecer uma base para extens õ es futuras. Como você precisa se lembrar da posição e dos atributos de cada figura, pode criar um objeto para cada figura que precisa armazenar e pode manter os objetos em uma lista (para sermos mais precisos, a lista armazenar á refer ências aos objetos, que s ão alocados em áreas de mem ó ria separadas). Definim os um a classe-base classe-base para as figuras e duas classes classes herdadas que cont êm o có digo de pintura para os dois tipos de figuras que queremos manipular — re t ângulos e elipses. A classe-base tem algumas propriedades, que simplesmente l êem os campos e gravam os valores correspond correspond entes com com m étodo s simples. simples. Not e que as coordenadas pod em ser lidas usando-se usando-se a propriedade R e c t , mas devem ser modificadas usando-se as quatro propriedades posicionais. O motivo é que, se você incluir incluir um a parte w r i t e na propriedade R e c t , poder á acessar o ret ângulo como como um todo, mas n ã o suas subpropriedades especí ficas. ficas. Aqui est ão as declara çõ es das tr ês classes: type T Ba Ba s e S h a p e = c l a s s private F Br B r u s h Co C o l o r : T Co Co l o r ; F P e n Co Co l o r : T Co l o r ; F Pe Pe n Si z e : I n t e g e r ; p r o c e d u r e S e t B r u s h C o l o r ( c o n s t V a l u e : T Co Co l o r ) ; p r o c e d u r e S e t P e n C o l o r ( c o n s t V a l u e : T Co Co l o r ) ; p r o c e d u r e S e t P e n S i z e ( c o n s t Va l u e : I n t e g e r ) ; p r o c e d u r e S e t B o t t o m( m( c o n s t V a l u e : I n t e g e r ) ; p r o c e d u r e S e t L e f t ( c o n s t Va l u e : I n t e g e r ) ; p r o c e d u r e S e t R i g h t ( c o n s t Va l u e : I n t e g e r ) ; p r o c e d u r e S e t T o p ( c o n s t Va l u e : I n t e g e r ) ; protected F Re R e c t : T Re Re c t ; public p r o c e d u r e P a i n t ( C a n v a s : T Ca Ca n v a s ) ; v i r t u a l ; published p r o p e r t y P e n S i z e : I n t e g e r r e a d F P e n Si S i z e wr i t e S e t P e n S i z e ; p r o p e r t y P e n Co Co l o r : T C o l o r r e a d F P e n Co Co l o r w r i t e S e t P e n Co Co l o r ; p r o p e r t y B r u s h Co C o l o r : T C o l o r r e a d F Br Br u s h C o l o r w r i t e S e t B r u s h C o l o r ; p r o p e r t y L e f t : I n t e g e r wr i t e S e t L e f t ; p r o p e r t y R i g h t : I n t e g e r wr i t e S e t R i g h t ; p r o p e r t y T o p : I n t e g e r wr i t e S e t T o p ; p r o p e r t y B o t t o m: m: I n t e g e r w r i t e S e t B o t t o m; m; p r o p e r t y R e c t : T Re R e c t r e a d F Re Re c t ; e nd;
4
Dominando o Delphi 6 — A Biblí Bibl ía
type T El El l S h a p e = c l a s s ( T B a s e S h a p e ) p r o c e d u r e P a i n t ( C a n v a s : T Ca Ca n v a s ) ; e nd;
override;
T Re Re c t S h a p e = c l a s s ( T B a s e S h a p e ) p r o c e d u r e P a i n t ( C a n v a s : T Ca Ca n v a s ) ; e nd;
override;
A maior parte do c ó digo dos m étodos procedimentos P a i n t :
é muito simples. O ú nico có digo relevante est á nos tr ês
p r o c e d u r e T Ba Ba s e S h a p e . P a i n t ( C a n v a s : T Ca Ca n v a s ) ; begin / / c o nf n f i g ur u r a o s a t r i b ut ut o s Ca n v a s . P e n . C o l o r : = f P e n Co l o r ; C a n v a s . P e n . Wi Wi d t h : = f P e n S i z e ; C a n v a s . B r u s h . C o l o r : = f B r u s h Co Co l o r ; e nd; p r o c e d u r e T El El l S h a p e . P a i n t ( C a n v a s : T Ca Ca n v a s ) ; begin i n h e r i t e d P a i n t ( Ca n v a s ) ; Ca n v a s . E l l i p s e ( f R e c t . Le f t , f R e c t . To p , f R e c t . R i g h t , f R e c t . B o t t o m) m) e nd; p r o c e d u r e T Re Re c t S h a p e . P a i n t ( C a n v a s : TC TCa n v a s ) ; begin i n h e r i t e d P a i n t ( Ca n v a s ) ; Ca n v a s . Re c t a n g l e ( f Re c t . L e f t , f Re c t . T o p , f R e c t . R i g h t , f R e c t . B o t t o m) m) e nd;
Todo esse có digo é armazenado na un idade ShapesH ShapesH (Shapes Hierarchy). Para armazenar armazenar uma lista de figuras, o formul ário tem um membro de dados de objeto T Li L i s t , chamado S h a p e s L i s t , que é inicial inicializado izado no manipulador do evento evento O n Cr C r e a t e e destru í do d o no final; o destrutor tamb ém libera todos os objetos da lista (em ordem inversa, para evitar a atualiza ção dos dados da lista internos com muita freq üê ncia): p r o c e d u r e T Sh S h a p e s F o r m. m. F o r mC mC r e a t e ( S e n d e r : begin S h a p e s Li Li s t : = TL i s t . Cr e a t e ; e nd;
T Ob j e c t ) ;
p r o c e d u r e T Sh S h a p e s F o r m. m. F o r mD mD e s t r o y ( S e n d e r : T O b j e c t ) ; var I : I n t e ge r ; begin / / e x c l u i c a da d a o bj bj e t o f o r I : = S h a p e s L i s t . C o u n t — 1 d o wn wn t o 0 d o
T Ba s e S h a p e ( S h a p e s L i s t [ I ] ) . F r e e ; ShapesList.Free; e nd;
Recursos grá gr áficos do Delphi
5
O programa inclui um novo objeto na lista sempre que o usu ário inicia a operação de desenho. Como o ob jeto jeto n ão est á completamente definido, definido, o form ulário mant ém um a refer refer ência a ele no campo C u r r S h a p e . Note que o tipo de objeto criado depende do status das teclas do mouse: S h a p e s F o r m. m . F o r mM m Mo u s e D o wn wn ( S e n d e r : p r o c e d u r e T Sh
T Ob Ob j e c t ; B u t t o n : T Mo Mo u s e Bu Bu t t o n ; S h i f t : T S h i f t S t a t e ; X, Y : I n t e g e r ) ;
begin i f B u t t o n = mb mb Le Le f t t h e n begin / / a t i v a o ar ar r a s t o
f Dr Dr a g g i n g : = T r u e ; S e t C a p t u r e ( Ha Ha n d l e ) ; / / c r i a o o bj e t o c or r e t o i f s s S h i f t i n S h i f t t h e n
C u r r S h a p e : = TE TE l l S h a p e . C r e a t e else
C u r r S h a p e : = TR TR e c t S h a p e . C r e a t e ; / / c o nf nf i g u r a o e s t i l o e a s c or e s
C u r r S h a p e . P e n Si S i z e : = C a n v a s . P e n . Wi Wi d t h ; C u r r S h a p e . P e n Co Co l o r : = C a n v a s . P e n . C o l o r ; C u r r S h a p e . B r u s h Co Co l o r : = C a n v a s . Br u s h . C o l o r ; / / c o n f i g ur ur a a po s i ç ã o i n i c i a l
Cu r r S h a pe . Cu r r S h a p e . Cu r r S h a pe . Cu r r S h a p e . C a n v a s . Dr a
L e f t : = X; X; T o p : = Y; R i g h t : = X; X; B o t t o m : = Y; w F o c u s Re Re c t ( C u r r S h a p e . R e c t ) ;
// inclui na li sta S h a p e s L i s t . Ad d ( C u r r S h a p e ) ; e nd; end;
Durante a operação de arrasto, desenhamos a linha correspondente exemplo MouseOne: S h a p e s F o r m. m . F o r mM m Mo u s e Mo Mo v e ( S e n d e r : T Ob Ob j e c t ; S h i f t : p r o c e d u r e T Sh TShiftState; X, Y: I n t e g e r ) ; var
A R e c t : T Re Re c t ; begin / / c o p i a a s c o o r d e n a d a s d o mo u s e n o t í t u l o %d , y = %d %d ) ’ , [ X, C a p t i o n : = F o r ma ma t ( ’ S h a p e s ( x = %d X , Y] ) ;
/ / c ó d i g o d e ar ar r a s t o i f f D r a g g i n g t h e n begin / / r e mo m o v e e r e d e s e n h a o r e t â n g u l o d e a r r a s t o
A R e c t : = N o r ma ma l i z e R e c t ( C u r r S h a p e . R e c t ) ; C a n v a s . Dr a wF w F o c u s R e c t ( AR ARe c t ) ; C u r r S h a p e . R i g h t : = X; X; C u r r S h a p e . B o t t o m : = Y;
à figura, como fizemos no
6
Dominando o Delphi 6 — A Biblía
ARe c t : = No r ma l i z e R e c t ( C u r r S h a p e . R e c t ) ; Ca n v a s . Dr a wF o c u s R e c t ( ARe c t ) ; e nd; e nd;
Desta vez, entretanto, tamb ém inclu í m os uma corre ção no programa. No exemplo MouseOne, se você movesse o mouse em dire ção ao canto superior esquerdo do formulário, enquanto arrastava, a chamada a Dr a wF o c u s R e c t n ão produzia nenhum efeito. Isso porque o ret ângulo passado como par âmetro para D r a w F o c u s R e c t deve ter um valor de T o p menor do que o valor de B o t t o m , e o mesmo é verdade para os valores de L e f t e Ri g h t . Em outras palavras, um ret ângulo que se estende para o lado negativo n ão funciona de forma correta. Entretanto, no final ele é pintado corretamente, pois a fun ção de desenho R e c t a n g l e n ão tem esse problema. Para corrigir esse problema, escrevemos uma fun ção simples que inverte as coordenadas de um ret â ngulo para fazê -las refletir os pedidos da chamada a Dr a wF o c u s Re c t : f u n c t i o n No r ma l i z e R e c t ( ARe c t : T Re c t ) : T Re c t ; var
t mp : I n t e g e r ; begin i f ARe c t . Bo t t o m < ARe c t . T o p begin
t mp : = ARe c t . Bo t t o m; ARe c t . Bo t t o m : = ARe c t . T o p; ARe c t . T o p : = t mp ; e nd; i f ARe c t . Ri g h t < ARe c t . L e f t
then
then
begin
t mp : ARe c t ARe c t e nd; Re s u l t e nd;
= ARe c t . Ri g h t ; . Ri g h t : = ARe c t . L e f t ; . L e f t : = t mp ; : = ARe c t ;
Finalmente, o manipulador do evento On Mo u s e U p configura o tamanho definitivo da imagem e atualiza a pintura do formulário. Em vez de chamar o m é todo I n v a l i d a t e , o que faria todas as imagens serem repintadas com muita cintilação, o programa usa a fun ção da API I n v a l i d a t e R e c t : p r o c e d u r e I n v a l i d a t e R e c t ( Wn d : HWn d ;
Re c t : P Re c t ; E r a s e : Bo o l ) ;
Os tr ês par âmetros representam o handle da janela (isto é, a propriedade Ha n d l e do formulá rio), o ret â ngulo que você deseja repintar e um flag indicando se você deseja ou n ão apagar a área antes de repintar. Essa fun ção exige, mais uma vez, um ret ângulo normalizado. (Voc ê pode tentar substituir essa chamada por um a chamada a I n v a l i d a t e , para ver a diferen ça, que é mais evidente qu ando você cria muitos formulários.) Aqui est á o có digo completo do manipulador do evento O n M o u s e U p : p r o c e d u r e T Sh a p e s F o r m. F o r mMo u s e U p ( S e n d e r : T Ob j e c t ; B u t t o n : T Mo u s e B u t t o n ;
S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; var
ARe c t : T Re c t ; begin i f f D r a g g i n g t h e n begin / / f i n al i z a o a r r a s t o
Recursos gráficos do Delphi
7
ReleaseCapture; f Dr a g g i n g : = F a l s e ; / / c o n f i g u r a o t a ma n ho f i n a l ARe c t : = No r ma l i z e R e c t ( C u r r S h a p e . Re c t ) ; Ca n v a s . Dr a wF o c u s R e c t ( ARe c t ) ; Cu r r S h a p e . R i g h t : = X; Cu r r S h a p e . Bo t t o m : = Y; / / c ó d i g o de i n v a l i d a ç ã o o t i mi z a d o ARe c t : = No r ma l i z e R e c t ( C u r r S h a p e . Re c t ) ; I n v a l i d a t e R e c t ( Ha n d l e , @ARe c t , F a l s e ) ; e nd; e nd;
NOTA
Quando você seleciona uma pena de desenho grande (veremos o có digo para isso em breve), a borda do quadro é pintada parcialmente dentro e parcialmente fora dele, para acomodar a pena grande. Para possibilitar isso, devemos invalidar um retângulo de moldura que é dilatado pela metade do tamanho da pena atual. Você pode fazer isso chamando a função I n f l a t e R e c t . Como alternativa, no método F o r mC r e a t e , configuramos a propriedade S t y l e de P e n do formulário C a n v a s como p s I n s i d e F r a me . Isso faz com que a função de desenho pinte a pena completamente dentro do quadro da figura.
N o m étodo correspondente ao evento On P a i n t , todas as figuras atualmente armazenadas na lista são pintadas, conforme você pode ver na Figura 1. Como o c ó digo de pintura afeta as propriedades de C a n v a s , precisamos armazenar os valores atuais e reinicializ á-los no final. O motivo é que, conforme mostraremos posteriormente neste cap í t ulo, as propriedades do canvas do formul ário sã o usadas para contro lar os atributo s selecionados pelo usuá rio, que poderia t ê-los mudado desde que a ú ltima figura foi criada. Aqui est á o có digo: p r o c e d u r e T Sh a p e s F o r m. F o r mP a i n t ( S e n d e r : T Ob j e c t ) ; var
I , Ol d P e n W: I n t e g e r ; AS h a p e : T Ba s e S h a p e ; Ol d P e n Co l , Ol d Br u s h Co l : T Co l o r ; begin
/ / a r ma z e n a o s a t r i b u t o s d e Ca n v as a t u al Ol d P e n Co l : = Ca n v a s . P e n . C o l o r ; Ol d P e n W : = Ca n v a s . P e n . Wi d t h ; Ol d Br u s h Co l : = Ca n v a s . Br u s h . C o l o r ; / / r e p i n t a c a da f i g ur a d a l i s t a f o r I : = 0 t o S h a p e Li s t . Co u n t - 1 d o begin
AS h a p e : = S h a p e L i s t . I t e ms [ I ] ; AS h a p e . P a i n t ( C a n v a s ) ; e nd; / / r e i n i c i a l i z a os a t r i b u t o s de Ca nv a s at u al Ca n v a s . P e n . C o l o r : = Ol d P e n Co l ; Ca n v a s . P e n . Wi d t h : = Ol d P e n W; Ca n v a s . Br u s h . Co l o r : = Ol d Br u s h C o l ; e nd;
8
Dominando o Delphi 6 — A Biblía
FIGURA 1
O exemplo Shapes pode ser usado para desenhar várias figuras, que ele armazena em uma lista.
Os outros m étodos do formulário são simples. Tr ês dos comandos de menu nos permitem usar as cores do fundo, as bordas da figura (a pena) e a área interna (o pincel). Esses m é todos usam o componente ColorDialog e armazenam o resultado nas propriedades do canvas do formul ário. Aqui est á um exemplo: p r o c e d u r e T Sh a p e s F o r m. P e n Co l o r 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin / / s e l e c i o n a u ma n ov a c o r p a r a a pe n a
Co l o r D i a l o g 1 . Co l o r : = C a n v a s . P e n . C o l o r ; i f Co l o r D i a l o g 1 . E x e c u t e t h e n Ca n v a s . P e n . C o l o r : = Co l o r Di a l o g 1 . C o l o r ; e nd;
As novas cores afetar ão as figuras criadas no futuro, mas n ã o as já existentes. A mesma estrat égia é usada para a largura das linhas (a pena), embora desta vez o programa tamb ém verifique se o valor se tornou pequeno demais, desativando o item de menu, se isso acontecer: p r o c e d u r e T Sh a p e s F o r m. De c r e a s e P e n S i z e 1 C l i c k ( S e n d e r : T Ob j e c t ) ; begin
Ca n v a s . P e n . Wi d t h : = Ca n v a s . P e n . Wi d t h - 2 ; i f Ca n v a s . P e n . Wi d t h < 3 t h e n De c r e a s e P e n S i z e 1 . E n a b l e d : = F a l s e ; e nd;
Para alterar as cores da borda (a pena) ou da superf íc ie (o pincel) da figura, usamos a caixa de di álogo Cor padr ão. Aqui est á um dos dois m étodos: p r o c e d u r e T Sh a p e s F o r m. P e n Co l o r 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
Co l o r D i a l o g 1 . Co l o r : = C a n v a s . P e n . C o l o r ; i f Co l o r D i a l o g 1 . E x e c u t e t h e n Ca n v a s . P e n . C o l o r : = Co l o r Di a l o g 1 . C o l o r ; e nd;
Na Figura 2, você pode ver out ro exemplo da saí d a do programa Shapes, desta vez usando outras cores para as figuras e seus fundos. O programa pede para o usu ário confirmar algumas operaçõ es, como a sa í d a do programa ou a remo ção de tod as as figuras da lista (com o comando File | New) : p r o c e d u r e T Sh a p e s F o r m. Ne w1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
Recursos gráficos do Delphi
9
i f ( S h a p e s Li s t . C o u n t > 0 ) a n d ( Me s s a g e Dl g ( ’ Ar e y o u s u r e y ou wa n t t o d e l e t e a l l t h e s h a p e s ?’ , mt Co n f i r ma t i o n , [ mb Ye s , mb No ] , 0 ) = i d Ye s ) then begin / / e x c l u i c a da ob j e t o f o r I : = S h a pe s L i s t . Co u n t - 1 d o wn t o 0 d o
T Ba s e S h a pe ( S h a p e s Li s t [ I ] ) . F r e e ; ShapesList.Clear; Refresh; e nd; e nd;
FIGURA 2
Alterar as cores e o tamanho da linha das figuras permite que você use o exemplo Shapes para produzir qualquer tipo de resultado.
Impr imindo Figur as Além de pintar as figuras em um canvas de formul ário, podemos pint á -las em um canvas de impressora, efetivamente imprimindo-as! Pelo fato de ser poss í v el executar os mesmos m é todos em um canvas de impressora como em qualquer outro canvas, voc ê poderia ficar tentado a incluir no programa um novo m étodo para imprimir as figuras. Com certeza isso é f á cil, mas uma op ção ainda melhor é escrever um ú nico m étodo de sa í d a para usar para a tela e para a impressora. Como exemplo dessa estrat égia, constru í m os uma n ova versão do programa, chamada ShapesPr. O ponto interessante é que movemos o c ó digo do exemplo F o r mP a i n t para outro m étodo que definimos, chamado Co mmo n P a i n t . Esse novo m étodo possui dois par â metros, o canvas e um fator de escala (cujo padr ão é 1) : p r o c e d u r e Co mmo n P a i n t ( Ca n v a s : T Ca n v a s ; S c a l e : I n t e g e r = 1 ) ;
O m étodo C o mmo n P a i n t produz como saí d a a lista de figuras do canvas, passadas como par âme tros, usando o fator de escala correto: p r o c e d u r e T S h a p e s F o r m. C o mmo n P a i n t (
Ca n v a s : T Ca n v a s ; S c a l e : I n t e g e r ) ; var
I , Ol d P e n W: I n t e g e r ; AS h a p e : T Ba s e S h a p e ; Ol d P e n Co l , Ol d Br u s h Co l : T Co l o r ; begin / / a r ma z e n a os a t r i b u t o s d o Ca n v as a t u al
Ol d P e n Co l : = Ca n v a s . P e n . C o l o r ;
10
Dominando o Delphi 6 — A Biblía
Ol d P e n W : = Ca n v a s . P e n . Wi d t h ; Ol d B r u s h C ol : = Ca n v a s . Br u s h . Co l o r ; / / r e p i n t a c a da f i g ur a d a l i s t a f o r I : = 0 t o S h a p e s Li s t . C o u n t - 1 d o begin
AS h a p e : = S h a p e s L i s t . I t e ms [ I ] ; AS h a p e . P a i n t ( C a n v a s , S c a l e ) ; e nd; / / r e i n i c i a l i z a os a t r i b u t o s d o Ca nv a s at u al Ca n v a s . P e n . C o l o r : = Ol d P e n Co l ; Ca n v a s . P e n . Wi d t h : = Ol d P e n W; Ca n v a s . Br u s h . Co l o r : = Ol d Br u s h Co l ; e nd;
Uma vez escrito esse có digo, os m étodos F o r mP a i n t e P r i n t 1 C l i c k são simples de implementar. Para pintar a imagem na tela, você pode chamar C o mmo n P a i n t sem um fator de escala (para que o valor padr ão de 1 seja usado): p r o c e d u r e T Sh a p e s F o r m. F o r mP a i n t ( S e n d e r : T Ob j e c t ) ; begin
Co mmo n P a i n t ( Ca n v a s ) ; e nd;
Para pintar o conteú do do formulário na imp ressora, em vez do formul ário, você pode reproduzir a saí d a no canvas da impressora, usando um fator de escala correto. Em vez de escolher uma escala, decidimos calculá-la automaticamente. A id é ia é imprimir as figuras no formul ário com o maior tamanho possí v el, d imensionando a área cliente do formulário de modo que ela ocupe a p ágina inteira. O có digo provavelmente é mais simples do que a descri ção: p r o c e d u r e T Sh a p e s F o r m. P r i n t 1 C l i c k ( S e n d e r : T Ob j e c t ) ; var
S ca l e , S ca l e 1 : I n t e g e r ; begin
S c a l e : = P r i n t e r . P a g e Wi d t h d i v C l i e n t Wi d t h ; S c a l e 1 : = Pr i n t e r . P a g e He i g h t d i v Cl i e n t He i g h t ; i f S c a l e 1 < S c a l e t h e n S c a l e : = Sc a l e 1 ; P r i n t e r . Be g i n Do c ; try
Co mmo n P a i n t ( P r i n t e r . Ca n v a s , S c a l e ) ; P r i n t e r . E n d Do c ; except
P r i n t e r . Ab o r t ; r a i s e; e nd; e nd;
É claro que você precisa se lembrar de chamar os comandos espec í ficos para com eçar a imprimir ( Be g i n Do c ) e efetivar a saí da ( E n d D o c ), antes e depois de chamar o m étodo C o mmo n P a i n t . Se uma exce ção for lan çada, o programa chama A b o r t para terminar o processo de impress ão de qualquer maneira.
Recursos gráficos do Delphi
11
Componentes Gr áficos do Delphi O exemplo Shapes usa quase nenhum componente, fora uma caixa de di á logo de seleção de cor padr ão. Como alternativa, poder í a mos ter usado alguns componentes do Delphi que suportam especificamente figuras: s
s
s
s
s
s
Vo cê usa o componente PaintBox quando precisa pintar em uma certa área de um formul ário que pode se mover no formul ário. Por exemplo, PaintBox é ú til para pintar em uma caixa de di á logo sem o risco de misturar a área da saí d a com a área dos controles. O componente PaintBox poderia se encaixar dentro de outros controles de um formul ário, como uma barra de ferramentas ou uma barra de status, e evitar qualquer confusã o ou sobreposi ção da sa í da . No exemplo Shapes, usar esse component e n ão fazia sentido, pois sempre trabalhamos na superf í c ie inteira do formulário. Vo cê usa o comp onente Shape para pintar figuras na tela, exatamente como fizemos até agora. Você poderia usar o componente Shape em lugar da saí da manual, mas queremos mostrar como se realizam algumas operações de saí da diretas. Esta estrat égia n ão era muito mais complexa do que a que o Delphi sugere. Usar o componente Shape teria sido útil para estender o exemplo, permitindo ao usuário arrastar figuras na tela, removê-las e trabalhar com elas de várias outras maneiras. Vo cê pode u sar o component e Image para apresentar um bitmap existente, po ssivelmente carregando-o de um arquivo, ou mesmo para pintar sobre um bitmap, conforme demonstraremos nos pr ó ximos dois exemplos e que discutiremos na pr ó xima seção. Se ele estiver incluí d o em sua vers ão de D elphi, voc ê pode usar o contro le TeeChart para gerar gr áficos comerciais, conforme veremos no final deste cap í t ulo. Vo cê pode usar o suporte gr áfico fornecido pelos controles bot õ es de bitmap e speed buton, entre outros. Posteriormente neste cap í t ulo, veremos como estender os recursos gr áficos desses controles. Vo cê pode usar o componente Animate para tornar as figuras animadas. Al ém de usar esse componente, você pode criar animaçõ es manualmente, apresentando bitm aps em seqüê ncia ou rolando-os, conforme veremos em outros exemplos.
Como você pode ver, temos um longo caminho pela frente para abordar o suporte gr áfico do Delphi de todos os seus ângulos.
Desenhando em um Bitmap Já mencionamos que, usando um componente Image, voc ê pode desenhar imagens diretamente em um b itmap. Em vez de desenhar na superf í c ie de uma janela, você desenha em um bitmap na mem ó ria e depois copia o bitm ap na superf í c ie da janela. A vantagem é que, em vez de ter d e repintar a imagem cada vez que um evento On P a i n t ocorrer, o componente copia o bitmap para o ví d eo. Tecnicamente, um objeto T Bi t ma p tem seu pr ó prio canvas. Desenhando nesse canvas, você pode mudar o conteú do do bitmap. Como alternativa, voc ê pode trabalhar no canvas de um bitmap Image conectado ao bitmap que deseja alterar. Você poderia considerar a escolha dessa estraté gia em lugar da estrat égia de pintura t í p ica se qualquer uma das seguintes condiçõ es fosse verdadeira: s
O programa precisa oferecer suporte a desenho plexas (como imagens fractais).
à m ão livre ou a imagens gr áficas muito com-
s
O programa deve ser muito r á pido no desenho de v árias imagens.
s
O consumo de mem ó ria RAM n ão
s
Vo cê
é problema.
é um programador preguiçoso.
12
Dominando o Delphi 6 — A Biblía
O ú ltimo ponto é interessante, pois a pintura geralmente exige mais có digo do que o desenho, embora ela permita mais flexibilidade. Em um programa gr áfico, por exemplo, se voc ê usar pintura, ter á de armazenar a localizaçã o e as cores de cada figura. Por ou tro lado, voc ê pode m udar facilmente a cor de uma figura existente ou movê-la. Essas opera çõ es são muito dif í c eis com a estrat égia de pintura, e podem fazer com que a área por tr ás de uma imagem seja perdida. Se voc ê estiver trabalhando em um aplicativo gr áfico complexo, provavelmente dever á escolher uma combinação das duas estrat égias. Para o s programadores gr á ficos casuais, a escolha entr e as duas estrat é gias envolve uma decisão t í p ica entre velocidade e mem ó ria: a pintura exige menos mem ó ria; o armazenamento do bitmap é mais r ápido.
Desenhando Figuras Agora, vamos ver um exemplo com o componente Image que pintar á em um bitmap. A id éia é simples. Basicamente, escrevemos uma versão simplificada do exemplo Shape, colocando um component e Image em seu formulário e redirecionando toda as operaçõ es de saí da para o canvas desse componente. Neste exemplo, ShapeBmp, t amb ém inclu í m os alguns itens de menu novos, para salvar a imagem em um arquivo e para carregar um bitmap existente. Para fazer isso, inclu í m os no formulário dois componentes de diá logo padr ão, OpenDialog e SaveDialog. Uma das propriedades que tivemos de mudar foi a cor de fundo do formulá rio. Na verdade, quando voc ê executa a primeira opera ção gr áfica na imagem, ela cria um bitmap que possui um fundo branco como padr ã o. Se o formul ário tem um fundo cinza, sempre que a janela é repintada, alguma cintilação ocorre. Por esse motivo, escolhemos um fundo branco tamb ém para o formulário. O có digo deste exemplo ainda é muito simples, considerando o n ú mero de operaçõ es e comandos de menu. A parte relativa ao desenho é linear e m uito p arecida com o exemplo Mo useOne, exceto que os eventos de mouse agora est ão relacionados à imagem; em vez do formul ário, usamos a fun ção No r m a l i z e R e c t durante o arrasto, e o programa usa o canvas da imagem. Aqui est á o manipulador do evento O n Mo u s e Mo v e , que reintroduz o desenho de pontos ao mover o mouse com a tecla Shift pressionada: p r o c e d u r e T Sh a p e s F o r m. I ma g e 1 Mo u s e Mo v e ( S e n d e r : T Ob j e c t ;
S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; var
ARe c t : T Re c t ; begin / / a p r e s e n t a a p o s i ç ã o d o m o u s e n o t í t u l o Ca p t i o n : = F o r ma t ( ’ S h a p e B mp ( x = %d , y = %d ) ’ , [ X , Y] ) ; i f f D r a g g i n g t h e n begin / / r e mo v e e r e d e s e n h a o r e t â n gu l o d e a r r a s t o
ARe c t : = No r ma l i z e R e c t ( f Re c t ) ; Ca n v a s . Dr a wF o c u s R e c t ( ARe c t ) ; f R e c t . Ri g h t : = X; f R e c t . Bo t t o m : = Y; ARe c t : = No r ma l i z e R e c t ( f Re c t ) ; Ca n v a s . Dr a wF o c u s R e c t ( ARe c t ) ; end else i f s s S h i f t i n S h i f t t h e n / / ma r c a o p o n t o e m v e r me l h o
I ma g e 1 . Ca n v a s . P i x e l s [ X, Y] : = c l Re d ; e nd;
Recursos gráficos do Delphi
13
Note que o ret ângulo de foco tempor ário é pintado diretamente no formulário, sobre a imagem (e, assim, n ã o é armazenado no bitmap). A diferen ça é que no final da opera ção de desenho, o programa pinta o ret â ngulo na imagem, armazenando-o no bitmap. Desta vez o programa n ão chama I n v a l i d a t e e n ão tem manipulador do evento OnPaint: p r o c e d u r e T Sh a p e s F o r m. I ma g e 1 Mo u s e U p ( S e n d e r : T Ob j e c t ;
Bu t t o n : T Mo u s e Bu t t o n ; S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; begin i f f D r a g g i n g begin
then
ReleaseCapture; f D r a g g i n g : = Fa l s e ; I ma g e 1 . C a n v a s . Re c t a n g l e ( f Re c t . L e f t , f R e c t . T o p , f R e c t . Ri g h t , f R e c t . Bo t t o m) ; e nd; e nd;
Para evitar u m suporte a arqu ivos demasiadamente complexo, d ecidimos implementar os com andos File | Load e File | Save As e nã o manipular o comando Save, que em geral é mais complexo. Simplesmente inclu í m os um campo f C h a n g e d no formulário, para saber quando uma imagem mudou, e inclu í m os c ó digo que verifica esse valor várias vezes (antes de pedir confirmação para o usu ário). O manipulador do evento On Cl i c k do item de menu File | New chama o m étodo F i l l A r e a para pintar um grande ret ângulo branco sobre o bitmap inteiro. Neste c ó digo, você tamb ém pode ver como o campo C h a n g e d é usado: p r o c e d u r e T Sh a p e s F o r m. Ne w1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
Ar e a : T Re c t ; Ol d Co l o r : T Co l o r ; begin i f n o t f C h a n g e d o r ( Me s s a g e Dl g ( ’ Ar e y o u s u r e y o u wa n t t o d e l e t e t h e c u r r e n t i ma g e ?’ , then mt Co n f i r ma t i o n , [ mb Ye s , mb No ] , 0 ) = i d Ye s ) begin { r e p i n t a a s u pe r f í c i e , c o b r i n do a á r e a i n t e i r a , e r e i n i c i a l i z a nd o o p i n c e l a nt i g o}
Ar e a : = Re c t ( 0 , 0 , I ma g e 1 . P i c t u r e . Wi d t h , I ma g e 1 . P i c t u r e . He i g h t ) ; Ol d Co l o r : = I ma g e 1 . Ca n v a s . Br u s h . Co l o r ; I ma g e 1 . Ca n v a s . Br u s h . Co l o r : = c l Wh i t e ; I ma g e 1 . C a n v a s . F i l l R e c t ( A r e a ) ; I ma g e 1 . Ca n v a s . Br u s h . Co l o r : = Ol d Co l o r ; f C h a n ge d : = F a l s e ; end; e nd;
É claro que o có digo tem de salvar a cor original e restaur á-la posteriormente. U m realinhamento das cores tamb ém é exigido pelo m étodo de resposta do comando File | Load. Quando você carrega um novo bitmap, na verdade, o componente Image cria um novo canvas com os atributos padr ão. Por esse motivo, o programa salva as cores e o tamanho da pena e os copia posteriormente no novo canvas: p r o c e d u r e T Sh a p e s F o r m. L oa d 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
P e n Co l , Br u s h Co l : T Co l o r ;
14
Dominando o Delphi 6 — A Biblía
P e nS i z e : I n t e g e r ; begin i f n o t f C h a n g e d o r ( Me s s a g e Dl g ( ’ Ar e y o u s u r e y o u wa n t t o de l e t e t h e c u r r e n t i ma ge ?’ , mt Co n f i r ma t i o n , [ mb Ye s , mb No ] , 0 ) = i d Y e s ) then i f Op e n Di a l o g 1 . E x e c u t e t h e n begin
P e n Co l : = I ma g e 1 . Ca n v a s . P e n . C o l o r ; Br u s h Co l : = I ma g e 1 . Ca n v a s . Br u s h . Co l o r ; P e n Si z e : = I ma g e 1 . Ca n v a s . P e n . Wi d t h ; I ma g e 1 . P i c t u r e . L o a d F r o mF i l e ( O p e n Di a l o g 1 . F i l e n a me ) ; I ma g e 1 . Ca n v a s . P e n . C o l o r : = Pe n Co l ; I ma g e 1 . Ca n v a s . Br u s h . Co l o r : = Br u s h Co l ; I ma g e 1 . Ca n v a s . P e n . Wi d t h : = P e n Si z e ; fChanged : = Fals e; e nd; e nd;
Salvar a imagem atual
é muito mais simples:
p r o c e d u r e T Sh a p e s F o r m. S a v e a s 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin i f S a v e Di a l o g 1 . E x e c u t e t h e n begin
I ma g e 1 . P i c t u r e . S a v e T o F i l e ( S a v e Di a l o g 1 . F i l e n a me ) ; fChanged : = Fals e; e nd; e nd;
Finalmente, aqui est á o có digo do evento On Cl o s e Qu e r y do formulário, que usa o campo C h a n -
ge d: p r o c e d u r e T Sh a p e s F o r m. F o r mCl o s e Q u e r y ( S e n d e r : T Ob j e c t ; v a r Ca n Cl o s e : Bo o l e a n ) ; begin i f n o t f C h a n g e d o r ( Me s s a g e Dl g ( ’ Ar e y o u s u r e y o u wa n t t o d e l e t e t h e c u r r e n t i ma g e ?’ , mt Co n f i r ma t i o n , [ mb Ye s , mb No ] , 0 ) = i d Ye s ) then
Ca n Cl o s e : = Tr u e else
Ca n Cl o s e : = F a l s e ; e nd;
ShapeBmp é um programa interessante (veja a Figura 3), com suporte a arquivos limitado, mas funcional. O problema em si é que o componente Image cria um bitmap de seu pr ó prio tamanho. Quando você aumenta o tamanho da janela, o componente Image é redimensionado, mas n ã o o bitmap presente na mem ó ria. Portanto, você n ão pode desenhar nas áreas direita e inferior da janela. Existem muitas solu çõ es possí v eis: usar a propriedade C o n s t r a i n t s para configurar o tamanho m á ximo do formulário, usar uma borda fixa, marcar visualmente a á rea de desenho na tela etc. Entretanto, decidimos deixar o programa como est á, porque ele realiza suficientemente bem o trabalho de demonstrar como se desenha em um bitmap.
Recursos gráficos do Delphi
15
FIGURA 3
O exemplo ShapeBMP tem suporte a arquivos limitado, mas funcional: você pode carregar um bitmap existente, desenhar figuras sobre ele e salvá-lo no disco.
Um Visualizador de Imagens O programa ShadeBmp pode ser usado como um visualizador de imagens, pois voc ê pode carregar qualquer bitmap nele. Em geral, no controle Image, você pode carregar qualquer tipo de arquivo grá fico que tenha sido registrado com a classe T p i c t u r e da VCL. Os formatos de arquivo padr ão são arquivos de bitmap (BMP), arquivos de í c one (ICO) ou me t a - a r q u i v o s do Windows (WMF). Os arquivos de bitmap e í c one são formatos bem conhecidos. Os meta-arquivos do Windows, entretanto, n ã o são t ão comu ns. Eles são um conjunto de comandos gr áficos, semelhantes a uma lista de chamadas de fun ção de GDI que precisam ser executadas para reconstruir uma imagem. Os meta-arquivos normalmente são referidos como imagens gr á ficas vetoriais e são parecidos com os formatos de arquivo gr á fico usados para bibliotecas de clip-art. O Delphi tamb ém vem com suporte de JPG para T I m a g e , e outros fornecedores possuem GIF e outros formatos de arquivo tratados.
NOTA
Para produzir um me t a - a r q u i v o do Windows, um programa deve chamar funções da GDI, redirecionando sua saída para o arquivo. No Delphi, você pode usar um método T Me t a f i l e C a n v a s e os métodos T C a n v a s de alto nível. Posteriormente, esse me t a - a r q u i v o pode ser reproduzido ou executado para chamar as funções correspondentes, produzindo assim uma figura. Os me t a - a r q u i v o s t êm duas vantagens principais: a quantidade de armazenamento limitada que eles exigem em comparação a outros formatos gráficos e a independência de dispositivo de sua saída. Vamos abordar o suporte a me t a - a r q u i v o do Delphi posteriormente neste capítulo.
Para construir um programa visualizador de imagens completo, ImageV, em torno do componente Image, precisamos apenas criar um formulário com uma imagem que preencha a área cliente inteira, um menu simples e um componente OpenDialog: o b j e c t Vi e we r F o r m: T Vi e we r F o r m Ca p t i o n = ’ I ma g e V i e w e r’
Me n u = Ma i n Me n u 1 o b j e c t I ma g e 1 : T I ma g e Al i g n = a l Cl i e n t e nd o b j e c t Ma i n Me n u 1 : T Ma i n Me n u o b j e c t F i l e 1 : TMe n u I t e m. . . o b j e c t Op e n 1 : T Me n u I t e m. . .
16
Dominando o Delphi 6 — A Biblía
o b j e c t E xi t 1 : T Me n u I t e m. . . o b j e c t Op t i o n s 1 : T Me n u I t e m o b j e c t S t r e t c h 1 : TMe n u I t e m o b j e c t Ce n t e r 1 : T Me n u I t e m o b j e c t He l p 1 : T Me n u I t e m o b j e c t Ab o u t I ma g e Vi e we r 1 : T Me n u I t e m e nd o b j e c t Op e n Di a l o g 1 : T Op e n Di a l o g
F i l e E di t S t y l e = f s E di t F i l t e r = ’ Bi t ma p ( * . b mp ) | * . b mp | I c o n ( * . i c o ) | * . i c o | Me t a f i l e ( * . mf ) | * . mf ’ Op t i o n s = [ o f Hi d e Re a d On l y , o f P a t h Mu s t E xi s t , o f F i l e Mu s t E x i s t ] e nd e nd
De forma surpreendente, esse aplicativo exige muito pouco có digo, pelo meno s em sua primeira versão b ásica. Os comandos File | Exit e H elp | About são simples, e o comando File | Open t em o seguinte có digo: p r o c e d u r e T Vi e we r F o r m. Op e n 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin i f Op e n Di a l o g 1 . E x e c u t e t h e n begin
I ma g e 1 . P i c t u r e . L o a d F r o mF i l e ( O pe n Di a l o g 1 . F i l e Na me ) ; Ca p t i o n : = ’ I ma g e V i e w e r - ’ + Op e n Di a l o g 1 . F i l e Na me ; e nd; e nd;
O q uarto e o quinto comandos de menu, Op tions | Stretch e Options | Center, simplesmente ativam/desativam a propriedade S t r e t c h (veja o resultado na Figura 4) e a propriedade C e n t e r do componente e incluem uma marca de seleção em si mesmos. Aqui est á o manipulador do evento On C l i c k do item de menu S t r e t c h 1 : p r o c e d u r e T Vi e we r F o r m. S t r e t c h 1 Cl i c k ( S e n d e r : begin I ma g e 1 . S t r e t c h : = n o t I ma g e 1 . S t r e t c h ;
TOb j e c t ) ;
S t r e t c h 1 . C h e c k e d : = I ma g e 1 . S t r e t c h ; e nd;
Lembre-se de que, ao alongar uma imagem, você pode mudar sua rela ção largura-altura, possivelmente d istorcendo a figura, e que nem todas as imagens podem ser alongadas de forma correta. O alongamento de bitmaps em preto-e-branco e de 256 cores nem sempre funciona corretamente. Alé m desse problema, o aplicativo tem alguns outros inconvenientes. Se voc ê selecionar um arquivo sem uma das extens õ es padr ão, o componente Image lan çar á uma exceção. O manipulador de exceçõ es fornecido pelo sistema se comporta como esperado; o arquivo de imagem errado n ão é carregado, e o programa pode continuar com seguran ça. Outro problema é que, se você carregar uma imagem grande, o visualizador n ão p ossuir á barras de rolagem. Voc ê pode maximizar a janela do visualizador, mas isso poder á n ão ser suficiente. Os componentes Image n ão manipulam barras de rolagem automaticamente, mas o formulário pode fazer isso. A seguir, vamos estender ainda mais esse exemplo, para incluir barras de rolagem.
Recursos gráficos do Delphi
17
FIGURA 4
Dois exemplos do programa ImageV, que apresentam as versõ es normal e alongada do m esmo bitmap.
Rolando uma Imagem Uma vantagem do modo como o rolamento autom ático funciona no Delphi é que, se o tamanho de um componente grande contido em um formul ário muda, barras de rolagem s ão inclu í d as ou removidas automaticamente. U m bo m exemplo é o uso do componente Image. Se a propriedade Au t o S i z e desse componente for configurada para T r u e e você carregar uma nova figura nele, o componente se dimensionará automaticamente, e o formulário incluirá ou remover á as barras de rolagem, conforme for necessário. Se você carregar um bitmap grande no exemplo ImageV, notar á que parte do bitmap permanece oculta. Para corrigir isso, você pode configurar a propriedade Au t o S i z e do componente Image para T r u e e desativar seu alinhamento com a área cliente. Você tamb ém deve configurar um tamanho inicial pequeno para a imagem. Voc ê n ã o precisa fazer quaisquer ajustes quando carrega um novo bitmap, pois o tamanho do componente Image é configurado automat icamente pelo sistema. Na Figura 5 você pode ver que barras de rolagem são realmente inclu í d as no formulário. A figura mostra duas execuçõ es diferentes do programa. A diferen ça entre a execu ção do programa à esquerda e a que est á à direita é que a primeira tem uma imagem menor do que sua área cliente; portanto, nenhuma barra de rolagem é inserida. Quand o você carregar uma imagem maior no pro grama, duas barras de rolagem aparecerão automaticamente, como no exemplo da direita.
18
Dominando o Delphi 6 — A Biblía
FIGURA 5
No exemplo ImageV2, barras de rolagem são inclu í das automaticamente no formulário, quando o b itmap inteiro n ão cabe na área cliente do formulário apresentado.
Mais algum c ó digo é exigido para d esativar as barras de rolagem e mudar o alinhamento da imagem quando o comando de menu Stretch é selecionado e para restaurá-las quando esse recurso for desativado. De novo, n ão atuamos diretamente sobre as barras de rolagem em si, mas simplesmente mudamos o alinhamento do painel, usando sua propriedade S t r e t c h , e calculamos manualmente o novo tamanho, usando o tamanho da figura atualmente carregada. (Este c ó digo imita o efeito da propriedade Au t o S i z e , que funciona apenas quando um novo arquivo é carregado.) p r o c e d u r e T Vi e we r F o r m. S t r e t c h 1 Cl i c k ( S e n d e r : begin I ma g e 1 . S t r e t c h : = n o t I ma g e 1 . S t r e t c h ;
TOb j e c t ) ;
S t r e t c h 1 . C h e c k e d : = I ma g e 1 . S t r e t c h ; i f I ma g e 1 . S t r e t c h t h e n I ma g e 1 . Al i g n : = a l Cl i e n t else begin
I ma g e 1 . Al i g n : = a l No n e ; I ma g e 1 . He i g h t : = I ma g e 1 . P i c t u r e . He i g h t ; I ma g e 1 . Wi d t h : = I ma g e 1 . P i c t u r e . Wi d t h ; e nd; e nd;
Bitmaps ao M áximo Quando o controle Image est á conectado a um bitmap, existem algumas operaçõ es adicionais que você deve realizar, mas, antes de examin á-las, temos que apresentar os formatos de bitmap. Existem diferentes tipos de bitmaps no Windows. Os bitmaps podem ser independentes de dispositivo ou n ão, um termo u sado para indicar se o bitm ap tem informa çõ es de gerenciamento de paleta extras. Os arquivos BMP normalmente são bitmaps independentes de dispositivo. Outra diferen ça se relaciona com a profundidade d e cores — isto é, o n ú mero d e diferentes cores que o bitmap pode usar ou, em outras palavras, o n ú mero de bits exigidos para armazenar cada pixel. Em um bitmap de 1 bit, cada ponto pode ser branco ou preto (para sermos mais precisos, os bitmaps de 1 bit podem ter uma paleta de cores, permitindo que ele represente quaisquer duas cores e n ão apenas preto e branco). Um bitmap de 8 bits normalmente possui uma paleta acompanhante para indicar como as 256 diferentes cores s ão mapeadas nas cores de sistema reais, um bitmap de 24 bits indica a cor de sistema diretamente. Para tornar as coisas mais complexas, quando o sistema desenha um bitmap em um computador com uma capacidade de cores diferente, ele precisa realizar alguma conversão. Internamente, o formato do bitmap é muito simples, qualquer que seja a profundidade de cores. Todos os valores que constituem uma linha s ão armazenados em um bloco de mem ó ria. Isso é efi-
Recursos gráficos do Delphi
19
ciente para m over os dados da mem ó ria para a tela, mas n ão é um modo t ã o eficiente para armazenar informaçõ es; os arquivos BMP geralmente são muito grandes e eles n ão t êm compactação.
NOTA
Na verdade, o formato BMP tem uma forma muito limitada de compactação, conhecida como RunLength Encoding (RLE), em que os pixels subseqüentes com a mesma cor são substituídos pelo número de tais pixels seguidos da cor. Isso pode reduzir o tamanho da imagem, mas, em alguns casos, a fará crescer. Para imagens compactadas no Delphi, você pode usar a classe T J p e g I m a g e e o suporte ao fo rmat o JPEG oferecido pela classe T P i c t u r e . Na verdade, tudo que T P i c t u r e faz é gerenciar uma lista registrada de classes gráficas.
O exemplo BmpDraw usa essa informa ção sobre a estrutura interna de um bitmap e alguns outros recursos t écnicos para levar a manipulação direta de bitmaps para um novo n í v el. Primeiro, ele estende o exemplo ImageV através da inclusão de um item de menu que voc ê pode usar para apresentar a profundidade de cores do bitmap atual, usando a propriedade P i x e l F o r ma t correspondente: p r o c e d u r e T Bi t ma p F o r m. Co l o r De p t h 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
s t r De p t h : S t r i n g ; begin c a s e I ma g e 1 . P i c t u r e . Bi t ma p . P i x e l F o r ma t o f p f De v i c e : s t r De p t h : = ’ De v i c e ’ ; p f 1 b i t : s t r De p t h : = ’ 1 - b i t ’ ; p f 4 b i t : s t r De p t h : = ’ 4 - b i t ’ ; p f 8 b i t : s t r De p t h : = ’ 8 - b i t ’ ; p f 1 5 b i t : s t r De p t h : = ’ 1 5 - b i t ’ ; p f 1 6 b i t : s t r De p t h : = ’ 1 6 - b i t ’ ; p f 2 4 b i t : s t r De p t h : = ’ 2 4 - b i t ’ ; p f 3 2 b i t : s t r De p t h : = ’ 3 2 - b i t ’ ; p f Cu s t o m: s t r De p t h : = ’ Cu s t o m’ ; e nd; Me s s a g e Dl g ( ’ Bi t ma p c o l o r d e p t h : ’ + s t r D e p t h ,
mt I n f o r ma t i o n , [ mb OK] , 0 ) ; e nd;
Vo cê pode tentar carregar bitmaps diferentes e ver o efeito desse método, como ilustra a Figura 6. O mais interessante é estudar como se acessa a imagem de mem ó ria mantida pelo objeto bitmap. Um a solu ção simples é usar a propriedade P i x e l s , conforme fizemos no exemplo ShapeBmp, para desenhar os pixels vermelhos durante a operação de arrasto. Nesse programa, inclu í m os um item de menu para criar um bitmap inteiramente novo, pixel por pixel, usando um c álculo matem ático simples para determinar a cor. (A mesma estrat égia pode ser usada, por exemplo, para construir imagens fractais.) Aqui est á o có digo do m étodo, que simplesmente varre o bitmap nas duas dire çõ es e define a cor de cada pixel. Como estamos realizando muitas opera çõ es sobre o bitmap, podemos armazenar uma refer ência a ele na vari ável local Bmp , por simplicidade: p r o c e d u r e T Bi t ma p F o r m. Ge n e r a t e S l o w1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
B mp : T Bi t ma p ; I , J , T : I n t e g er ; begin / / o bt é m a i ma g e m e a mo d i f i c a
Bmp : = I ma g e 1 . P i c t u r e . Bi t ma p ; Bmp . P i x e l F o r ma t : = p f 2 4 b i t ; B mp . Wi d t h : = 2 5 6 ;
20
Dominando o Delphi 6 — A Biblía
Bmp . He i g h t : = 2 5 6 ; T : = Ge t T i c k C o u n t ; / / a l t e r a c ad a p i x e l f o r I : = 0 t o Bmp . He i g h t f o r J : = 0 t o B mp . Wi d t h Bmp . C a n v a s . P i x e l s [ I , Ca p t i o n : = ’ I ma g e V i e w e r I n t T o S t r ( Ge t T i c k C ou n t e nd;
1 do - 1 do J ] : = RGB ( I * J mo d 2 5 5 , I , J ) ; Me mo r y I ma g e ( MS e c s : ’ + T) + ’ ) ’ ;
FIGURA 6
A profundidade de cores de um bitmap padr ão do Windows, conforme apresentada pelo exemplo BmpDR AW.
Note que o programa controla o tempo exigido por essa opera ção, que, em meu computador, levou cerca de seis segundos. Conforme se v ê a partir do nome da fun ção, essa é uma versão lenta do có digo. Podemos aceler á-la consideravelmente acessando o bitmap uma linha inteira por vez. Esse recurso pouco conhecido está disponí v el através da propriedade S c a n Li n e do bitmap, que retorna um ponteiro para a área de mem ó ria da linha de bitmap. Pegando esse ponteiro e acessando a mem ó ria diretamente, tornamos o programa muito mais r ápido. O ú nico problema é que precisamos conhecer a representação interna do bitmap. No caso de um b itmap de 24 bits, todo po nto é representado por tr ês bytes que definem a intensidade de azul, verde e vermelho (o inverso da seq üê ncia RGB). Aqui est á o có digo alternativo, com uma saí d a ligeiramente diferente (pois mo dificamos deliberadamente o cálculo da cor): p r o c e d u r e T Bi t ma p F o r m. Ge n e r a t e F a s t 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
B mp : T Bi t ma p ; I , J , T : I n t e g er ; L i n e : P By t e Ar r a y ; begin / / o bt é m a i ma g e m e a mo d i f i c a
Bmp : = I ma g e 1 . P i c t u r e . Bi t ma p ; Bmp . P i x e l F o r ma t : = p f 2 4 b i t ; B mp . Wi d t h : = 2 5 6 ; Bmp . He i g h t : = 2 5 6 ;
Recursos gráficos do Delphi
21
T : = Ge t T i c k C o u n t ; / / mu da c ad a p i x e l , l i n h a po r l i n ha f o r I : = 0 t o Bmp . He i g h t - 1 d o begin
L i n e : = PBy t e A r r a y ( Bmp . S c a n L i n e [ I ] ) ; f o r J : = 0 t o B mp . Wi d t h - 1 d o begin
Li n e [ J * 3 ] : = J ; L i n e [ J * 3 + 1 ] : = I * J mo d 2 5 5 ; Li n e [ J * 3 +2 ] : = I ; end; e nd; / / a t u a l i z a o v í d e o I ma g e 1 . I n v a l i d a t e ; Ca p t i o n : = ’ I ma g e V i e w e r - Me mo r y I ma g e ( MS e c s : ’ + I n t T o S t r ( Ge t T i c k C ou n t - T ) + ’ ) ’ ; end;
Apenas mover um a linha na mem ó ria n ão faz uma tela ser atualizada; port anto, o programa chama I n v a l i d a t e no final. A sa í d a produzida por este segundo m étodo (veja a Figura 7) é muito parecida, mas o tempo que ela levou em meu computado r foi de cerca de 60 milissegundos. Isso é quase um cent ésimo do tempo da outra estrat égia! Essa t écnica é t ão r ápida que podemos usá-la para rolar as linhas do bitmap e ainda produzir um efeito r ápido e suave. A opera ção de rolagem tem algumas op çõ es; p ortanto, quando você seleciona os it ens de menu correspond entes, o p rograma simplesmente mostra um painel dentro do formul ário. Esse painel tem uma barra de controle que voc ê pode usar para ajustar a velocidade da operação de rolagem (reduzindo sua suavidade à medida que a velocidade aumenta). A posição da barra de controle é salva em um campo local do formul á rio: p r o c e d u r e T Bi t ma p F o r m. T r a c k Ba r 1 C h a n ge ( S e n d e r : T Ob j e c t ) ; begin
n Li n e s : = Tr a c k Ba r 1 . P o s i t i o n ; T r a c k Ba r 1 . Hi n t : = I n t T o S t r ( T r a c k Ba r 1 . P o s i t i o n ) ; e nd;
FIGURA 7
O desenho que voc ê vê na tela é gerado pelo exemplo BmpD raw em uma fração de segundo (conforme informado em seu t í t ulo).
No painel existem tamb ém dois bot õ es, usados para iniciar e interromper a operação de rolagem. O có digo do bot ão Go tem dois la ços f o r . O la ço externo é usado para repetir a opera ção de rolagem,
22
Dominando o Delphi 6 — A Biblía
tantas vezes quantas forem as linhas no bitmap. O la ço int erno realiza a operação de rolagem copiando cada linha do bitmap na anterior. A primeira linha é armazenada temporariamente em um bloco de me m ó ria e depois copiada na ú ltima linha no final. Esse bloco de mem ó ria tempor á rio é mantido em um a á rea de mem ó ria alocada dinamicamente ( Al l o c Me m ), grande o suficiente para conter uma linha. Essa informação é obtida pelo cálculo da diferen ça nos endereços de mem ó ria de duas linhas consecutivas. O centro da operação de movimentação é efetuado através do uso da fun ção M o v e do Delphi. Seu par â metro é a variável a ser movida, e n ão os endereços de mem ó ria. Por isso, voc ê tem de retirar a refer ência aos ponteiros. (Bem, esse m é todo é realmente um bom exerc í c io sobre pon teiros!) Finalmente, note que, desta vez, n ã o podemos invalidar a imagem inteira ap ó s cada opera ção de rolagem, pois isso produz muito tremido na sa í d a. A solu ção oposta é invalidar cada linha depois de ela ter sido movida, mas isso torna o programa lento demais. Como uma solu ção intermediária, decidimos invalidar um bloco de linhas por vez, conforme d eterminado p ela expressão J mo d n L i n e s = 0 . Quando determinado n ú mero de linhas tiver sido movido, o programa atualiza essas linhas: Re c t ( 0 , P a n e l S c r o l l . He i g h t + H - n L i n e s , W, P a n e l S c r o l l . He i g h t + H) ;
Conforme você pode ver, o n ú mero de linhas
é determinado pela posição do controle TrackBar. Um usu ário pode at é mudar a velocidade movendo o controle speed durante a operação de rolagem. Tamb ém permitimos que o usu ário pressione o bot ã o Cancel durante a operação. Isso se tornou possí v el pela chamada a Ap p l i c a t i o n . P r o c e s s Me s s a g e s no laço f o r interno. O bot ã o Cancel altera o flag f C a n c e l , que é testado em cada iteração do la ço f o r externo: p r o c e d u r e T Bi t ma p F o r m. Bt n Ca n c e l Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
fCancel : = True; e nd;
Assim, ap ó s toda essa descrição, aqui est á o có digo completo do manipulador do evento On Cl i c k do bot ã o Go: p r o c e d u r e T Bi t ma p F o r m. Bt n Go Cl i c k ( S e n d e r : T Ob j e c t ) ; var
W, H , I , J , L i n e B y t e s : I n t e g e r ; L i n e : P By t e Ar r a y ; B mp : T Bi t ma p ; R: T R e c t ; begin / / c o nf i g ur a a i n t e r f a c e c om o u s u á r i o
f C a n c e l : = Fa l s e ; Bt n G o . E n a b l e d : = F a l s e ; Bt n C a n c e l . E n a b l e d : = Tr u e ; / / o b t é m o b i t ma p d a i ma g e m e o r e d i me n s i o n a Bmp : = I ma g e 1 . P i c t u r e . Bi t ma p ; W : = Bmp . Wi d t h ; H : = Bmp . He i g h t ; / / a l o c a me mó r i a s u f i c i e n t e p a r a u ma l i n h a L i n e By t e s : = Ab s ( I n t e g e r ( Bmp . S c a n Li n e [ 1 ] ) I n t e g e r ( Bmp . S c a n Li n e [ 0 ] ) ) ; L i n e : = Al l o c Me m ( L i n e B y t e s ) ;
Recursos gráficos do Delphi
23
/ / r o l a t a n t o s i t e n s qu a n t a s f o r e m a s l i n ha s f or I : = 0 t o H - 1 do begin / / s a i d o l a ç o f o r s e o b ot ã o Ca n c e l f o i p r e s s i o n ad o i f f C a n c e l t h e n Break; / / c o pi a a p r i me i r a l i n h a Mo v e ( ( B mp . S c a n L i n e [ 0 ] ) ^ , L i n e ^ , L i n e B y t e s ) ; / / p ar a t o d a l i n ha f or J : = 1 t o H - 1 do begin / / mo v e a l i n h a p ar a a a n t e r i o r Mo v e ( ( Bmp . S c a n Li n e [ J ] ) ^ , ( Bmp . S c a n L i n e [ J - 1 ] ) ^ , L i n e B y t e s ) ; / / c a da n Li n e s at u a l i z a a s a í d a i f ( J mo d n L i n e s = 0) t h e n begin R : = Re c t ( 0 , P a ne l S c r o l l . He i g h t + J - n L i n e s , W, P a n e l S c r o l l . H e i g h t + J ) ; I n v a l i d a t e R e c t ( Ha n d l e , @R, F a l s e ) ; Up d a t e Wi n d o w ( Ha n d l e ) ; end; e nd; / / mo v e a pr i me i r a l i n ha p ar a o f i n a l Mo v e ( L i n e ^ , ( B mp . S c a nL i n e [ B mp . He i g h t - 1 ] ) ^ , L i n e B y t e s ) ; / / a t u a l i z a a p ar t e f i n a l d o bi t ma p R : = Re c t ( 0 , Pa n e l S c r o l l . He i g h t + H - n L i n e s , W, Pa n e l S c r o l l . He i g ht + H) ; I n v a l i d a t e R e c t ( Ha n dl e , @R, F a l s e ) ; Up d a t e Wi n d o w ( Ha n d l e ) ; / / p e r mi t e q ue o p r o g r a ma ma n i p u l e o u t r a s me n s a g e n s Ap p l i c a t i o n. Pr o c e s s Me s s a g e s ; e nd;
/ / r e i n i c i a l i z a a UI Bt n G o . E n a b l e d : = T r u e ; Bt n C a n c e l . E n a b l e d : = F a l s e ; e nd;
Vo cê pode ver um bitmap durante a opera ção de rolagem na Figura 8. Note que a rolagem pode ocorrer em qualquer tipo de bitmap, e n ão apenas nos bitmaps de 24 bits gerados por este programa. Na verdade, você pode carregar outro bitmap no programa e depois rol á-lo, como fizemos para criar a ilustração.
Um Bitmap Animado em um Bot ão Os bot õ es de bitmap são f áceis de usar e podem produzir aplicativos de melhor apar ência do que os bo t õ es padr ão (o componente Button). Para aprimorar ainda mais o efeito visual de um bot ão, tamb ém podemos considerar a animação do bot ão. Existem basicamente dois tipos de bot õ es animados — bo t õ es que mudam sua imagem ligeiramente quando s ão pressionados e bot õ es que possuem uma
24
Dominando o Delphi 6 — A Biblía
imagem que se move, independentemente da opera ção. Vamos mostrar um exemplo simples de cada tipo, Fire e World. Para cada um dos exemplos, exploraremos duas vers õ es ligeiramente diferentes. FIGURA 8
O exemplo BmpDraw permite a rolagem r ápida de um bitmap.
Um Bot ão de D ois Est ados O primeiro exemplo, o programa Fire, tem um formul ário simples, contendo apenas um bot ão de bitmap. Esse bot ão est á conectado a um elemento Gl y p h que representa um canh ão. Imagine tal bot ão como parte de um programa de jogo. Quando o bot ão é pressionado, a figura muda p ara mostrar um canh ão atirando. Assim que o bot ã o é solto, a imagem padr ão é novamente carregada. Nesse í n terim, o programa apresenta uma mensagem, caso o usu á rio tenha realmente dado um clique no bot ão. Para escrever este programa, precisamos manipular tr ê s eventos do bot ão: On Mo u s e D o w n , O n M o u s e U p e On Cl i c k . O có digo dos tr ê s m étodos é extremamente simples: p r o c e d u r e T Fo r m1 . Bi t Bt n F i r e Mo u s e D o wn ( S e n d e r : TOb j e c t ;
Bu t t o n : T Mo u s e Bu t t o n ; S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; begin / / c a r r e g a o b i t ma p do c a nh ã o a t i r a n do i f Bu t t o n = mb Le f t t h e n Bi t Bt n F i r e . Gl y p h . L o a d F r o mF i l e ( ’ f i r e 2 . b mp ’ ) ; e nd; p r o c e d u r e T Fo r m1 . Bi t Bt n F i r e Mo u s e Up ( S e n d e r : T Ob j e c t ;
Bu t t o n : T Mo u s e Bu t t o n ; S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; begin / / c a r r e g a o b i t ma p do c a nh ã o p a d r ã o i f Bu t t o n = mb Le f t t h e n Bi t Bt n F i r e . Gl y p h . L o a d F r o mF i l e ( ’ f i r e . b mp ’ ) ; e nd; p r o c e d u r e T F o r m1 . B i t Bt n F i r e C l i c k ( S e n d e r : T Ob j e c t ) ; begin P l a y S o u n d ( ’ B o o m . w a v ’ , 0 , s n d _ As y n c ) ; Me s s a g e D l g ( ’ Boom! ’ , mt Wa r n i n g , [ mb OK] , 0 ) ; e nd;
Recursos gráficos do Delphi
25
Inclu í m os alguns recursos sonoros, reproduzindo um arquivo WAV quando o bot ão é pressionado, com uma chamada para a fun ção P l a y S o u n d da unidade MmSystem. Quando você mant ém o bo t ã o esquerdo do mouse pressionado sobre o bot ão de bitmap, o bot ã o é pressionado. Se, ent ã o, voc ê move o cursor do mouse para fora do bot ão, enquanto mant ém o bot ã o do mouse pressionado, o bot ão de bitmap é liberado, mas ele n ão recebe um evento On Mo u s e Up ; portanto, o canh ão atirando permanece lá. Se, posteriormente, você soltar o bot ã o esquerdo do mouse fora da superf í c ie do bot ão de bitmap, ele receber á o evento On Mo u s e U p . O motivo é que todos os bot õ es no Windows capturam a entrada de mouse quando s ão pressionados.
Muitas Imagens em um Bitmap O exemplo Fire usou uma estrat égia manual. Carregamos dois bitmaps e alteramos o valor da propriedade Gl y p h quando quer í a mos mudar a imagem. O componente BitBtn, entretanto, tamb ém pode manipular vários bitmaps automaticamente. Você pode preparar um ú nico bitmap que contenha várias imagens (ou grifos) e configurar esse n ú mero como o valor da propriedade Nu mGl y p h s . Todos esses “sub-bitmaps” devem ter o mesmo tamanho, pois o bitmap global é dividido em partes iguais. Se você fornecer mais de uma imagem no bitmap, elas ser ão usadas de acordo com as regras a seguir:
é usado para o bot ão solto, a posição padr ã o. s O segundo bitmap é usado para o bot ão desativado. s O terceiro bitmap é usado quando o bot ão recebe um clique de mouse. s O quarto bitmap é usado quando o bot ã o permanece pressionado, como nos bot õ es que se comportam como caixas de sele ção. Normalmente, você fornece uma ú nica imagem, e as outras s ã o automaticamente calculadas a partir dela, com alteraçõ es gr á ficas simples. Entretanto, é f ácil fornecer uma segunda, uma terceira e uma quarta figura personalizada. Se voc ê n ão fornecer todo s os quatro bit maps, os ausentes serão cals
O primeiro bitmap
culados automaticamente a partir do primeiro. Em nosso exemplo, a nova versão de Fire (chamada Fire2), precisamos apenas da primeira e da terceira imagens do bitmap, mas somos obrigados a incluir o segundo bitmap. Para ver como essa imagem (a segunda do b itmap) po de ser usada, incluí m os uma caixa de sele ção para desativar o bo t ão de bitmap. Para construir a nova versã o do programa, preparamos um bitmap de 32x96 pixels (veja a Figura 9) e o usamos para a propriedade Gl y p h do bitmap. O Delphi configura automaticamente a propriedade Nu mGl y p h s em 3, pois o bitmap é tr ê s vezes mais largo do que sua altura. FIGURA 9
O bitmap com tr ês imagens do exemplo Fire2, conforme visto no Delphi Image Editor.
26
Dominando o Delphi 6 — A Biblía
A caixa de seleção, usada para ativar e desativar o bot ão (para que possamos ver a imagem correspondente ao status desativado), tem o seguinte evento On Cl i c k : p r o c e d u r e T Fo r m1 . Ch e c k Bo x 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
Bi t Bt n F i r e . E n a b l e d : = Ch e c k Bo x 1 . C h e c k e d ; e nd;
Quando você executa o programa, existem duas maneiras de mudar o bitmap no bot ã o. Você pode desativar o bot ão de bitmap usando a caixa de seleção (veja a Figura 10) ou pode pressionar o bo t ã o para ver o canh ão atirar. N a primeira versão (o exemplo Fire), a imagem com o canh ão atirando permanecia no bot ão at é que a caixa de mensagem fosse fechada. Agora (no exemplo Fire2), a imagem é mostrada apenas enquanto o bot ão est á pressionado. Assim que você sai da superf í c ie do bot ão, a primeira imagem é apresentada. FIGURA 1 0
Os bot õ es de bitmap ativado e desativado do exemplo Fire2, em duas execuçõ es diferentes do aplicativo.
O Mundo Girando O segundo exemplo de animação, World, tem um bot ão que apresenta a Terra, que gira lentamente, mostrando os vários continentes. Você pode ver alguns exemplos na Figura 11, mas, é claro, deve executar o programa para ver sua sa í d a. No exemplo anterior, a imagem mudava quando o bot ão era pressionado. Agora, a imagem muda sozinha, automaticamente. Isso ocorre gra ças à presen ça de um componente Timer, que recebe uma mensagem em intervalos fixos de tempo. Aqui est á um resumo das propriedades do componente: o b j e c t Wo r l d F o r m: T Wo r l d F o r m Ca p t i o n = ’ Wo r l d ’
On Cr e a t e = F o r mCr e a t e o b j e c t L a b e l 1 : T La b e l . . . o b j e c t Wo r l d Bu t t o n : T Bi t Bt n Ca p t i o n = ’ &S t a r t ’ On Cl i c k = Wo r l d Bu t t o n Cl i c k Gl y p h . Da t a = { W1 . b mp } Spaci ng = 15 e nd o b j e c t T i me r 1 : T Ti me r
E n a b l e d = Fa l s e Int erval = 500 On T i me r = T i me r 1 T i me r e nd e nd
Recursos gráficos do Delphi
27
FIGURA 1 1
Alguns exemplos do programa World em funcionamento.
O componente temporizador é iniciado e parado (ativado e desativado) quando o usu á rio pressiona o bot ão de bitmap que possui a imagem do mundo: p r o c e d u r e T Wo r l d F o r m. Wo r l d Bu t t o n Cl i c k ( S e n d e r : T Ob j e c t ) ; begin i f T i me r 1 . E n a b l e d t h e n begin
T i me r 1 . E n a b l e d : = F a l s e ; Wo r l d Bu t t o n . Ca p t i o n : = ’ &S t a r t ’ ; e nd else begin
T i me r 1 . E n a b l e d : = T r u e ; Wo r l d Bu t t o n . Ca p t i o n : = ’ &S t o p ’ ; e nd; e nd;
Conforme você pode ver na Figura 11, um r ó tulo sobre o bot ão indica qual das imagens est á sendo apresentada. Sempre que a mensagem do temporizador é recebida, a imagem e o r ó tulo mudam: p r o c e d u r e T Wo r l d F o r m. T i me r 1 Ti me r ( S e n d e r : T Ob j e c t ) ; begin Co u n t : = ( C o u nt mo d 1 6 ) + 1 ; L a b e l 1 . Ca p t i o n : = ’ Di s p l a y i n g i m ag e ’ +
I n t T o S t r ( Co u nt ) ; Wo r l d Bu t t o n . G l y p h . L o a d F r o mF i l e ( ’ w’ + I n t T o St r ( Co u n t ) + ’ . b mp ’ ) ; e nd;
Nesse có digo, C o u n t é um campo do formulá rio que é inicializado como 1 no m étodo F o r mCr e a t e . A cada intervalo do temporizador, C o u n t é aumentado como m ó dulo 1 6 e depois convertido para uma string (precedida pela letra w). A raz ão para esse limite é simples — t í n hamos 1 6 bitmaps da Terra para apresentar. Chamar os arquivos de bitmap de W1 . B MP , W2 . BMP etc. facilita para o programa acessá-los, construindo as strings com o nome em tempo de execu ção.
NOTA
A operação de m ódulo retorna o resto da divisão entre inteiros. Isso significa que C o u n t mo d 1 6 retorna invariavelmente um valor no intervalo de 0 a 15. Somando um a esse valor de retorno, obtemos o número do bitmap, que está no intervalo de 1 a 16.
28
Dominando o Delphi 6 — A Biblía
Um a List a de Bit maps, o Uso de Recursos e um Cont r olCanvas O programa World funciona, mas é muito lento por dois motivos. Primeiramente, a cada intervalo do temporizador, ele precisa ler um arquivo do disco e, emb ora um a cache de disco possa tornar isso mais r ápido, certamente essa n ão é a solu ção mais eficiente. Alé m de ler o arquivo do disco, o programa tem de criar e destruir objetos de bitmap do Windows e isso requer algum tempo. O segundo problema depende de como a imagem é atualizada: quando você muda o bitmap do bot ão, o componente é completamente apagado e repintado. Isso causa algum tremido, conforme voc ê pode ver executando o programa. Para resolver o primeiro problema (e para mostrar a você uma estrat é gia diferente para manipu lar bitmaps), criamos uma segunda versão do exemplo, World2. Aqui, inclu í m os um cont êiner T O b j e c t L i s t do Delphi 5, armazenando uma lista de bitmaps, no formulário do programa. O formulário tamb ém tem mais alguns campos: type
T Wo r l d F o r m = c l a s s ( T F o r m ) ... private
Co u n t , YP o s , XP o s : I n t e g e r ; Bi t ma p s L i s t : T Ob j e c t L i s t ; Co n t r o l Ca n v a s : T Co n t r o l Ca n v a s ; e nd;
Todos os bitmaps s ão carregados quando o programa come ça e destru í d os quando ele termina. A cada intervalo do temporizador, o programa mostra um dos bitmaps da lista no bot ã o de bitmap. Usando uma lista, evitamos o carregamento de um arquivo cada vez que precisarmos apresentar um bitmap, m as ainda precisamos ter todos o s arquivos com as imagens no diretó rio que p ossui o arquivo execut ável. Uma solu ção para esse problema é mover os bitmaps dos arquivos independentes para o arquivo de recursos do aplicativo. Isso é mais f ácil fazer do que explicar. Para usar os recursos em lugar dos arquivos de bitmap, precisamos primeiro criar esse arquivo. A melhor estrat é gia é escrever um script de recursos (um arquivo R C), listando os nom es dos arquivos de bitmap e dos recursos correspondentes. Abra um novo arquivo de texto (em qualquer editor) e escreva o seguinte có digo: W1 B I T MA P " W1 . B MP " W2 B I T MA P " W2 . B MP " W3 B I T MA P " W3 . B MP " // .. . etc.
Uma vez que você tenha preparado esse arquivo RC (o chamamos de Wo r l d B mp . R C), é possí vel compilá-lo em um arquivo RES, usando o compilador de recursos inclu í d o e o aplicativo de linha de comando BRCC32, que pode ser encontrado no diret ó rio BIN do Delphi, e depois inclu í - lo no pro jeto atravé s da inser çã o da diretiva { $ R WO R L DB MP . R E S } no c ó digo-fonte do projeto ou em uma das unidades. Entretanto, no Delphi 5, você pode usar uma estrat é gia mais simples. Você pode pegar o arquivo RC e simplesmente inclu í - lo no projeto, usando o comando Add to Project do menu Project ou simplesmente arrastando o arquivo para o projeto. O Delphi 5 ativar á automaticamente o compilador de recursos e depois ligar á o arquivo de recursos ao arquivo execut ável. Essas opera çõ es são controladas por uma diretiva de inclusão de recurso estendida, inclu í d a no c ó digo-fonte do projeto: { $ R ’ WORLDBMP. r e s ’ WORLDBMP. RC ’ }
Recursos gráficos do Delphi
29
Um a vez que t enhamos definido corretamente os recursos do aplicativo, precisamos carregar os bitmaps dos recursos. Para um ob jeto T Bi t ma p , podemos usar o m é todo L o a d F r o mRe s o u r c e Na me , caso o recurso tenha um identificador de string, ou o m étodo L o a d F r o mR e s o u r c e I D , caso ele tenha um identificador num érico. O primeiro par âmetro dos dois m étodos é um handle para o aplicativo, conhecido como HI n s t a n c e , dispon í v el no Delphi como uma variável global.
DICA
Este
O Delphi define uma segunda variável global, Ma i n I n s t a n c e , que se refere a HI n s t a n c e do arquivo executável principal. A não ser que você esteja dentro de uma DLL, pode usar uma ou outra indistintamente.
é o có digo do m étodo F o r mC r e a t e :
p r o c e d u r e T Wo r l d F o r m. F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; var
I : I n t e g er ; B mp : T Bi t ma p ; begin
Co u n t : = 1 ; / / c a r r e ga o s b i t ma ps e o s i n c l u i n a l i s t a Bi t ma p s L i s t : = T L i s t . Cr e a t e ; f or I : = 1 t o 16 do begin
Bmp : = T Bi t ma p . Cr e a t e ; Bmp . L o a d F r o mRe s o u r c e Na me ( HI n s t a n c e , ’ W ’ + I n t T o S t r ( I ) ) ; Bi t ma p s L i s t . Ad d ( B mp ) ; e nd; e nd;
NOTA
Como alternativa, poderíamos ter usado o componente ImageList, mas, para este exemplo, decidimos usar uma estratégia de baixo nível para mostrar a você todos os detalhes envolvidos.
Permanece um prob lema para ser resolvido: o bter um a transiçã o suave de uma imagem do mundo para a seguinte. O programa deve pintar os bitmaps em um canvas usando o m étodo D raw. Infelizmente, o canvas do bot ão de bitmap n ão est á diretamente dispon í v el (e sem evento protegido); portanto, decidimos usar um componente T C o n t r o l Ca n v a s (em geral o canvas interno de um controle, mas um que voc ê tamb ém possa associar externamente). Para usá -lo para pintar sobre um bot ão, podemos atribuir o bot ão ao controle canvas no m étodo F o r m C r e a t e : Co n t r o l Ca n v a s : = T Co n t r o l C a n v a s . Cr e a t e ; Co n t r o l Ca n v a s . Co n t r o l : = Wo r l d Bu t t o n ; YP o s : = ( Wo r l d Bu t t o n . H e i g h t — Bmp . He i g h t ) d i v 2 ; XP o s : = Wo r l d Bu t t o n . Ma r g i n ;
A posição horizontal do bot ão onde a imagem est á localizada (e onde devemos pint ar) depende do componente Ma r g i n do í c one do bot ã o de bitmap e da altura do bitmap. Uma vez que o canvas do controle esteja corretamente definido, o m étodo T i me r 1 T i me r simplesmente pinta sobre ele — e sobre o bot ão: p r o c e d u r e T Wo r l d F o r m. T i me r 1 Ti me r ( S e n d e r : T Ob j e c t ) ; begin
Co u n t : = ( C o u n t mo d 1 6 ) + 1 ; L a b e l 1 . Ca p t i o n : = F o r ma t ( ’ Di s p l a y i n g i m a g e %d’ , [ C o u nt ] ) ;
30
Dominando o Delphi 6 — A Biblía
/ / d e s e n h a o bi t ma p a t u a l n o c an v a s d o c o n t r o l e Co n t r o l Ca n v a s . Dr a w ( XP o s , Y Po s , Bi t ma p s L i s t . I t e ms [ C o u n t - 1 ] a s T Bi t ma p ) ; e nd;
O ú ltimo problema é mudar a posi ção da imagem quando o bot ão esquerdo do mouse for pressionado ou solto sobre ele (isto é, nos eventos O n M o u s e D o w n e On Mo u s e U p do bot ão). Além de mover a imagem por alguns pixels, devemos atualizar a imagem do bitmap, pois o Delphi a apresentar á au tomaticamente enquanto redesenha o bot ão. Caso contr ário, um usu ário veria a imagem inicial at é qu e o intervalo do temporizador tenha decorrido e o componente ativado o evento On T i me r . (Isso poderia levar algum tempo, caso voc ê o tivesse parado!) Aqui est á o có digo do primeiro dos dois m étodos: p r o c e d u r e T Wo r l d F o r m. Wo r l d Bu t t o n Mo u s e D o wn ( S e n d e r : T Ob j e c t ;
Bu t t o n : T Mo u s e Bu t t o n ; S h i f t : T S h i f t S t a t e ; X , Y: I n t e g e r ) ; begin i f Bu t t o n = mb Le f t t h e n begin / / p i n t a a i ma g e m a t u a l s o b r e o b o t ã o
Wo r l d Bu t t o n . G l y p h . As s i g n ( Bi t ma p s Li s t . I t e ms [ Co u n t - 1 ] a s TBi t ma p ) ; I n c ( YP o s , 2 ) ; I n c ( XP o s , 2 ) ; e nd; e nd;
O Controle Animado H á uma maneira melhor de obter anima ção do que apresentar uma série de bitmaps em seq üê ncia. Use o controle comum Animate do Win32. O controle Animate é baseado no uso de arquivos AVI (Audio Video Interleaved), uma série de bitmaps semelhante a um filme.
NOTA
Na verdade, o controle Animate pode apresentar apenas os arquivos AVI que tenham um único fluxo de vídeo, sejam descompactados ou compactados com compactação RLE8 e não t enham mudanças de paleta; e se eles t iverem som, ele será ignorado. Na prática, os arquivos correspondentes a esse requisito são aqueles constituídos de uma série de bitmaps de computador e não aqueles baseados em um filme real.
O controle Animate pode ter duas fontes poss í v eis para sua anima ção: s
s
Ele pode ser baseado em qualquer arquivo AVI que atenda aos requisitos indicados na nota anterior; para usar esse tipo de fonte, configure um valor correto para a propriedade F i l e Na me . Ele pode usar uma animação int erna especial do Windows, part e da biblioteca de controle comum; para usar esse tipo de fonte, escolha um d os valores possí v eis da propriedade C o m m o n A V I (que é baseada em uma enumera ção) .
Se você simplesmente colocar um controle Animate em um formul ário, escolha uma anima ção usando um dos m étodos que acabamos de descrever e, finalmente, configure sua propriedade Ac t i v e para T r u e . Voc ê começar á a ver a animação at é em tempo de projeto. Por defini ção, a animação ocorre continuamente, reiniciando assim que acaba. Entretanto, você pode regular esse efeito usando a propriedade R e p e t i t i o n s . O valor padr ão -1 causa uma repeti ção infinita; use qualquer outro valor para especificar um n ú mero de repetiçõ es.
Recursos gráficos do Delphi
31
Vo cê tamb ém pode especificar o quadro inicial e final da seq üê ncia, com as propriedades S t a r t F r a me e S t o p F r a me . Essas tr ês propriedades (posição inicial, posição final e n ú mero de repetiçõ es) correspondem aos tr ês par âmetros do m étodo P l a y , que você usar á freq ü entemente com um controle Animate. Como alternativa, você pode configurar as propriedades e depois chamar o m étodo S t a r t . Em tempo de execu çã o, você tamb ém pode acessar o n ú mero total de quadros usando a propriedade F r a me C o u n t . Você pode usar isso para executar a anima ção do in í c io ao fim. Finalmente, para obter um controle mais apurado, você pode usar o m étodo S e e k , que apresenta um quadro especí f ico. Usamos todos esses m étodos em um programa demonstrativo simples (AnimCtrl), que pode usar os dois arquivos e as anima çõ es padr ão do Windows. O programa permite que voc ê escolha um arquivo ou um a das anima çõ es usando u m compo nente ListBox. Incluí m os nesse component e ListBox um item para cada elemento da enumera ção T C o m m o n A V I e usamos a mesma ordem: o b j e c t L i s t Bo x 1 : T L i s t Bo x
I t e ms . S t r i n g s = ( ’ [ Us e a n AVI f i l e ] ’ ’ F i n d Fo l d e r ’ ’ Fi n d Fi l e ’ ’ F i n d Co mp u t e r ’ ’ Co p y Fi l e s ’ ’ Co p y Fi l e ’ ’ Re c y c l e Fi l e’ ’ E mp t y Re c y c l e ’ ’ De l e t e F i l e ’ ) On Cl i c k = L i s t Bo x 1 Cl i c k e nd
Gr aças a essa estrutura, quando o usu ário d á um clique no component e ListBox, apenas converter o n ú mero dos itens selecionados para o tipo de dados enumerado fornecer á o valor correto para a propriedade C o m m o n A V I . p r o c e d u r e T Fo r m1 . L i s t Bo x 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
An i ma t e 1 . Co mmo n AVI : = TC o mmo n AVI ( L i s t B o x 1 . I t e mI n d e x ) ; i f ( L i s t Bo x 1 . I t e mI n d e x = 0 ) a n d Op e n Di a l o g 1 . E x e c u t e t h e n An i ma t e 1 . F i l e Na me : = Op e n Di a l o g 1 . F i l e Na me e nd;
Conforme você pode ver, quando o primeiro item é selecionado (o valor é c a N o n e ), o programa carrega automaticamente um arquivo AVI, usando um componente OpenDialog. O componente mais importante do formulário é o controle Animate. Aqui est á sua descrição textual: o b j e c t An i ma t e 1 : T An i ma t e
Au t o S i z e = F a l s e Al i g n = a l C l i e n t C ommo n AVI = a v i F i n d F o l d e r On Op e n = An i ma t e 1 Op e n e nd
Ele é alinhado com a área cliente, de modo que um usu ário pode redimension á-lo facilmente, dependendo do tamanho real dos quadros da animação. Conforme voc ê pode ver, tamb ém definimos um manipulador para um evento desse componente, O n O p e n : p r o c e d u r e T Fo r m1 . An i ma t e 1 Op e n ( S e n d e r : T Ob j e c t ) ; begin
32
Dominando o Delphi 6 — A Biblía
L b l F r a me s . Ca p t i o n : = ’ F r a m e s ’ + I n t T o S t r ( An i ma t e 1 . F r a me C o u n t ) ; e nd;
Quando um novo arquivo (ou anima ção comum) é aberto, o programa simplesmente produz como sa í d a o n ú mero de seus quadros em um r ó tulo. Esse r ó tulo fica junto a vá rios bot õ es e alguns controles SpinEdit em um painel grande, atuando como uma barra de ferramentas. Voc ê pode vê-los no formulário em tempo de projeto da Figura 12.
FIGURA 1 2
O formulário do exemplo AnimCtrl em tempo de p rojeto.
Os bot õ es Start e Stop s ão totalmente triviais, mas o bot ã o Play Once tem algum c ó digo: p r o c e d u r e T Fo r m1 . Bt n On c e Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
An i ma t e 1 . P l a y ( 0 , An i ma t e 1 . F r a me C o u n t , 1 ) ; e nd;
As coisas come çam a ficar mais interessantes com o c ó digo usado para reproduzir a anima ção tr ês vezes ou para reproduzir apenas um trecho dela. Esses dois m étodos t êm por base o m é todo P l a y : p r o c e d u r e T Fo r m1 . Bt n Tr i c e C l i c k ( S e n d e r : begin
TOb j e c t ) ;
An i ma t e 1 . P l a y ( 0 , An i ma t e 1 . F r a me C o u n t , 3 ) ; e nd; p r o c e d u r e T Fo r m1 . Bt n F r a g me n t Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
An i ma t e 1 . P l a y ( S p i n E d i t 1 . Va l u e , S p i n E di t 2 . V a l u e , - 1 ) ; e nd;
Os dois ú ltimos manipuladores de evento de bo t ão são baseados no m étodo S e e k . O bot ão Goto simplesmente vai para o quadro indicado pelo componente SpinEdit correspondente, enquanto os bot õ es Reverse vão para cada quadro por sua vez, partindo do ú ltimo e fazendo uma pausa entre cada um deles: p r o c e d u r e T Fo r m1 . Bt n Go t o Cl i c k ( S e n d e r : T Ob j e c t ) ; begin
An i ma t e 1 . S e e k ( S p i n E d i t 3 . Va l u e ) ; e nd;
Recursos gráficos do Delphi
33
p r o c e d u r e T Fo r m1 . Bt n Re v e r s e Cl i c k ( S e n d e r : T Ob j e c t ) ; var
I n i t : T Da t e T i me ; I : I n t e g er ; begin f o r I : = An i ma t e 1 . F r a me C o un t d o wn t o 1 d o begin
An i ma t e 1 . S e e k ( I ) ; / / e s p e r a 5 0 mi l i s s e g u nd o s I n i t : = No w; w h i l e No w < I n i t + En c o d e Ti me ( 0 , 0 , 0 , 5 0 ) d o Ap p l i c a t i o n . P r o c e s s Me s s a g e s ; e nd; e nd;
O Controle Animate em um Bot ão Agora que você sabe como o controle Animate funciona, podemos us á-lo para construir outro bot ão animado. Basta colocar um controle Animate e um bo tã o grande (p ossivelmente com u ma fonte grande tamb ém) em um formul ário. Em seguida, escreva o seguinte c ó digo para transformar o bot ão na janela progenitora do controle Animate em tempo de execução e posicion á-lo corretamente: p r o c e d u r e T Fo r m1 . F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; var
Di f f : I n t e g e r ; begin
An i ma t e 1 . P a r e n t : = Bu t t o n 1 ; Di f f : = B u t t o n 1 . H e i g h t - An i ma t e 1 . He i g h t ; An i ma t e 1 . S e t B o u n d s ( h Di f f d i v 2 , h Di f f d i v 2 , An i ma t e 1 . Wi d t h , An i ma t e 1 . He i g h t ) ; An i ma t e 1 . Ac t i v e : = T r u e ; e nd;
Vo cê pode ver um exemplo desse efeito na Figura 13 (o projeto tem o nome AnimBtn). Essa é mesmo a estratégia mais simples para prod uzir um bot ão animado, mas ela tamb ém permite o m í n imo de controle. FIGURA 1 3
O efeito do controle Animate dentro de um bot ão, como ilustrado pelo programa AnimBtn.
Grades Gr áficas As grades representam outro grupo interessante de componentes gr áficos do Delphi. O sistema oferece diferentes componentes gr áficos: uma grade de strings, uma de imagens, grades relacionadas a
34
Dominando o Delphi 6 — A Biblía
banco de dados e uma grade de amostra de cores. Os dois primeiros tipos de grades s ão particularmente ú teis, pois eles permitem que voc ê represente muitas informa çõ es e tamb ém que o usu ário navegue nelas. É claro que as grades são extremamente importantes na programação de banco de dados, e elas podem ser personalizadas com figuras, conforme vimos no Cap í t ulo 13 do Dominando o Delphi 6 . Os componentes DrawGrid e StringGrid s ão intimamente relacionados. Na verdade, a classe T S t r i n g Gr i d é uma subclasse de T Dr a wGr i d . Para que usar essas grades? Basicamente, voc ê pode armazenar alguns valores, ou nas strings relacionadas ao compon ente StringGrid ou em outras estruturas de dados, e depois apresentar valores selecionados, usando crit érios especí ficos. Embora as grades de strings possam ser usadas quase como est ão (pois elas já fornecem recursos de edi çã o), as grades de ob jetos genéricos normalmente exigem mais codificação. Na verdade, as grades definem o modo como as informa çõ es s ã o organizadas para exibiçã o e n ão da maneira como s ão armazenadas. A ú nica grade que armazena os dados qu e apresenta é StringGrid. Todas as outras grades (incluindo os componentes DrawGrid e DBGrid) são apenas visualizadores de dados, e n ão cont êineres de dados. O componente DBGrid n ão possui os dados que apresenta; ele busca os dados a partir da fonte de dados conectada. Às vezes isso causa confus ão. A estrutura b ásica de uma grade inclui várias colunas e linhas fixas, que indicam a regi ão n ão rolante da grade (conforme você pode ver na Figura 14). As grades est ão entre os componentes mais complexos dispon í v eis no Delphi, conforme indicado pelo alto n ú mero de propriedades e m étodos que elas cont êm. Existem muitas op çõ es e propriedades excelentes para as grades, controlando sua apar ência e seu comportamento.
FIGURA 1 4
Quando você coloca um novo componente grade em um formulário, ele cont ém um a linha fixa e uma coluna fixa como padr ão.
Em sua apar ência, a grade pode ter linhas de diferentes tamanhos ou pode n ão ter linhas. Você pode configurar o tamanho de cada coluna ou linha independentemente das outras, pois as propriedades Ro wS i z e , C o l Wi d t h e R o wHe i g h t são arrays. Para o comportamento da grade, você pode deixar que o usu ário redimensione as colunas e as linhas ( g o C o l S i z i n g e g o R o wS i z i n g ), arraste colunas e linhas inteiras para uma nova posi ção ( g o R o wMo v i n g e g o C o l u mn Mo v i n g ), selecione a edição autom ática e permita seleçõ es de intervalos. Como várias op çõ es permitem que o s usuários executem diversas opera çõ es nas grades, tamb é m existem vá rios elementos relacionados às grades, como On C o l u mn Mo v e , On Dr a wCe l l ou On S e t E d i t T e x t . O evento mais importante provavelmente é On Dr a w Ce l l . Em resposta a esse evento, um programa deve pintar determinada cé lula da grade. Apenas as grades de strings podem apresentar automaticamente seu conteú do. Na verdade, o componente DrawGrid n ão tem suporte ao armazenamento de dados. Ele é simplesmente uma ferramenta p ara organizar uma p arte da t ela, para apresentar informaçõ es em um formato regular. Trata-se de uma ferramenta simples, mas tamb ém poderosa. M étodos como Ce l l Re c t , que retorna o ret ângulo correspondente à á rea de uma cé lula, ou Mo u s e T o Ce l l , que retorna a célula em uma posição especí fica, sã o uma satisfa ção de usar. Manipulando linhas e colunas
Recursos gráficos do Delphi
35
redimensionáveis e grades rolantes, eles simplificam tarefas complexas e liberam o programador de c álculos ma çantes. Para que você pode usar uma grade? Construir uma planilha eletr ô nica provavelmente é a primeira id é ia que vem à mente, mas isso talvez seja um pouco complexo d emais para um exemplo. Decidimos usar o controle StringGrid em um programa que mostra as fontes instaladas no sistema, e o controle DrawGrid em um programa que simula o jogo MineSweeper (Campo minado).
Uma Grade de Fontes Se você colocar um componente StringGrid em um formul ário e configurar suas op çõ es corretamente, ter á um editor funcional completo de strings organizadas em uma grade, sem realizar qualquer programação. P ara tornar o exemplo m ais interessante, d ecidimos desenhar cada célula da grade com uma fonte diferente, variando seu tamanho e seu tipo. Voc ê pode ver o resultado do programa FontGrid na Figura 15. FIGURA 1 5
Um exemplo da saí da do aplicativo FontGrid. A largura de cada coluna pode ser alterada em tempo de execu ção.
O formul ário desse programa é muito simples. Você precisa apenas colocar um component e grade em um formulá rio, alinh á-lo com a área cliente, configurar algumas propriedades e opçõ es, e deixar o programa fazer o resto. Na verdade, o n ú mero de colunas e linhas e seu tamanho são calculados em tempo de execu ção. As propriedades importantes que você precisa configurar são De f a u l t Dr a wi n g , que deve ser F a l s e para nos permitir pintar a grade como quisermos, e Op t i o n s : o b j e c t F o r m1 : T F o r m1 Ca p t i o n = ’ F on t Gr i d ’
On Cr e a t e = F o r mCr e a t e o b j e c t S t r i n g Gr i d 1 : T S t r i n g Gr i d Al i g n = a l Cl i e n t De f a u l t Co l Wi d t h = 2 0 0 De f a u l t Dr a wi n g = Fa l s e Op t i o n s = [ g o F i x e d Ve r t Li n e , g o F i x e d Ho r z L i n e , g o Ve r t L i n e , g o Ho r z L i n e , g o Dr a w Fo c u s S e l e c t e d , g o Co l S i z i n g , g o Co l Mo v i n g , g o E d i t i n g ] On Dr a wCe l l = S t r i n g Gr i d 1 Dr a wCe l l e nd e nd
Como normalmente acontece no Delphi, quanto mais simples for o formulário, mais complexo ser á o có digo. Este exemplo segue essa regra, embora ele tenha apenas dois m étodos, um para inicia-
36
Dominando o Delphi 6 — A Biblía
lizar a grade na inicialização e outro para desenhar os itens. A edi ção, na verdade, n ão foi personalizada e ocorre usando a fonte de sistema. O primeiro dos dois m étodos é F o r mC r e a t e . No in í cio, esse m étodo usa o objeto global S c r e e n para acessar as fontes instaladas no sistema. A grade tem uma coluna para cada fonte, assim como uma coluna fixa com n ú meros representando tamanhos de fonte. O nome de cada coluna é copiado do objeto S c r e e n para a primeira célula de cada coluna (que possui um í n dice zero): p r o c e d u r e T Fo r m1 . F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; var
I , J : I n t e g er ; begin { o n ú me r o d e c o l u n a s é i g ua l a o n ú me r o d e f o n t e s ma i s 1 p ar a a p r i me i r a c o l u n a f i x a , q u e t e m u m t a ma n ho i g u al a 2 0 }
S t r i n g Gr i d 1 . C o l C o u n t : = S c r e e n . F o n t s . Co u n t + 1 ; S t r i n g Gr i d 1 . C o l Wi d t h s [ 0 ] : = 5 0 ; f o r I : = 1 t o S c r e e n . F o n t s . Co u n t d o begin / / e s c r e v e o n o me d a f o n t e n a p r i me i r a l i n ha
S t r i n g Gr i d 1 . Ce l l s [ I , 0 ] : = S cr e e n . F on t s . S t r i n gs [ I - 1 ] ; { c a l c u l a o t a ma n h o má x i mo e x i g i d o p ar a a c o l u n a , o b t e n d o a l a r g u r a d o t e x t o c o m o ma i o r t a ma n h o d a f o n t e n e s s a c o l u n a} S t r i n g Gr i d 1 . C a n v a s . F o n t . Na me : = S t r i n g Gr i d 1 . Ce l l s [ I , 0 ] ; S t r i n g Gr i d 1 . C a n v a s . F o n t . S i z e : = 3 2 ; S t r i n g Gr i d 1 . C o l Wi d t h s [ I ] : = S t r i n g Gr i d 1 . C a n v a s . T e x t Wi d t h ( ’ A a B b Y y Z z ’ ) ; e nd; ...
N a ú ltima parte do có digo anterior, o programa calcula a largura de cada coluna. Isso é feito através da avaliação do espa ço o cupado pela string de t exto personalizada AaBbY yZz , usando-se a fonte da coluna (escrita na primeira célula, Ce l l s [ I , 0 ] ) e o maior tamanho de fonte usado pelo programa ( 3 2 ). Para calcular o espaço exigido pelo texto, você pode aplicar os m étodos T e x t Wi d t h e T e x t He i g h t em um canvas, com a fonte correta selecionada. As linhas, em vez disso, t êm sempre uma largura 2 6 e uma altura que aumenta, calculada com a f ó rmula apro ximada: 1 5 + I x 2 . Na verdade, calcular o texto mais alto significa verificar a altura do texto em cada coluna, certamente uma operação complexa demais para este exemplo. A f ó rmula aproximada funciona suficientemente bem, conforme você pode ver executando o programa. Na primeira célula de cada linha, o programa escreve o tamanho da fonte, que corresponde ao n ú mero da linha mais sete. A ú ltima operação é armazenar a string “ AaBbY yZ z ” em cada célula n ão fixa da grade. Para fazer isso, o programa usa um la ço f o r aninhado. Espere usar laços for aninhados freq ü entemente quando trabalhar com grades. Aqui est á a segunda parte do m étodo F o r mC r e a t e : / / d e f i n e o nú me r o d e c o l u n a s S t r i n g Gr i d 1 . R o wCo u n t : = 2 6 ; f or I : = 1 t o 25 do begin //
e s c r e v e o n ú me r o n a pr i me i r a c o l u n a S t r i n g Gr i d 1 . Ce l l s [ 0 , I ] : = I n t T o S t r ( I + 7 ) ; / / c o n f i g u r a uma a l t u r a q ue a u me n t a p ar a a s l i n h as
Recursos gráficos do Delphi
37
S t r i n g Gr i d 1 . R o wHe i g h t s [ I ] : = 1 5 + I * 2 ; / / i n s e r e t e x t o p a dr ã o e m c a d a c o l u n a f o r J : = 1 t o S t r i n g Gr i d 1 . C o l C o u n t d o S t r i n g Gr i d 1. C e l l s [ J , I ] : = ’ A a B b Y y Z z ’ e nd; S t r i n g Gr i d 1 . R o wHe i g h t s [ 0 ] : = 2 5 ; e nd;
Agora, podemos estudar o segundo m étodo, S t r i n g Gr i d 1 Dr a wCe l l , que corresponde ao evento On Dr a w Ce l l da grade. Esse m étodo tem vários par âmetros: s
C o l e R o w se referem à célula que estamos pintando atualmente.
s
R e c t é a área da célula que vamos pintar.
s
S t a t e é o estado da célula, um conjunto de tr ês flags que podem estar ativos ao mesmo tempo : g d S e l e c t e d (a célula est á selecionada), g d F o c u s e d (a célula tem o foco de entrada) e g d F i x e d (a célula est á na área fixa, que normalmente tem uma cor de fundo diferente). É im portante conhecer o estado da célula, pois isso normalmente afeta sua sa í da .
O m é todo Dr a w Ce l l pinta o texto do elemento d a grade correspond ente, com a fonte usada pela coluna e com o tamanho usado para a linha. Aqui est á a listagem desse m étodo: p r o c e d u r e T Fo r m1 . S t r i n g Gr i d 1 Dr a w Ce l l ( S e n d e r : T Ob j e c t ;
Co l , Ro w: I n t e g e r ; Re c t : TRe c t ; S t a t e : T Gr i d Dr a wS t a t e ) ; begin / / s e l e c i o n a u ma f o n t e , d e p e nd e n do da c o l u na i f ( C o l = 0 ) o r ( R o w = 0 ) t h e n
S t r i n g Gr i d 1 . C a n v a s . F o n t . Na me : = I else
S t r i n g Gr i d 1 . C a n v a s . F o n t . Na me : = S t r i n g Gr i d 1 . C e l l s [ C o l , 0 ] ; / / s e l e c i o n a o t a ma n ho d a f o n t e , i f R o w = 0 t h e n
d e p e nd e n do d a l i n ha
S t r i n g Gr i d 1 . Ca n v a s . F o n t . S i z e : = 1 4 else
S t r i n g Gr i d 1 . C a n v a s . F o n t . S i z e : = Ro w + 7; / / s e l e c i o na a c or d e f u n d o i f g d S e l e c t e d i n S t a t e t h e n
S t r i n g Gr i d 1 . Ca n v a s . B r u s h . C o l o r : = c l Hi g h l i g h t e l s e i f g d Fi x e d i n S t a t e
then
S t r i n g Gr i d 1 . Ca n v a s . B r u s h . C o l o r : = c l B t n F a c e else
S t r i n g Gr i d 1 . C a n v a s . Br u s h . Co l o r : = c l Wi n d o w; / / exi be o t exto S t r i n g Gr i d 1 . C a n v a s . T e x t R e c t ( Re c t , Re c t . L e f t , R e c t . To p , S t r i n g Gr i d 1 . Ce l l s [ C o l , Ro w] ) ; / / desenha o f oco i f g d F o c u s e d i n S t a t e
then
S t r i n g Gr i d 1 . C a n v a s . Dr a wF o c u s Re c t ( Re c t ) ; e nd;
38
Dominando o Delphi 6 — A Biblía
O nome da fonte é recuperado pela linha 0 da mesma coluna. O tamanho da fonte é calculado somando-se 7 ao n ú mero da linha. As colunas fixas usam alguns valores padr ão. Tendo configurado a fonte e seu tamanho, o programa seleciona uma cor para o fundo da c élula, dependendo de seus estados possí v eis: selecionada, fixa ou normal (isto é, nenhum estilo especial). O valor do flag g d F o c u s e d do estilo é usado algumas linhas depois para desenhar o ret â ngulo de foco t í p ico. Quando t udo estiver configurado, o programa poder á produzir alguma saí d a em si, desenhando o texto e, se necessário, o ret â ngulo de foco, com as duas ú ltimas instru çõ es do m étodo S t r i n g Gr i d 1 Dr a wCe l l anterior.
DICA
Para desenhar o texto na célula da grade, usamos o método T e x t R e c t do canvas, em vez do método T e x t Ou t , mais comum. O motivo é que T e x t R e c t recorta a saída no ret ângulo dado, evitando o desenho fora dessa área. Isso é particularmente importante no caso de grades, pois a saída de uma célula não deve ultrapassar suas bordas. Como estamos pintando no canvas da grade inteira, quando estivermos desenhando uma célula, podemos acabar danificando também o conteú do de células vizinhas.
Como uma observa ção final, lembre-se de que, quando voc ê decidir desenhar o conteú do da cé lula de uma grade, n ão deve desenhar apenas a imagem padr ão, mas tamb ém fornecer uma sa í d a diferente para o item selecionado, desenhar o foco corretamente etc.
Minas em uma Grade O componente StringGrid usa o array C e l l s para armazenar os valores dos elementos e tamb ém possui uma propriedade Ob j e c t s para armazenar dados personalizados de cada célula. O componente DrawGrid, por sua vez n ão tem um armazenamento predefinido. Por esse motivo, o exemplo a seguir define um array bidimensional para armazenar o valor das c élulas da grade — isto é, do campo de reprodu ção. O exemplo Mines é um clone do jogo Campo minado inclu í d o com o Windows. Se voc ê nunca jogou esse jogo, sugerimos que tente fazer isso e leia suas regras no arquivo de ajuda, pois forneceremos apenas uma descrição b ásica. Quando o programa come ça, ele apresenta um campo vazio (um a grade) em qu e existem algumas minas escondidas. Dando um clique com o bot ão esquerdo do mouse em uma c élula, você testa se h á ou n ão uma mina nessa posi ção. Se voc ê encontrar uma mina, ela explodirá, e o jogo termina. Voc ê perde. Se n ão houver mina na cé lula, o programa indicar á o n ú mero de minas nas oito c élulas vizinhas a ela. Conhecendo o n ú mero de minas pr ó ximas à célula, você tem um bom palpite para a pr ó xima jogada. Para ajudá-lo ainda mais, quando uma célula n ã o possui minas na área vizinha, o n ú mero de minas dessas células é apresentado automaticamente e, se uma delas n ão tiver minas nas vizinhan ças, o processo ser á repetido. Assim, se voc ê tiver sorte, com um ú nico clique de m ouse, poder á descobrir um bom n ú mero de c élulas vazias (veja a Figura 16). Quando você achar que encontrou uma mina, basta dar um clique com o bot ã o direito do mouse na célula; isso colocar á uma bandeira lá. O programa nã o diz se sua infer ência est á correta; a bandeira é apenas um ind í cio para suas tentativas futuras. Se posteriormente você mudar de id éia, pode dar novamente um clique com o bot ão direito do mouse na cé lula, para remover a bandeira. Quando tiver encontrado todas as minas, você ganhar á, e o jogo termina. Essas são as regras do jogo. Agora, precisamos implement á-las usando um componente DrawGrid como ponto de partida. Nesse exemplo, a grade é fixa e n ão pode ser redimensionada ou modificada de qualquer maneira, durante a execu ção. Na verdade, ela possui células quadradas de 30x30 pixels, que ser ão usadas para apresentar bitmaps do mesmo tamanho.
Recursos gráficos do Delphi
39
FIGURA 1 6
O p rograma Mines apó s um ú nico clique de sorte. Um grupo de c élulas sem m inas é apresentado simultaneamente.
O có digo desse programa é complexo e n ão é f ácil encontrar um ponto de partida para descrevê-lo. Por isso, inclu í m os mais coment ários que o normal no c ó digo-fonte que acompanha o capí t ulo, para que você possa percorr ê-lo a fim de entender o que ele faz. Contudo, descreveremos seus elementos mais importantes. Primeiro, os dados do programa s ã o armazenados em dois arrays (declarados como campos p r i v a t e do formulário): D i s p l a y : a r r a y [ 0 . . NI t e ms - 1 , 0 . . NI t e ms - 1 ] o f Bo o l e a n ; M a p : a r r a y [ 0 . . NI t e ms - 1 , 0 . . NI t e ms - 1 ] o f C h a r ;
O primeiro é um array de valores boleanos que indicam se um item deve ser apresentado ou permanecer oculto. Note que o n ú mero de linhas e colunas desse array é NI t e ms . Você pode mudar livremente essa constante, mas deve redimensionar a grade de acordo. O segundo array, Ma p , cont é m as posiçõ es das minas e bandeiras, e os n ú meros das minas vizinhas. Ele usa có digos de caractere, em vez de um tipo de dados de enumera ção correto, para utilizar os d í g itos de 0 a 8 para indicar o n ú mero de minas em torno da c élula. Aqui est á uma lista dos có digos: s s
s
s
M: Mina oculta indica a posição de uma mina que o usu ário ainda n ão encontrou. K: M in a conh ecida indica a posição de uma mina j á encontrada pelo usu ário e que tem uma bandeira. W : M ina incorreta indica uma posiçã o onde o usu ário colocou uma bandeira, mas onde n ão existe mina. 0 a 8: N ú mero de minas indica o n ú mero de minas nas c élulas vizinhas.
O primeiro m é todo a explorar é F o r mCr e a t e , executado na inicialização. Esse m étodo inicializa vários campos da classe de formulário, preenche os dois arrays com valores padr ão (usando dois la ços f o r aninhados) e depois estabelece as minas na grade. Para o n ú mero de vezes definido em uma constante (isto é, o n ú mero de minas), o programa insere uma mina em uma posi ção aleat ó ria. Entretanto, se j á havia uma mina, o la ço deve ser executado mais uma vez, pois o n ú mero final de minas no array Ma p deve ser igual ao n ú mero solicitado. Caso contr ário, o programa nunca terminar á, pois ele testa quando o n ú mero de minas encontradas é igual ao n ú mero de minas inclu í d as na grade. Aqui est á o có digo do laço; ele pode ser executado mais do que NMi n e s vezes, graças ao uso da vari ável inteira Mi n e s T o Pl a c e , que é aumentada quando tentamos colocar uma mina sobre uma já existente:
40
Dominando o Delphi 6 — A Biblía
R a n d o mi z e ; / / c o l o c a ’ N Mi n e s ’ mi n a s q u e n ã o s e s o b r e p õ e m Mi n e s T o P l a c e : = NMi n e s ; w h i l e Mi n e s T o P l a c e > 0 d o begin
X : = Ra n d o m ( NI t e ms ) ; Y : = Ra n d o m ( NI t e ms ) ; / / s e n ã o h o u v e r u ma mi n a i f Ma p [ X, Y] < > ’ M ’ t h e n begin / / i n s e r e u ma mi n a Ma p [ X, Y] : = ’ M ’ ;
De c ( Mi n e s To P l a c e ) e nd; e nd;
O ú ltimo trecho do có digo de inicialização calcula o n ú mero de minas vizinhas de cada c élula que n ã o tem mina. Isso é feito chamando-se o procedimento Co mp u t e Mi n e s para cada célula. O có digo dessa fun ção é bastante complexo, pois ele precisa considerar os casos especiais das minas junto a uma ou d uas bordas da grade. O efeito dessa chamada é armazenar no array Ma p o caractere que repr esenta o n ú mero de minas vizinhas a cada c élula. O pr ó ximo procedimento ló gico é Dr a w Gr i d 1 Mo u s e D o wn . Esse m étodo primeiramente calcula a célula em que foi dado o clique de mouse, com uma chamada ao m étodo Mo u s e T o C e l l da grade. Em seguida, existem tr ês trechos alternativos de có digo: um pequeno para quando o jogo tiver terminado e os outros dois para os bot õ es do mouse. Quando o bot ã o esquerdo do mouse é pressionado, o programa verifica se existe uma mina (oculta ou n ão) e, se houver, ele apresenta uma mensagem e term ina com uma explosão (veja a Figura 17). FIGURA 1 7
Ai! Você pisou em uma mina!
Se n ão houver mina, o programa configura o valor Di s p l a y da célula como T r u e e, se houver um 0 , ele iniciar á o procedimento F l o o d Z e r o s . Esse m étodo apresenta os oito itens pr ó ximos a uma célula visí v el que tenha o valor 0, repetindo a opera ção seguidamente, caso uma das cé lulas vizinhas tamb ém tenha o valor 0. Essa chamada recursiva é complexa, pois você tem de fornecer um modo de termin á-la. Se houver duas c é lulas pr ó ximas, ambas tendo o valor 0, cada uma estar á na área vizinha à outra; portanto, elas poderiam continuar para sempre a pedir para que a outra c élula exiba a si pr ó pria e às suas células vizinhas. Novamente, o có digo é complexo, e a melhor maneira de estud á-lo pode ser percorr ê-lo no depurador.
Recursos gráficos do Delphi
41
Quando o usu ário pressiona o bot ão direito do mouse, o programa muda o status da c élula. A ação do bot ão direito do mouse é alternar a bandeira na tela, de modo que o usu ário sempre possa remover uma bandeira existente, caso ele ache que a decisão anterior esteja errada. Por isso, o status de uma célula que cont ém uma mina pode mudar de M (mina oculta) para K (mina conhecida) e viceversa. Quando todas as minas tiverem sido encontradas, o programa terminar á com uma mensagem de felicitaçõ es. Um trecho muito importante do có digo est á no final do m étodo de resposta ao evento O n M o u s e D o w n . Sempre que o usu ário d á um clique em uma célula e seu conteú do muda, essa c élula deve ser repintada. Se você repintar a grade inteira, o programa ficar á mais lento. Por isso, usamos a fun ção de API do Windows I n v a l i d a t e R e c t : My Re c t : = Dr a wGr i d 1 . Ce l l Re c t ( Co l , R o w) ; I n v a l i d a t e Re c t ( D r a wGr i d 1 . H a n d l e , @My Re c t , F a l s e ) ;
O ú ltimo m étodo importante é Dr a wGr i d 1 Dr a wCe l l . Já usamos esse procedimento de pint ura no ú ltimo exemplo, de modo que você deve se lembrar de que ele é chamado para cada célula que precisa de repintura. Fundamentalmente, esse m étodo extrai o có digo correspondente à célula, o que mostra um bitmap correspondente, carregado a partir de um arquivo. Mais uma vez, preparamos um bitmap para cada uma das imagens em um novo arquivo de recursos, que é incluí d o no projeto graças ao Pro ject Manager melhorado do Delphi 5. Lembre-se de que, ao usar recursos, o c ó digo tende a ser mais r ápido do que o uso de arquivos separados e, novamente, acabamos com um ú nico arquivo execut ável para instalar. Os bitmaps t ê m nomes correspondentes ao có digo da grade, com um caractere ( ‘ M ’) na frente, pois o nome ‘0 ’ seria inv álido. Os bitmaps podem ser carregados e desenhados na c élula com o seguinte c ó digo: Bmp . L o a d Fr o mRe s o u r c e Na me ( HI n s t a n c e , ’ M ’ + C o d e ) ; Dr a w Gr i d 1 . C a n v a s . Dr a w ( R e c t . L e f t , Re c t . T o p , Bmp ) ;
É claro que isso ocorrer á apenas se a célula estiver visí vel — isto é, se D i s p l a y for T r u e . Caso contrário, um bitmap indefinido padr ão ser á apresentado. (O nome do bitmap é ‘UNDEF ’.) Carregar os bitmaps a partir dos recursos toda vez parece lento, de modo que o programa poderia ter armazenado t odos os bitmaps em uma lista na mem ó ria, como fez o exemplo Wo rld2, já visto neste cap í t ulo. Entretanto, desta vez, decidimos usar uma estrat égia diferente, embora ligeiramente menos eficiente: uma cache. Isso faz sentido, pois j á usamos recursos em lugar de arquivos para acelerar as coisas. A cache de bitmap do programa Mines é pequena, pois ela tem apenas um elemento, mas sua presen ça acelera o programa consideravelmente. O programa armazena o ú ltimo bitmap que usou e seu có digo; ent ão, sempre que ele precisar desenhar um novo item, se o c ó digo for o mesmo, usar á o bitmap da cache. Aqui est á a nova versã o do có digo anterior: i f n o t ( Co d e = L a s t Bmp ) t h e n begin
Bmp . L o a d F r o mRe s o u r c e N a me ( H I n s t a n c e , ’ M ’ + C o d e ) ; L a s t B mp : = Co d e ; e nd; Dr a w Gr i d 1 . C a n v a s . Dr a w ( R e c t . L e f t , Re c t . T o p , Bmp ) ;
Aumentar o tamanho dessa cache certamente melhorar á a velocidade do programa. Você pode considerar uma lista de bitmaps como uma cache grande, mas isso provavelmente é in ú til, pois alguns bitmaps (aqueles com n ú meros altos) são raramente usados. Conforme voc ê pode ver, algumas melhorias podem ser feitas para acelerar o programa, e muito tamb é m pode ser feito para melhorar sua interface com o usu ário. Se você tiver entendido esta versão do programa, achamos que poder á aprimo r á-lo consideravelmente.
42
Dominando o Delphi 6 — A Biblía
Usando TeeChart O TeeChart é um componente baseado na VCL, criado por David Berneda e licenciado para a Borland, para a inclusão nas versõ es Developer e Enterprise do Delphi. O componente TeeChart é muito complexo: o D elphi inclui um arquivo de ajuda e o utros m ateriais de referência para esse compon ente; portanto, n ão perderemos tempo listando todos os seus recursos. Construiremos apenas dois exemplos. O TeeChart aparece em tr ês versõ es: o componente independente (na p ágina Additional da paleta de componentes), a vers ão consciente de dados (na p ágina Data Controls) e a versão relat ó rio (na p á gina Q Report). O Delphi Enterprise tamb ém inclui um controle DecisionChart na p ágina Decision Cube da paleta. A versão consciente de dado s do TeeChart foi apresentada no Capí t ulo 13 deste livro, e a usaremos novamente em um exemplo orientado à Web.
NOTA
É claro que seria mais simples construir um exemplo usando o TeeChart Wizard, mas ver todos os passos dará a você um melhor entendimento da estrutura desse componente.
O componente TeeChart fornece a estrutura b ásica para a produ ção de gr áficos, através de uma base complexa de gr áficos e uma s érie de classes e do cont êiner visual para gr áficos (o controle em si). Os gr áficos são objetos da classe T C h a r t S e r i e s ou de classes derivadas. Uma vez que voc ê tenha colocado o componente TeeChart em um formulário, deve criar uma ou mais s éries. Para fazer isso, voc ê pode abrir o Chart Component Editor: selecione o componente, d ê um clique com o bot ã o direito do mouse para apresentar o menu local do projetista de formul ário e escolha o comando Edit Chart. Agora, pressione o bot ão Add (na p ágina Séries da guia Chat) e escolha o gr áfico (ou série) que você deseja incluir dentre os muitos dispon í v eis (conforme você pode ver na Figura 18). FIGURA 1 8
A TeeChart Gallery permite que você escolha o tipo de gr áfico ou uma série.
Assim que você cria uma nova sé rie, um no vo objeto d e uma subclasse T C h a r t S e r i e s é incluí do em seu formul ário. Esse é o mesmo comportamento do componente MainMenu, que inclui objetos da classe T Me n u I t e m no formulário. Você pode ent ão editar as propriedades do objeto T S e r i e s no Chart Component Editor, ou pode selecionar o objeto T C h a r t S e r i e s no Object Inspector (com a caixa de combinação Object Selector) e editar suas muitas propriedades. As diferentes subclasses T C h a r t S e r i e s — isto é, os diferentes tipos de gr áfico — t êm diferentes propriedades e m étodos (embora alguns deles sejam comuns a mais de uma subclasse). Lembre-se de que um gr áfico pode ter várias séries: se elas forem todas do mesmo tipo, provavelmente v ão se in-
Recursos gráficos do Delphi
43
tegrar melhor, como no caso de v árias barras. De qualquer forma, voc ê tamb ém pode ter um layout complexo com gr áficos de diferentes tipos visí v eis simultaneamente. Às vezes, essa é uma op ção extremamente poderosa.
Construindo um Primeiro Exemplo Para construir este exemplo, colocamos um componente TeeChart em um formulário e depois simplesmente inclu í m os quatro séries 3D Bar — isto é, qu atro o bjetos da classe T B a r S e r i e s . Em seguida, configuramos algumas propriedades, como o t í t ulo do gr á fico etc. Aqui est á um resumo dessas informa çõ es, extraí d as da descrição textual do formulário: o b j e c t Ch a r t 1 : T Ch a r t
An i ma t e d Z o o m = Tr u e Ti t l e . Te x t . S t r i n gs = ( ’ S i mp l e T e e Ch a r t De mo f o r Ma s t e r i n g De l p h i ’ ) Be v e l Ou t e r = b v Lo we r e d o b j e c t S e r i e s 1 : T Ba r S e r i e s S e r i e s Co l o r = c l R e d Ma r k s . Vi s i b l e = F a l s e e nd o b j e c t S e r i e s 2 : T Ba r S e r i e s
S e r i e s Co l o r = c l Gr e e n Ma r k s . Vi s i b l e = F a l s e e nd o b j e c t S e r i e s 3 : T Ba r S e r i e s
S e r i e s Co l o r = c l Y e l l o w Ma r k s . Vi s i b l e = F a l s e e nd o b j e c t S e r i e s 4 : T Ba r S e r i e s
S e r i e s Co l o r = c l B l u e Ma r k s . Vi s i b l e = F a l s e e nd e nd
Em seguida, inclu í m os no formul ário uma grade de string e um bot ão de seleção chamado Update. Esse bot ão é usado para copiar os valores num éricos da grade de string no gr áfico. A grade é baseada em uma matriz de 5x4, assim como uma linha e uma coluna para os t í t ulos. Aqui est á sua descrição textual: o b j e c t S t r i n g Gr i d 1 : TS t r i n g Gr i d
Co l Co u n t = 6 De f a u l t Co l Wi d t h = 5 0 Op t i o n s = [ g o Fi x e d Ve r t L i n e , g o F i x e d Ho r z L i n e , g o Ve r t L i n e , g o Ho r z L i n e , g o Ed i t i n g ] S c r o l l Ba r s = s s No n e On Ge t E d i t Ma s k = S t r i n g Gr i d 1 Ge t E d i t Ma s k e nd
O valor 5 para a propriedade R o w C o u n t é o padr ão, e ele n ão aparece na descrição textual. (O mesmo vale para o valor 1 para as propriedades F i x e d C o l s e F i x e d R o ws .) Um elemento importante dessa grade de strings é a m áscara de edição usada por todas as suas c élulas. Isso é ajustado usando-se o evento On Ge t E d i t Ma s k : p r o c e d u r e T Fo r m1 . S t r i n g Gr i d 1 Ge t E di t Ma s k ( S e n d e r : T Ob j e c t ; AC ol , AR o w: L o n g i n t ; v a r Va l u e : s t r i n g ) ;
44
Dominando o Delphi 6 — A Biblía
begin / / má s c a r a d e e d i ç ã o p a r a a s c é l u l a s d a g r a d e Va l u e : = ’ 0 9 ; 0 ’ ; e nd;
Na verdade, existe m ais um compo nente important e, um a caixa de verificação u sada para alternar a visibilidade dos marcadores da série. (Os marcadores são pequenos r ó tulos amarelos que descrevem cada valor; você precisará executar o programa para vê-los.) Voc ê pode ver o formulário em tempo de projeto na Figura 19 . Neste caso, as séries são populadas por valores aleat ó rios; esse é um excelente recurso do componente, pois ele permite que você veja uma pr évia da saí d a sem introduzir dados em si. FIGURA 1 9
O exemplo Graph1, baseado no componente T eeChart, em tempo de projeto.
Incluindo Dados no Gr áfico Agora, basta inicializarmos os dados da grade de string e copi á-los nas séries do gr á fico. Isso ocorre no manipulador do evento On Cr e a t e do formulário. Esse m étodo preenche os itens fixos da grade e os nomes das s éries, em seguida, preenche a parte relativa aos dados da grade de strings e, por fim chama o manipulador do evento On Cl i c k do bot ão Update, para atualizar o gr áfico: p r o c e d u r e T Fo r m1 . F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; var
I , J : I n t e g er ; begin w it h S tring G rid1 do begin { p r e e n c he a c o l u n a e a f i l e i r a f i x a s , e o s n o m e s d a s é r i e s d o gr á f i c o } f or I : = 1 t o 5 do Ce l l s [ I , 0 ] : = Fo r ma t ( ’ Gr o u p %d ’ , [ I ] ) ; f or J := 1 t o 4 do begin Ce l l s [ 0 , J ] : = F o r ma t ( ’ S e r i e s %d ’ , [ J ] ) ; Ch a r t 1 . S e r i e s [ J - 1 ] . T i t l e : = Fo r ma t ( ’ S e r i e s %d ’ , [ J ] ) ; end;
Recursos gráficos do Delphi
/ / preenche R a n d o mi z e ; f or I : = 1 f or J : = Ce l l s / / com e nd;
45
a g r a d e c om v a l o r e s a l e a t ó r i o s t o 5 do 1 t o 4 do [ I , J ] : = I n t T oS t r ( Ra n d o m ( 1 0 0 ) ) ;
/ / a t u al i z a o g r á f i c o Up d a t e B u t t o n Cl i c k ( S e l f ) ; e nd;
Podemos acessar as sé ries usando o nome do componente (como S e r i e s 1 ) ou usando a propriedade de array S e r i e s do gr áfico, como em C h a r t 1 . S e r i e s [ J - 1 ] . Nessa expressão, note que os dados em si da grade de strings come çam na linha e na coluna 1 — a primeira linha e coluna, indicadas pelo í n dice zero, são usadas para os elementos fixos — e o array de gr áficos S e r i e s é baseado em zero. Outro exemplo de atualização de cada série est á presente no manipulador do evento On Cl i c k da caixa de seleção; esse m étodo alterna a visibilidade dos marcadores: p r o c e d u r e T Fo r m1 . Ch Bo x Ma r k s C l i c k ( S e n d e r : T Ob j e c t ) ; var
I : I n t e g er ; begin f or I : = 1
t o 4 do Ch a r t 1 . S e r i e s [ I - 1 ] . Ma r k s . V i s i b l e : = Ch B o x Ma r k s . C h e c k e d ;
e nd;
Mas o c ó digo realmente interessante está no m étodo Up d a t e B u t t o n Cl i c k , que atu aliza o gr áfico. Para fazer isso, o programa primeiro remove os dados existentes de cada gr áfico e depois inclui novos dados (ou pontos de dados, para usar o jarg ão correto): p r o c e d u r e T Fo r m1 . Up d a t e Bu t t o n Cl i c k ( S e n d e r : T Ob j e c t ) ; var
I , J : I n t e g er ; begin f or I : = 1 begin
t o 4 do
Ch a r t 1 . S e r i e s [ I - 1 ] . Cl e a r ; f or J := 1 t o 5 do Ch a r t 1 . S e r i e s [ I - 1 ] . Ad d ( S t r T oI n t ( S t r i n g Gr i d 1 . Ce l l s [ J , I ] ) , ’ ’ , Ch a r t 1 . S e r i e s [ I - 1 ] . S e r i e s Co l o r ) ; e nd; e nd;
Os par âmetros do m étodo Ad d (usados quando você n ão quer especificar um valor X , mas apenas um valor Y) são o valor em si, o r ó tulo e a cor. Neste exemplo, o r ó tulo n ão é usado; portanto, simplesmente o omitimos. Poder í a mos ter usado o valor padr ão c l T e e C o l o r para obter a cor correta das séries. Você poderia usar cores especí f icas para indicar diferentes intervalos de dados. U ma vez que você tenha construí d o o gr áfico, o TeeChart permite m uitas opções de visualização. Você pode facilmente ampliar a visualização (basta indicar a área com o bot ão esquerdo do mouse), reduzir (usando o mouse do modo o posto, arrastando em dire ção ao canto superior esquerdo) e usar o bot ão direito do mouse para mover a visualização. Você pode ver um exemplo de zoom na Figura 20.
46
Dominando o Delphi 6 — A Biblía
FIGURA 2 0
O formulário do exemplo Graph1 em tempo de execu ção. Note que demos um zoom no gr áfico.
Criando Séries Dinamicamente O exemplo Graph1 mostra alguns dos recursos do componente TeeChart, mas ele tem por base um ú nico tipo de gr áfico fixo. Poder í a mos t ê-lo aprimorado, p ermitindo alguma personalização da forma das barras verticais; em vez disso, escolhemos uma estrat égia mais geral, permitindo ao usu ário escolher diferentes tipos de séries (gr á ficos). Inicialmente, o componente TeeChart tem os mesmos atributos que no exemplo anterior. Mas agora o formulário tem quatro caixas de combina ção, uma para cada linha da grade de strings. Cada caixa de combinação tem quatro valores (Line, Bar, Area e Point), correspondentes aos quatro tipos de séries que queremos manipular. Para manipular essas caixas de combina ção de uma maneira mais flexí v el no có digo, inclu í m os um array desses controles nos campos privados do formul ário: private
C o m b o s : a r r a y [ 0 . . 3 ] o f T C o m b o B o x ;
Esse array é preenchido com o componente em si no m étodo F o r m C r e a t e , que tamb ém seleciona o item inicial de cada um deles. Aqui est á o novo có digo de F o r mC r e a t e : / / pree nche C o mb o s [ 0 ] : C o mb o s [ 1 ] : C o mb o s [ 2 ] : C o mb o s [ 3 ] : / / mo s t r a o f or I : = 0 Co mb o s [ I
DICA
o a r r a y Co mb o s = C o mb o B o x 1 ; = C o mb o B o x 2 ; = C o mb o B o x 3 ; = C o mb o B o x 4 ; t i p o de g rá f i c o i n i c i a l t o 3 do ] . I t e mI n d e x : = 1 ;
Esse exemplo demonstra um modo comum de criar um array de controles no Delphi, algo que os programadores Visual Basic desejam freqü entemente. Na verdade, o Delphi é t ão flexível que os arrays de controles não são incorporados; você pode criá-los como quiser. Essa estrat égia conta com o fato de que geralmente é possível associar o mesmo manipulador de evento a diferentes eventos, algo que o VB não permite fazer.
Recursos gráficos do Delphi
47
Todas essas caixas de combina ção compartilham o mesmo manipulador de eventos On Cl i c k , que destr ó i cada uma das séries atuais do gr á fico, cria as novas quando solicitadas e depois atualiza suas propriedades e dados: p r o c e d u r e T Fo r m1 . Co mb o Ch a n g e ( S e n d e r : T Ob j e c t ) ; var
I : I n t e g er ; S e r i e s C l a s s : TCh a r t S e r i e s Cl a s s ; Ne wS e r i e s : T Ch a r t S e r i e s ; begin / / d e s t r ó i a s s é r i e s e x i s t e n t e s ( e m o r d e m i n v e r s a ) f o r I : = 3 d o wn t o 0 d o
Ch a r t 1 . S e r i e s [ I ] . F r e e ; / / c r i a as n o v a s s é r i e s f or I : = 0 t o 3 do begin c a s e Co mb o s [ I ] . I t e mI n d e x o f 0 : S e r i e s Cl a s s : = T Li n e S e r i e s ; 1 : S e r i e s Cl a s s : = T Ba r S e r i e s ; 2 : S e r i e s Cl a s s : = TAr e a S e r i e s ; e l s e / / 3 : e p a d r ã o S e r i e s Cl a s s : = T P o i n t S e r i e s ; end; Ne wS e r i e s : = S e r i e s C l a s s . Cr e a t e ( s e l f ) ; Ne wS e r i e s . P a r e n t Ch a r t : = Ch a r t 1 ; Ne wS e r i e s . Ti t l e : = F o r ma t ( ’ S e r i e s %d ’ , [ I + 1 ] ) ; e nd; / / a t u a l i z a o s ma r c a d or e s e o s d a d os C hB o x Ma r k s C l i c k ( s e l f ) ; Up d a t e B u t t o n Cl i c k ( s e l f ) ; Mo d i f i e d : = Tr u e ; e nd;
A parte central desse có digo é a instru ção c a s e , que armazena uma nova classe na vari ável de referência de classe S e r i e s C l a s s , usada para criar os novos objetos de sé rie e configurar as propriedades P a r e n t C h a r t e T i t l e de cada um. Tamb ém poder í a mos ter usado uma chamada ao m étodo A d d S e r i e s do gr áfico em cada desvio da instru ção c a s e e depois configurado a propriedade T i t l e com outro la ço f o r . Na verdade, uma chamada como Ch a r t 1 . A d d Se r i e s ( T Ba r S e r i e s . Cr e a t e ( s e l f ) ) ;
cria os objetos da série e ao mesmo tempo configura seu gr áfico progenitor. Note que essa nova versão do programa permite que você mude o tipo de gr á fico para cada série independentemente. Você pode ver um exemplo do efeito resultante na Figura 21. Por fim o exemplo Graph2 permite salvar os dados atuais que est á apresentando em um arquivo e carregar os dados de arquivos existentes. O programa tem uma vari ável booleana Mo d i f i e d , usada para saber se o usu ário mudou qualquer um dos dados. O suporte a arquivos é baseado em streams e n ão é particularmente com plexo, po is o n ú mero de elementos a salvar é fixo (todos os arquivos t ê m o mesmo tamanho). Aqui est ão os dois m étodos conectados aos itens de menu Open e Save: p r o c e d u r e T Fo r m1 . Op e n 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
L oa d S t r e a m: T Fi l e S t r e a m; I , J , Va l u e : I n t e g er ;
48
Dominando o Delphi 6 — A Biblía
begin i f Op e n Di a l o g 1 . E x e c u t e begin
then
Cu r r e n t F i l e : = Op e n Di a l o g 1 . F i l e n a me ; Ca p t i o n : = ’ G r a p h [ ’ + Cu r r e n t F i l e + ’ ] ’ ; / / c a r r e g a d o a r q ui v o a t u a l L o a d S t r e a m : = T F i l e S t r e a m. Cr e a t e ( Cu r r e n t F i l e , f mOp e n Re a d ) ; try
/ / l ê o v a l o r d e c a d a e l e me n t o d e g r a d e f or I : = 1 t o 5 do f or J : = 1 t o 4 do begin Lo a d St r e a m. Re a d ( Va l u e , s i z e o f ( I n t e g e r ) ) ; S t r i n g Gr i d 1 . Ce l l s [ I , J ] : = I n t T o St r ( Va l u e ) ; end; / / c a r r e g a o s t a t u s d a c a i x a d e s e l e ç ã o e d as c a i x a s d e c o mb i n a ç ã o L o a d St r e a m. Re a d ( Va l u e , s i z e o f ( I n t e g e r ) ) ; Ch Bo x Ma r k s . Ch e c k e d : = Bo o l e a n ( Va l u e ) ; f or I : = 0 t o 3 do begin Lo a d St r e a m. Re a d ( Va l u e , s i z e o f ( I n t e g e r ) ) ; Co mb o s [ I ] . I t e mI n d e x : = Va l u e ; end; finally L o a d S t r e a m. F r e e ; end; / / d i s p a r a a t u al i z a r e v e n t o s C h Bo x Ma r k s Cl i c k ( S e l f ) ; C o m b o C h a n g e ( S e l f ) ; Up d a t e Bu t t o n Cl i c k ( S e l f ) ; Mo d i f i e d : = F a l s e ; e nd; e nd; p r o c e d u r e T Fo r m1 . S a v e 1 Cl i c k ( S e n d e r : T Ob j e c t ) ; var
S a v e St r e a m: T F i l e S t r e a m; I , J , Va l u e : I n t e g er ; begin i f Mo d i f i e d t h e n i f Cu r r e n t F i l e = ’ ’ t h e n / / c h a ma " S a l v a r c o mo " S a v e As 1 C l i c k ( S e l f ) else begin / / s a l v a o ar q u i v o a t u a l
S a v e S t r e a m : = T Fi l e S t r e a m. Cr e a t e ( Cu r r e n t F i l e , f mOp e n Wr i t e o r f mCr e a t e ) ; try
/ / e s c r e v e o v a l o r d e c a d a e l e me n t o d a g r a d e f or I : = 1 t o 5 do f or J : = 1 t o 4 do begin Va l u e : = St r T o I n t De f ( T r i m ( S t r i n g Gr i d 1 . C e l l s [ I , J ] ) , 0 ) ; S a v e S t r e a m. Wr i t e ( Va l u e , s i z e o f ( I n t e g e r ) ) ;
Recursos gráficos do Delphi
49
end; / / s a l v a a c a i x a de s e l e ç ã o e a s c a i x a s d e c o mb i n a ç ã o
Va l u e : = I n t e g e r ( C Bo x Ma r k s . Ch e c k e d ) ; S a v e S t r e a m. Wr i t e ( Va l u e , s i z e o f ( I n t e g e r ) ) ; f or I : = 0 t o 3 do begin
Va l u e : = Co mb o s [ I ] . I t e mI n d e x ; S a v e S t r e a m. Wr i t e ( Va l u e , s i z e o f ( I n t e g e r ) ) ; end; Mo d i f i e d : = Fa l s e ; finally
S a v e St r e a m. F r e e ; end; e nd; end;
FIGURA 2 1
V ários tipos de gr áficos ou séries de gr áfico apr esentados pelo exemplo Graph2.
Um Gr áfico de Banco de Dados na Web No Cap í t ulo 2 2 d este livro, vimos como criar uma figura simples e retorn á-la a partir de um aplicativo CGI. Podemos aplicar a mesma estrat égia para retornar um gr áfico complexo e din âmico, constru í do com o componente T DB Ch a r t . Usar esse componente na mem ó ria é um pouco mais complexo do que configurar todas as suas propriedades em tempo de projeto, pois voc ê ter á que configurar as propriedades no có digo Pascal. (Vo cê n ão pode usar um componente visual, como um DBChart, em um m ó dulo Web ou em qualquer outro m ó dulo de dados.) No aplicativo ISAPI WebChart, usamos a tabela Country.DB para produzir um gr á fico de pizza com a área e a popula ção dos paí ses da Am érica, como no exemplo ChartDb do Cap í t ulo 13 deste livro. Os dois gr áficos sã o gerados por duas a çõ es diferentes, indicadas pelos caminhos / p o p u l a t i o n e / a r e a . Como a maior parte do c ó digo é usada mais de uma vez, o reunimos nos eventos On Cr e a t e e O n A f t e r D i s p a t c h do m ó dulo da Web.
50
Dominando o Delphi 6 — A Biblía
ALERTA
Da maneira como est á escrito, este p rograma não oferece suporte a usuários concorrentes. Você precisará incluir algum có digo de linha de execução (threading) e sincronismo nessa DLL ISAPI, para fazê-lo funcionar com vários usuários simultaneamente. Uma alternativa é colocar todo o código nos manipuladores de eventos Action, para que nenhum objeto global seja compartilhado entre vários pedidos. Ou, então, você pode transformá-lo em um aplicativo CGI.
O m ó dulo de dados tem um objeto tabela, que e tr ês campos privados:
é corretamente inicializado em tempo de projeto,
private
Ch a r t : T DBCh a r t ; S er i e s : TP i e S e r i e s ; I ma g e : T I ma g e ;
Os objetos correspondentes a esses campos são criados junto ao m ó dulo da Web (e usados por chamadas subseq ü entes): p r o c e d u r e T We b Mo d u l e 1 . We b Mo d u l e 1 C r e a t e ( S e n d e r : T Ob j e c t ) ; begin / / a b r e a t a b e l a d e b a nc o d e d ad o s
T a b l e 1 . Op e n ; / / c r i a o g r á f i c o Ch a r t : = TDBCh a r t . Cr e a t e ( n i l ) ; Ch a r t . Wi d t h : = 6 0 0 ; Ch a r t . He i g h t : = 4 0 0 ; Ch a r t . Ax i s V i s i b l e : = F a l s e ; Ch a r t . L e g e n d . Vi s i b l e : = F a l s e ; Ch a r t . Bo t t o mAx i s . T i t l e . Ca p t i o n : = ’ Name ’ ; / / c r i a a s é r i e d e p i z z a S e r i e s : = TP i e S e r i e s . Cr e a t e ( C h a r t ) ; S e r i e s . P a r e n t C h a r t : = Ch a r t ; S e r i e s . Da t a S o u r c e : = Ta b l e 1 ; S e r i e s . XL a b e l s S o u r c e : = ’ Name ’ ; S e r i e s . Ot h e r S l i c e . S t y l e : = p o Be l o wP e r c e n t ; S e r i e s . O t h e r S l i c e . T e x t : = ’ Ot h e r s ’ ; S e r i e s . Ot h e r S l i c e . Va l u e : = 2 ; Ch a r t . Ad d Se r i e s ( S e r i e s ) ; / / c r i a o bi t ma p de me mó r i a I ma g e : = TI ma g e . Cr e a t e ( n i l ) ; I ma g e . Wi d t h : = Ch a r t . Wi d t h ; I ma g e . H e i g h t : = C h a r t . He i g h t ; e nd;
O pr ó ximo passo é executar o manipulador da ação especí f ica, que configura a série do gr áfico de pizza para o campo de dados especí f ico e atualiza alguns t í t ulos: p r o c e d u r e T We b Mo d u l e 1 . We b Mo d u l e 1 Ac t i o n P o p u l a t i o n Ac t i o n (
S e n d e r : T Ob j e c t ; Re q u e s t : T We b Re q u e s t ; Re s p o n s e : T We b Re s p o n s e ; v a r Ha n d l e d : Bo o l e a n ) ; begin / / c o n f i g ur a os v a l o r e s e s p e c í f i c o s
Chart.Title.Text.Clear; Ch a r t . T i t l e . T e x t . Ad d ( ’ Po pu l a t i o n o f Co u n t r i e s ’ ) ; Ch a r t . L e f t A x i s . T i t l e . Ca p t i o n : = ’ Po p ul a t i o n ’ ; S e r i e s . T i t l e : = ’ Po p ul a t i o n ’ ; S e r i e s . P i e Va l u e s . V a l u e S o u r c e : = ’ Po p ul a t i o n ’ ; e nd;
Recursos gráficos do Delphi
51
Isso cria o componente DBChart correto na mem ó ria. O ú ltimo passo, novamente comum para as duas a çõ es, é salvar o gr áfico em uma imagem de bitmap e depois format á-lo como um JPEG em um stream, para ser posteriormente retornado do aplicativo no lado do servidor. O c ó digo é parecido com aquele do exemplo anterior: p r o c e d u r e T We b Mo d u l e 1 . We b Mo d u l e 1 A f t e r Di s p a t c h (
S e n d e r : T Ob j e c t ; Re q u e s t : T We b Re q u e s t ; Re s p o n s e : T We b Re s p o n s e ; v a r Ha n d l e d : Bo o l e a n ) ; var
J p e g : T J p e g I ma g e ; Me mS t r : T Me mo r y S t r e a m; begin / / p i n t a o g r á f i c o n o b i t ma p d e me mó r i a
Ch a r t . Dr a w ( I ma g e . Ca n v a s , I ma g e . Bo u n d s Re c t ) ; / / c r i a o j p e g e c o p i a a i ma g e m n e l e J p e g : = TJ p e g I ma g e . Cr e a t e ; try
J p e g . A s s i g n ( I ma g e . P i c t u r e . Bi t ma p ) ; Me mS t r : = T Me mo r y S t r e a m. Cr e a t e ; / / sal va em um st ream e o ret orna J p e g . S a v e T o St r e a m ( Me mS t r ) ; Me mS t r . P o s i t i o n : = 0 ; Re s p o n s e . Co n t e n t T y p e : = ’ i ma g e / j p e g ’ ; Re s p o n s e . Co n t e n t S t r e a m : = Me mS t r ; Re s p o n s e . S e n d Re s p o n s e ; finally
Jpeg.Free; e nd; e nd;
O resultado, visí v el na Figura 22, é certamente interessante. Como op ção, você pode estender esse aplicativo vinculando-o a um a tabela H TML que mo stra os dados do b anco de dados. Basta escrever um programa com uma a ção principal que retorne a tabela H TML e u ma referê ncia à figura incorporada, que ser á retornada por uma segunda ativação da DLL ISAPI com um caminho diferente. FIGURA 2 2
O JPEG com o gr áfico de população gerado pelo aplicativo WebChart.
52
Dominando o Delphi 6 — A Biblía
Usando Meta-Arquivos Os formatos de bitmap abordados anteriormente neste cap í t ulo armazenavam o status de cada pixel de um bitmap, embora eles normalmente compactassem a informa ção. Um tipo totalmente diferente de formato gr áfico é representado pelos formatos orientados a vetores. Nesse caso, o arquivo armazena a informação exigida para recriar a figura, como os pontos inicial e final de cada linha ou o cálculo matem ático que define uma curva. Existem muitos formatos de arquivo orientados a vetores diferentes, mas o ú nico suportado pelo sistema operacional Windows é o WMF (Windows Metafile Format) — formato de meta-arquivos do Windows. Esse formato foi estendido no Win32 para o EMF (Extended Metafile Format) — formato de meta-arquivo estendido, que armazena informa çõ es extras relacionadas aos modos de mapeamento e ao sistema de coordenadas. Um meta-arquivo do Windows é basicamente uma série de chamadas à s fun çõ es primitivas da GDI. Depois de ter armazenado a seq üê ncia de chamadas, você pode execut á-las, reproduzindo as figuras. O Delphi suporta meta-arquivos do Windows atrav és das classes T Me t a f i l e e T Me t a F i l e C a n v a s . Construir um exemplo é muito simples. A classe T Me t a f i l e é usada para manipular o arquivo em si, com m étodos para carregar e salvar os arquivos, e propriedades que determinam os principais recursos do arquivo. Um a delas é a propriedade E n h a n c e d , que determina o tipo de formato de meta-arquivo. Note que, quando o Windows est á lendo um arquivo, a propriedade E n h a n c e d é configurada dependendo da extensão de arquivo — WMF para meta-arquivos do Windows 3.1 e EMF para os meta-arquivos estendidos do Win32. Para gerar um meta-arquivo, você pode usar um objeto da classe T Me t a f i l e C a n v a s , conectado ao arquivo através de seus construtores, como se v ê no seguinte trecho de có digo: Wmf : = TMe t a f i l e . Cr e a t e ; Wmf Ca n v a s : = T Me t a f i l e C a n v a s . Cr e a t e Wi t h Co mme n t ( Wmf , 0 , ’ Ma r c o ’ , ’ De mo me t a f i l e ’ ) ;
Um a vez que você tenha criado os dois objetos, pode pint ar sobre o o bjeto canvas com chamadas regulares e, no final, salvar o meta-arquivo conectado a um arquivo f í sico. Uma vez que você tenha o meta-arquivo (um novo que acabou de criar ou um constru í d o com outro programa), pode apresent á-lo em um componente Image ou pode simplesmente chamar os m é todos Dr a w ou S t r e t c h D r a w de qualquer canvas. No exemplo WmfDemo, escrevemos algum c ó digo simples, apenas para mostrar a voc ê os fundamentos dessa estrat égia. O manipulador do evento On Cr e a t e do formulário cria o meta-arquivo estendido, um objeto simples que é usado para operaçõ es de leitura e escrita: p r o c e d u r e T Fo r m1 . F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; begin
Wmf : = TMe t a f i l e . Cr e a t e ; Wmf . E n h a n c e d : = T r u e ; R a n d o mi z e ; e nd;
O formulário do programa tem dois bot õ es e os componentes PaintBox, mais uma caixa de seleçã o. O primeiro bot ão cria um meta-arquivo, gerando uma s érie de linhas parcialmente aleató rias. O resultado aparece no primeiro componente PaintBox e tamb ém é salvo em um arquivo fixo: p r o c e d u r e T Fo r m1 . Bt n Cr e a t e Cl i c k ( S e n d e r : TOb j e c t ) ; var
Wmf Ca n v a s : T Me t a f i l e C a n v a s ; X, Y: I n t e g e r ; begin
Recursos gráficos do Delphi
53
/ / c r i a o c a nv a s v i r t u al Wmf Ca n v a s : = TMe t a f i l e C a n v a s . Cr e a t e Wi t h C o mme n t ( Wmf , 0 , ’ Ma r c o ’ , ’ De mo me t a f i l e ’ ) ; try
/ / l i mp a o f u n do Wmf C a n v a s . Br u s h . Co l o r : = c l Wh i t e ; Wmf C a n v a s . F i l l Re c t ( Wmf C a n v a s . Cl i p Re c t ) ; / / d e s e n h a 4 0 0 l i n h as f or X : = 1 t o 20 do f or Y : = 1 t o 20 do begin
Wmf C a n v a s . Mo v e T o ( 1 5 * ( X + Ra n d o m ( 3 ) ) , 1 5 * ( Y + Ra n d o m ( 3 ) ) ) ; Wmf Ca n v a s . L i n e T o ( 4 5 * Y , 4 5 * X ) ; end; finally / / f i n al i z a a o p e r a ç ã o d e d e s e n h o
Wmf C a n v a s . F r e e ; e nd;
/ / a p r e s e n t a o d e s e n h o a t u al e o s a l v a P a i n t Bo x 1 . C a n v a s . Dr a w ( 0 , 0 , Wmf ) ; Wmf . S a v e To F i l e ( E x t r a c t F i l e P a t h ( Ap p l i c a t i o n . E x e Na me ) + ’ t e s t . e mf ’ ) ; e nd;
ALERTA
Se você desenhar ou salvar o meta-arquivo antes que o canvas do meta-arquivo conectado seja fechado ou destruído, essas operações não produzirão n enhum efeito ! Esse é o mot ivo pelo qual chamamos o método F r e e antes de chamar D r a w e S a v e To F i l e .
Recarregar e repintar o meta-arquivo
é ainda mais simples:
p r o c e d u r e T Fo r m1 . Bt n Lo a d Cl i c k ( S e n d e r : T Ob j e c t ) ; begin / / c a r r e g a o me t a - a r q ui v o
Wmf . L oa d F r o mF i l e ( E x t r a c t F i l e P a t h ( Ap p l i c a t i o n . E x e Na me ) + ’ t e s t . e mf ’ ) ; / / d e s e n ha - o o u e s t i c a - o i f c b S t r e t c h e d . Ch e c k e d t h e n
P a i n t Bo x 2 . C a n v a s . S t r e t c h Dr a w Pa i n t Bo x 2 . C a n v a s . Cl i p Re c t , Wmf ) else
P a i n t Bo x 2 . C a n v a s . Dr a w ( 0 , 0 , Wmf ) ; e nd;
Note que você pode reproduzir exatamente o mesmo desenho, mas tamb ém modificá-lo com a chamada a S t r e t c h Dr a w . (O resultado dessa opera ção aparece na Figura 23.) Essa opera ção é diferente do esticamento de um bitmap, que normalmente degrada ou modifica a imagem, pois aqui estamos mudando a escala atravé s da alteração do mapeamento de coordenadas. Isso significa que, ao imp rimir um m eta-arquivo, você pode esticá-lo para preencher a p ágina inteira com um efeito muito bom, algo muito dif í c il de fazer com um bitmap.
54
Dominando o Delphi 6 — A Biblía
FIGURA 2 3
A saí d a do exemplo WmfD emo com um meta-arquivo alongado.
Girando Texto Neste cap í t ulo, abord amos muitos exemplos diferentes do uso de bitmaps e criamos figuras de mu itos tipos. Entretanto, o tipo mais importante de figura com o qual normalmente tratamos nos aplicativos Delphi é texto. Na verdade, mesmo quando apresenta um r ó tulo ou o texto de uma caixa de edi ção, o Windows ainda o pinta da mesma maneira como qualquer outro elemento gr áfico. Já apresentamos um exemplo de pintura de fonte anteriormente neste capí t ulo, no exemplo FontGrid. Agora, vamos voltar a esse assunto com uma estrat égia ligeiramente menos comum. Quando você pinta texto no Windows, n ã o h á meios de indicar a dire ção da fonte: o Windows parece desenhar o texto apenas horizontalmente. Entretanto, para sermos precisos, o Windows desenha o texto na dire ção aceita por sua fonte, que é horizontal como padr ão. Por exemplo, podemos mudar o texto apresentado pelos componentes de um formulário, modificando a fonte do pr ó prio formu lário, como fizemos no exemplo SideText. Na verdade, voc ê n ã o pode modificar uma fonte, mas pode criar uma nova fonte, semelhante a uma j á existente: p r o c e d u r e T Fo r m1 . F o r mCr e a t e ( S e n d e r : T Ob j e c t ) ; var
ALo g F o n t : T Lo g F o n t ; h F o n t : T Ha n d l e ; begin
ALo g F on t . ALo g F on t . ALo g F on t . ALo g Fo n t . ALo g F o n t . ALo g Fo n t . ALo g F on t . ALo g F on t . ALo g F on t . ALo g Fo n t . ALo g Fo n t . ALo g Fo n t . ALo g F on t .
l f H e i g h t : = F o n t . He i g h t ; l f Wi d t h : = 0 ; l f E s c a p e me n t : = - 4 5 0 ; l f Or i e n t a t i o n : = - 4 5 0 ; l f We i g h t : = f w_ De mi Bo l d ; lf It ali c := 0; // falso l f U nd e r l i n e : = 0 ; / / f a l s o l f S t r i k e Ou t : = 0 ; / / f a l s o l f C h a r S e t : = An s i _ Ch a r S e t ; l f Ou t P r e c i s i o n : = Ou t _ De f a u l t _ P r e c i s ; l f Cl i p P r e c i s i o n : = Cl i p _ De f a u l t _ P r e c i s ; l f Qu a l i t y : = De f a u l t _ Qu a l i t y ; l f P i t c h An d F a mi l y : = De f a u l t _ P i t c h ;
Recursos gráficos do Delphi
55
S t r Co p y ( AL o g F o n t . l f F a c e N a me , P Ch a r ( F o n t . Na me ) ) ; F on t : = C r e a t e F o n t I n d i r e c t ( A L o g Fo n t ) ; F o n t . Ha n d l e : = F o n t ; e nd;
Esse có digo produziu o efeito desejado no r ó tulo do formulário d o exemplo, mas, se você incluir outros componentes nele, o texto geralmente ser á impresso fora da parte vis í v el do componente. Em outras palavras, você precisar á fornecer esse tipo de suport e dentro d e component es se quiser que tud o apareça corretamente. Para os r ó tulos, entretanto, você pode evitar criar um novo componente; em vez disso, basta mudar a fonte associada ao C a n v a s do formulá rio (e n ão o formul ário inteiro) e usar os m é todos de desenho de texto padr ão. O exemplo SideText muda a fonte do C a n v a s no m étodo On P a i n t , que é semelhante a On Cr e a t e : p r o c e d u r e T Fo r m1 . F o r mP a i n t ( S e n d e r : T Ob j e c t ) ; var
AL og F o n t : T Lo g F o n t ; h F on t : T Ha n d l e ; begin
AL og F o nt . l f H e i g h t : = F o n t . He i g h t ; AL og F o nt . l f E s c a p e me n t : = 9 0 0 ; AL o g F o n t . l f Or i e n t a t i o n : = 9 0 0 ; ... h F o n t : = C r e a t e F o n t I n d i r e c t ( A L o g Fo n t ) ; Ca n v a s . F o n t . Ha n d l e : = h F o nt ; Ca n v a s . T e x t Ou t ( 0 , Cl i e n t He i g h t , ’ He l l o ’ ) ; e nd;
A orienta ção da fonte tamb ém é modificada por um terceiro manipulador de eventos, associado a um temporizador. Seu efeito é girar o formulário com o passar do tempo, e seu c ó digo é muito parecido com o procedimento anterior, com exce ção do c ó digo para determinar o escape de fonte (o ângulo da rotação da fonte): AL o g F o n t . l f E s c a p e me n t : = - ( G e t T i c k C o u n t d i v 1 0 ) mo d 3 6 0 0 ;
Com essas tr ês t é cnicas diferentes de rotação de fonte (t í t ulo de r ó tulo, texto pintado e texto girando com o passar do tempo), o formul ário do programa SideText em tempo de execu ção é semelhante à Figura 24. FIGURA 2 4
Os efeitos do exemplo SideText, com t exto realmente girando.