Você está na página 1de 968

Tradução

Sandra Figueiredo & Carlos Schafranski

Revisão técnica
Fernando Vasconcelos Mendes
Gerente de Tecnologia da Softium Informática Ltda.
Editor da Revista Clube Delphi

São Paulo
© 2005 by Pearson Education do Brasil
© 2003 by Que Publishing
Tradução autorizada a partir da edição original em inglês: Delphi for .NET Developer’s Guide,
by Xavier Pacheco, publicado pela Pearson Education, Inc., Que/Sams

Todos os direitos reservados. Nenhuma parte desta publicação poderá ser reproduzida ou
transmitida de qualquer modo ou por qualquer outro meio, eletrônico ou mecânico, incluindo
fotocópia, gravação ou qualquer outro tipo de sistema de armazenamento e transmissão de
informação, sem prévia autorização, por escrito, da Pearson Education do Brasil.

Os nomes de empresas e produtos mencionados neste livro são marcas comerciais ou


registradas de seus respectivos proprietários.

Diretor Editorial: José Martins Braga


Consultora Editorial: Docware Traduções Técnicas
Gerente de Produção: Heber Lisboa
Editor de Texto: Gustav Schmid
Designer de capa: Alberto Cotrim (sobre projeto original)
Editoração Eletrônica: Estúdio Castellani
Imagem de capa: © Stockbyte/PictureQuest

Dados Internacionais de Catalogação na Publicação (CIP)


(Câmara Brasileira do Livro, SP, Brasil)

Pacheco, Xavier
Guia do desenvolvedor de Delphi for .NET / Xavier Pacheco ; tradução Sandra Figueiredo
& Carlos Schafranski ; revisão técnica Fernando Vasconcelos Mendes. – São Paulo : Pearson
Makron Books, 2005.

Título original: Delphi for .NET developer’s guide.


ISBN 85-346-1542-X

1. Delphi (Linguagem de computador) 2. Microsoft .NET Framework I. Título.

04-8700 CDD-005.265

Índices para catálogo sistemático


1. Delphi for .NET : Programação de computadores :
Ciência da computação 005.265

2005
Direitos exclusivos para a língua portuguesa cedidos à
Pearson Education do Brasil,
uma empresa do grupo Pearson Education
Av. Ermano Marchetti,1435
CEP: 05038-001 – São Paulo – SP
Tel.: (11) 3613-1222 – Fax: (11) 3611-0444
Sumário Geral

Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XXVII

Parte I Introdução ao .NET Framework


1 Introdução ao .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
2 Visão geral sobre o .NET Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Parte II A linguagem de programação do Delphi for.NET3


3 Introdução ao Delphi for .NET e a nova IDE . . . . . . . . . . . . . . . . . . . . . 23
4 Programas, units e namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5 A Linguagem Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Parte III O desenvolvimento com a biblioteca de classes


do .NET Framework
6 Assemblies – Bibliotecas e packages. . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7 Programação GDI+ – Desenhando no .NET . . . . . . . . . . . . . . . . . . . . . 139
8 Mono – um projeto .NET para múltiplas plataformas . . . . . . . . . . . . . 183
9 Gerenciamento de memória e garbage collection. . . . . . . . . . . . . . . . . 206
10 Coleções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
11 Trabalhando com as classes String e StringBuilder . . . . . . . . . . . . . . . . 243
12 Operações de arquivo e de streaming . . . . . . . . . . . . . . . . . . . . . . . . . . 263
13 Desenvolvendo controles WinForms personalizados . . . . . . . . . . . . . . 287
14 Threading no Delphi for .NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
15 API Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
16 Interoperabilidade – COM Interop e o Platform Invocation Service . . 391

Parte IV Desenvolvimento para bancos de dados com o ADO.NET


17 Visão geral sobre o ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
18 Utilizando o objeto connection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
19 Utilizando os objetos Command e DataReader . . . . . . . . . . . . . . . . . . 456
20 DataAdapters e DataSets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
21 Trabalhando com WinForms – Visualização e vinculação de dados . . 500
22 Salvando dados na origem de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . 528
23 Trabalhando com transações e DataSets fortemente tipificados. . . . . . 558
24 O Borland Data Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
VI Guia do desenvolvedor de Delphi for .NET

Parte V Desenvolvimento para Internet com o ASP.NET


25 Fundamentos do ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591
26 Criando páginas Web com o ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . 610
27 Construindo aplicações ASP.NET com acesso a banco de dados . . . . . 642
28 Criando Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
29 .NET Remoting e Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
30 .NET Remoting em ação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737
31 Tornando aplicações ASP.NET seguras . . . . . . . . . . . . . . . . . . . . . . . . . 769
32 Distribuição e configuração do ASP.NET. . . . . . . . . . . . . . . . . . . . . . . . 787
33 Cache e gerenciamento de estados em aplicações ASP.NET . . . . . . . . . 811
34 Desenvolvendo controles ASP.NET Server personalizados . . . . . . . . . . 839
Índice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 871
Sumário

Introdução XXVII

1 Introdução ao .NET 1
A iniciativa .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
A visão .NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Os componentes do .NET Framework – o Common Language Runtime
e as bibliotecas de classe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Tipos de aplicações .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Explicação sobre a VCL for .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Desenvolvimento distribuído por meio de Web Services . . . . . . . . . . . . . . . . . . . . 7
Definição de Web Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Clientes Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Ferramentas de desenvolvimento de Web Services. . . . . . . . . . . . . . . . . . . . 10

2 Visão geral sobre o .NET Framework 11


Do desenvolvimento à execução. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
O Common Language Runtime (CLR) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Módulos gerenciados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Código gerenciado e não-gerenciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Compilação e execução MSIL e JIT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
O Common Type System (CTS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Value types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Reference types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
A Common Language Specification (CLS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
.NET Framework Class Library (FCL) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
O namespace System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Namespaces primários do subsistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3 Introdução ao Delphi for .NET e a nova IDE 23


Delphi for .NET– um cenário maior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Introdução ao Integrated Development Environment (IDE) . . . . . . . . . . . . . . . . 24
Welcome Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Designer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Formulários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Tool Palette/Code Snippets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
VIII Guia do desenvolvedor de Delphi for .NET

Object Inspector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Code Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Project Manager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Model View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Data Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Object Repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Code Explorer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
To-Do List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

4 Programas, units e namespaces 38


Estruturas do módulo gerenciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
A estrutura do programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
A estrutura da unit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Sintaxe da cláusula uses. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Referência circular de unit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Declaração de namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Utilização de namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
A cláusula de namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Convertendo namespaces genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Aliases de unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

5 A Linguagem Delphi 48
Tudo sobre o .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Comentários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Procedures e funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Parênteses em chamadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Parâmetros com valor padrão (default) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Constantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores de atribuição. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores de comparação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Operadores binários. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Procedures para incrementar e decrementar. . . . . . . . . . . . . . . . . . . . . . . . . 58
Operadores do tipo “fazer e atribuir” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Tipos na linguagem Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Objetos em todos os lugares! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Uma comparação de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Caracteres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Tipos Variant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Sumário IX

Tipos definidos pelo usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66


Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Arrays dinâmicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Registros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Código inseguro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Classes e objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Aliases de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Typecasting e conversão de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Strings de recursos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Testando condições . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
A instrução if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Utilização de instruções case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Loops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
O loop for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
O loop while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
repeat..until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
A instrução Break. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
A instrução Continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Procedures e funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Passando parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Escopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Units e namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
A cláusula uses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Referência circular de unit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Packages e assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Programação orientada a objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Utilizando objetos no Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Declaração e instanciação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Destruição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
A origem dos objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Campos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Tipos de métodos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Referências de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Propriedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Especificadores de visibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Classes “amigáveis” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Class Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Tipos aninhados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Sobrecarga de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
X Guia do desenvolvedor de Delphi for .NET

Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Tratamento estruturado de exceções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Classes de exceção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Fluxo de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Regerando uma exceção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

6 Assemblies – Bibliotecas e packages 121


Assemblies centrais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Visualizando conteúdo e dependências de assemblies . . . . . . . . . . . . . . . . . . . . 121
Lembrar do GAC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Construindo assemblies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Por que utilizar assemblies?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Utilização de packages para construir assemblies . . . . . . . . . . . . . . . . . . . . 125
Utilização de bibliotecas para construir assemblies . . . . . . . . . . . . . . . . . . 130
Utilização de assemblies no Delphi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Utilização de assemblies do Delphi em C# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Instalando packages na IDE do Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Atribuição de nomes fortes a assemblies. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Carregando assemblies dinamicamente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

7 Programação GDI+ – Desenhando no .NET 139


Conceitos fundamentais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Os namespaces da GDI+ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
A classe Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Siatema de coordenadas do Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
Desenhando linhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
As classes Pen e Brush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
Desenhando linhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Pontas de linhas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Unindo linhas – a classe GraphicsPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Desenhando curvas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
O spline cardinal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
O spline de Bezier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Desenhando formas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Desenhando retângulos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Desenhando elipses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Desenhando polígonos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Desenhando gráficos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Mais sobre o LinearGradientBrush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
GraphicsPaths e Regions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Desenhando com a classe GraphicsPath . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Desenhando com a classe Region . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Recortando regiões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Trabalhando com imagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Sumário XI

As classes de imagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162


Carregando e criando bitmaps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Alterando a resolução de uma imagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Desenhando uma imagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Interpolação. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
Desenhando um efeito de espelho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
Utilização dos métodos de transformação . . . . . . . . . . . . . . . . . . . . . . . . . 168
Criando uma miniatura. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Revisitando os sistemas de coordenadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
Exemplo de animação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

8 Mono – um projeto .NET para múltiplas plataformas 183


Recursos do Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
História do Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Por que o Mono? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Guia do Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Objetivos do Mono 1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Objetivos do Mono 1.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Objetivos do Mono 1.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Instalação/Configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Instalação do Mono – utilizando o Red Carpet. . . . . . . . . . . . . . . . . . . . . . 187
Criando seu primeiro programa no Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Executando assemblies gerados no Delphi sob o Mono (no Linux). . . . . . . . . . 191
O ASP.NET para múltiplas plataformas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Distribuição do ASP.NET para o Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Configuração do XSP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Parâmetros de runtime do XSP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
Algumas armadilhas e uma pequena extensão de exemplo . . . . . . . . . . . . 198
O ADO.NET com o Mono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
O Mono e o Apache. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
O Mono e o System.Windows.Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

9 Gerenciamento de memória e garbage collection 206


Como a Garbage Collection funciona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Garbage Collection geracional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Invocando o Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
Construtores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Finalização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
O padrão Dispose – IDisposable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Exemplo de IDisposable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Implementando a IDisposable automaticamente . . . . . . . . . . . . . . . . . . . . 214
Questões de desempenho com relação à finalização . . . . . . . . . . . . . . . . . . . . . 216

10 Coleções 217
Interfaces System.Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
XII Guia do desenvolvedor de Delphi for .NET

Interface IEnumerable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218


Interface ICollection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Interface IList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Interface IDictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Interface IEnumerator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Classes System.Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
A coleção Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
A classe Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
A classe ArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
A classe HashTable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Criando uma coleção fortemente tipificada . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Descendendo da CollectionBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Utilizando coleção fortemente tipificada . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Criando um dicionário fortemente tipificado . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Descendendo da DictionaryBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Utilizando o dicionário fortemente tipificado . . . . . . . . . . . . . . . . . . . . . . 241

11 Trabalhando com as classes String e StringBuilder 243


O tipo System.String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Imutabilidade da string no .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
Operações com strings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
Comparação de strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
A classe StringBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Métodos StringBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
Uso da StringBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Formatação de string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Especificadores de formato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
Especificadores de formato numérico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
Especificadores de formato de data e hora . . . . . . . . . . . . . . . . . . . . . . . . . 257
Especificadores de formato de tipos enumerados . . . . . . . . . . . . . . . . . . . . 261

12 Operações de arquivo e de streaming 263


Classes no namespace System.IO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
Trabalhando com o sistema de diretórios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Criando e excluindo diretórios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
Movendo e copiando diretórios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
Examinando informações no diretório . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Trabalhando com arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Criando e excluindo arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Movendo e copiando arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Examinando informações no arquivo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Trabalhando com Text File Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Trabalhando com streams de arquivos binários . . . . . . . . . . . . . . . . . . . . . 275
Acesso assíncrono a streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Sumário XIII

Monitorando a atividade do diretório . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279


Serialização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
Como a serialização funciona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
Formatters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
Um exemplo de serialização/desserialização . . . . . . . . . . . . . . . . . . . . . . . . 283

13 Desenvolvendo controles WinForms personalizados 287


Princípios básicos da construção de componentes. . . . . . . . . . . . . . . . . . . . . . . 288
Quando construir um componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
Passos da criação de componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Decidindo sobre a uma classe ancestral . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
Criando uma unit para o componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
Criando propriedades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
Criando eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
Criando métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
Construtores e destrutores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
Comportamento em tempo de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Testando o componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Fornecendo um ícone de componente . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
Componentes de exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
ExplorerViewer: um exemplo de UserControl . . . . . . . . . . . . . . . . . . . . . . 314
SimpleStatusBars: utilização de Extender Providers . . . . . . . . . . . . . . . . . . 324
Pintura de usuário: o controle PlayingCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

14 Threading no Delphi for .NET 338


Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
Threads no estilo .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
AppDomain . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
O namespace System.Threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
A classe System.Threading.Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
System.Threading.ThreadPriority . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
System.Threading.ThreadState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
O tipo enumerado System.Threading.ApartmentState . . . . . . . . . . . . . . . . 348
A classe System.Threading.ThreadPool . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
A classe System.Threading.Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
Escrevendo código thread-safe no estilo .NET . . . . . . . . . . . . . . . . . . . . . . . . . . 354
Mecanismos bloqueadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
Eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
Armazenamento local de thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
Comunicações interprocessos do Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . 361
Classes e métodos thread-safe do .NET Framework . . . . . . . . . . . . . . . . . . 361
Questões da interface com o usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
O método System.Windows.Forms.Control.Invoke( ) . . . . . . . . . . . . . . . . 363
XIV Guia do desenvolvedor de Delphi for .NET

A propriedade System.Windows.Forms.Control.InvokeRequired . . . . . . . 363


O método System.Windows.Forms.Control.BeginInvoke( ). . . . . . . . . . . . 364
O método System.Windows.Forms.Control.EndInvoke( ) . . . . . . . . . . . . . 364
O método System.Windows.Forms.Control.CreateGraphics( ) . . . . . . . . . 365
Exceções em threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
System.Threading.ThreadAbortException . . . . . . . . . . . . . . . . . . . . . . . . . 368
System.Threading.ThreadInterruptedException . . . . . . . . . . . . . . . . . . . . . 370
System.Threading.ThreadStateException . . . . . . . . . . . . . . . . . . . . . . . . . . 371
System.Threading.SynchronizationLockException . . . . . . . . . . . . . . . . . . 371
Garbage Collection e threading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371

15 API Reflection 372


Reflection em um assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
Reflection em um módulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
Reflection em tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
Invocação em tempo de execução de membros de um tipo (vinculação tardia) . 379
Invocando os tipos do membro para eficiência . . . . . . . . . . . . . . . . . . . . . 382
Um outro exemplo de invocação de membro . . . . . . . . . . . . . . . . . . . . . . 383
Emitindo MSIL por meio da reflexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
Ferramentas/classes para emitir MSIL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
Processo de emissão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388
Um exemplo de System.Reflection.Emit . . . . . . . . . . . . . . . . . . . . . . . . . . . 388

16 Interoperabilidade – COM Interop e o Platform Invocation Service 391


Por que ter interoperabilidade?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
Questões comuns de interoperabilidade. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
Utilizando objetos COM no código .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
Automation e vinculação tardia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
Valor, referência e parâmetros opcionais . . . . . . . . . . . . . . . . . . . . . . . . . . 396
Vinculação inicial no COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
Assemblies de interoperabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
Criando um assembly de interoperabilidade . . . . . . . . . . . . . . . . . . . . . . . 402
O que há em um assembly de interoperabilidade? . . . . . . . . . . . . . . . . . . . 402
Utilização de eventos COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
Controle do tempo de vida do COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
Tratamento de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
Assemblies de interoperabilidade primários . . . . . . . . . . . . . . . . . . . . . . . . 406
Personalizando assemblies de interoperabilidade e PIAs . . . . . . . . . . . . . . 408
Utilizando objetos .NET no código COM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
Registrando um assembly .NET para Automation . . . . . . . . . . . . . . . . . . . 409
Automation e vinculação tardia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
Type Libraries de interoperabilidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
O que há em uma Type Library de interoperabilidade? . . . . . . . . . . . . . . . 412
Implementando interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
Tipos de parâmetros e empacotamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
Sumário XV

Tratamento de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418


Utilizando rotinas exportadas de DLLs do Win32 no código .NET . . . . . . . . . . 419
Sintaxe Delphi tradicional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
Sintaxe de atributos personalizados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Tipos de parâmetros e empacotamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
Tratamento de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
Códigos de erros Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
Códigos de erros HResult. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Questões de desempenho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Utilizando rotinas .NET no código Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
Sintaxe Delphi tradicional. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436
Tipos de parâmetros e empacotamento . . . . . . . . . . . . . . . . . . . . . . . . . . . 437

17 Visão geral sobre o ADO.NET 443


Princípios de projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Arquitetura de dados desconectados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
Integração com XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Representação comum de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Embutido no .NET Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Vantagens das tecnologias existentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Objetos ADO.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
Classes conectadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
Classes desconectadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447
Data Providers .NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447

18 Utilizando o objeto connection 449


Funcionalidade de conexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
Configurando a propriedade ConnectionString . . . . . . . . . . . . . . . . . . . . . . . . . 450
Especificando uma SqlConnection.ConnectionString . . . . . . . . . . . . . . . . 450
Especificando uma OleDbConnection.ConnectionString . . . . . . . . . . . . . 451
Especificando uma OdbcConnection.ConnectionString . . . . . . . . . . . . . . 451
Especificando uma OracleConnection.ConnectionString . . . . . . . . . . . . . 452
Abrindo e fechando conexões. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
Eventos de conexão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Pool de conexão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455

19 Utilizando os objetos Command e DataReader 456


Executando comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
A Interface IDbCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
Comandos Non-Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
Recuperando valores únicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
Executando comandos Data Definition Language (DDL) . . . . . . . . . . . . . . . . . 460
Especificando parâmetros com IDbParameter . . . . . . . . . . . . . . . . . . . . . . . . . . 462
Executando stored procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464
XVI Guia do desenvolvedor de Delphi for .NET

Derivando parâmetros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466


Consultando resultsets com DataReaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
A interface IDataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Consultando um resultset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Consultando múltiplos resultsets com DataReaders. . . . . . . . . . . . . . . . . . . . . . 469
Utilizando DataReader para recuperar dados BLOB . . . . . . . . . . . . . . . . . . . . . . 470
Utilizando DataReader para recuperar informações sobre estrutura . . . . . . . . . 472

20 DataAdapters e DataSets 475


DataAdapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
Composição de DataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
Criando um DataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 476
Obtendo resultados de consultas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
Mapeando resultados de consultas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
Trabalhando com DataSets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
Composição de DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484
Operações de DataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485
Trabalhando com DataTables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
Definindo colunas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
Definindo chaves primárias. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Trabalhando com constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
Trabalhando com DataRelations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
Manipulando dados – trabalhando com DataRow . . . . . . . . . . . . . . . . . . . 495
Pesquisar, classificar e filtrar dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497

21 Trabalhando com WinForms – Visualização e vinculação de dados 500


Exibindo dados com DataView e DataViewManager . . . . . . . . . . . . . . . . . . . . . 500
A classe DataView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
A classe DataViewManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Projetos de exemplo com DataView e DataViewManager . . . . . . . . . . . . . 503
Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
As interfaces Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
Data Binding simples versus complexa . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Classes WinForm Data Binding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Criando Windows Forms de vinculação de dados . . . . . . . . . . . . . . . . . . . 516

22 Salvando dados na origem de dados 528


Atualizando a origem de dados com SQLCommandBuilder . . . . . . . . . . . . . . . 528
Atualizando a origem de dados com a lógica de atualização personalizada . . . 531
Utilizando uma classe Command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531
Utilizando a classe SqlDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539
Atualizando com uma stored procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Tratando concorrência . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551
Atualizando dados depois da atualização . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Sumário XVII

23 Trabalhando com transações e DataSets fortemente tipificados 558


Processamento de transação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
Um exemplo simples de processamento de transação . . . . . . . . . . . . . . . . 559
Transações ao utilizar um DataAdapter. . . . . . . . . . . . . . . . . . . . . . . . . . . . 562
Níveis de isolamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
Pontos de salvamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
Transações aninhadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565
DataSets fortemente tipificados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Vantagens e desvantagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Criando DataSets fortemente tipificados . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Examinando o arquivo .pas de um DataSet com tipificação forte . . . . . . . 568
Utilizando o DataSet fortemente tipificado . . . . . . . . . . . . . . . . . . . . . . . . 576

24 O Borland Data Provider 578


Visão geral da arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
Classes Borland Data Provider. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 579
BdpConnection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
BdpCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581
BdpDataReader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 582
BdpDataAdapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583
BdpParameter/BpdParameterCollection . . . . . . . . . . . . . . . . . . . . . . . . . . . 584
BdpTransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
Designers dentro da IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
O Connections Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587
O Command Text Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587
O Parameter Collection Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587
Caixa de diálogo Data Adapter Configuration . . . . . . . . . . . . . . . . . . . . . . 588

25 Fundamentos do ASP.NET 591


Tecnologias Web – como elas funcionam. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591
Visão geral sobre o protocolo HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592
O pacote de solicitação HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592
O pacote de resposta HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593
ASP.NET – como ele funciona . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594
Uma aplicação Web simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
Estrutura de página do ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
Comunicação baseada em evento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 599
VIEWSTATE e manutenção de estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 600
CodeBehind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 600
Classes ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601
A classe HTTPResponse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601
A Classe HTTPRequest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
A Classe HTTPCookie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Tratando eventos de Postback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608
XVIII Guia do desenvolvedor de Delphi for .NET

26 Criando páginas Web com o ASP.NET 610


Criando páginas Web com controles ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . 610
Exemplo de formulário de solicitação de download. . . . . . . . . . . . . . . . . . 611
Layout de página . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612
Criando um formulário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Processando o evento de carga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Salvando arquivos de dentro de uma aplicação ASP.NET. . . . . . . . . . . . . . 614
Ordem do processamento de eventos para um formulário da Web . . . . . . 616
Pré-preenchendo controles de lista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 616
Realizando validação de formulário Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Validação no cliente versus no servidor. . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Classe BaseValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
RequiredFieldValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619
CompareValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
RegularExpressionValidator. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
RangeValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
CustomValidator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
ValidationSummary. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Formatação de formulário Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Propriedades WebControl fortemente tipificadas. . . . . . . . . . . . . . . . . . . . 625
Folhas de estilo em cascata . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627
Utilização da classe Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 628
Navegando entre formulários Web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
Passando dados via POST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
Utilizando o método Response.Redirect( ) e QueryString. . . . . . . . . . . . . . 630
Utilizando o método Server.Transfer( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Utilizando variáveis de sessão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 632
Dicas e técnicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Utilizando o controle Panel para simulação de múltiplos formulários . . . 633
Fazendo o upload de um arquivo a partir do cliente . . . . . . . . . . . . . . . . . 635
Enviando um e-mail de resposta a partir de um formulário. . . . . . . . . . . . 636
Exibindo imagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 638
Adicionando controles dinamicamente – um visualizador
de imagens baseado em miniaturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639

27 Construindo aplicações ASP.NET com acesso a banco de dados 642


Data Binding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642
Vinculação simples de dados. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
Vinculação complexa de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Controles de lista com vinculação de dados. . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Controle CheckBoxList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648
Controle DropDownList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650
Controle ListBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
Controle RadioButtonList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Sumário XIX

Controles iterativos de vinculação de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . 657


Controle Repeater . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 657
Controle DataList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662
Trabalhando com o DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
Paginando o DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668
Editando o DataGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Adicionando itens ao DataGrid. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677
Classificando o DataGrid. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 678
O formulário de solicitação de downloads e administração orientados
a banco de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 678

28 Criando Web Services 685


Termos relacionados aos Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Construção de um Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686
O atributo [WebService] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
Retornando dados a partir de um Web Service . . . . . . . . . . . . . . . . . . . . . . 692
Explicação sobre o atributo [WebMethod] . . . . . . . . . . . . . . . . . . . . . . . . . 694
Consumindo Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 695
O processo de descoberta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Construindo uma classe proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Utilizando a classe proxy. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 698
Consumindo um DataSet a partir de um Web Service . . . . . . . . . . . . . . . . 702
Invocando um método assíncrono de um Web Service . . . . . . . . . . . . . . . 704
Segurança em Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705

29 .NET Remoting e Delphi 710


Tecnologias remoting disponíveis atualmente . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Soquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
Java RMI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
XML-RPC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
DCOM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
COM-Interop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
.NET Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713
Arquiteturas distribuídas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713
Cliente/Servidor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714
Peer-to-peer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714
Multicamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715
Os benefícios do desenvolvimento de aplicações multicamada. . . . . . . . . . . . . 716
Escalabilidade e tolerância a falhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Desenvolvimento e distribuição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 717
Segurança. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
Os princípios básicos do .NET Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
XX Guia do desenvolvedor de Delphi for .NET

Visão geral da arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718


Application Domains. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
O namespace System.Runtime.Remoting . . . . . . . . . . . . . . . . . . . . . . . . . . 719
Objetos Remotable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720
Object activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722
Leases e sponsors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723
Proxies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724
Canais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724
Sua primeira aplicação .NET Remoting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724
Configurando o projeto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725
Adicionando referências . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
BankPackage.dll: comparação entre clientes e servidores . . . . . . . . . . . . . . 727
Implementando o servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
Implementando o cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733

30 .NET Remoting em ação 737


Projeto de template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737
Rastreando mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
Analisando os pacotes SOAP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740
Client Activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743
O padrão Factory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743
O exemplo em tempo de execução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750
Problemas dos CAOs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 751
Gerenciamento de Lifetime. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 751
Falha ao renovar o Lease . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754
Arquivos de configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755
Configuração do servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 757
Configuração do cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 758
Alternando entre a comunicação HTTP e TCP . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Alternando entre codificação SOAP e binária . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Diferenças entre codificação SOAP e binária . . . . . . . . . . . . . . . . . . . . . . . . . . . 766

31 Tornando aplicações ASP.NET seguras 769


Métodos de segurança do ASP.NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
Autenticação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
Configurando o modelo de autenticação do ASP.NET . . . . . . . . . . . . . . . . 770
Autenticação do Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
Autenticação baseada em formulários. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
Autenticação do Passport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Autorização . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Autorização de arquivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Autorização de URL – a seção <authorization> . . . . . . . . . . . . . . . . . . . . . . 782
Autorização baseada em papéis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783
Personificação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Finalizando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Sumário XXI

32 Distribuição e configuração do ASP.NET 787


Distribuindo aplicações ASP.NET . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 787
Considerações simples sobre distribuição . . . . . . . . . . . . . . . . . . . . . . . . . . 787
Instalação por XCOPY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Ajustes de configuração. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
O arquivo machine.config. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
O arquivo web.config . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Dicas de configuração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799
Redirecionando tratamento de erros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799
Reinicialização do Worker Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800
Armazenando saída em cache para obter desempenho . . . . . . . . . . . . . . . 802
Monitorando o processo ASP.NET. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 803
Rastreando a aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Adicionando/obtendo definições personalizadas de configuração . . . . . . . . . . 808
Adicionando e lendo <appSettings> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809
Adicionando e lendo seções personalizadas de configuração. . . . . . . . . . . 809

33 Cache e gerenciamento de estados em aplicações ASP.NET 811


Aplicações ASP.NET que usam cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811
Armazenando páginas em cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811
Armazenando fragmentos de páginas em cache . . . . . . . . . . . . . . . . . . . . . 816
Armazenando dados em cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816
Dependências do arquivo de cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 820
Estendendo dependências de arquivos para utilização no SQL Server . . . . 821
Métodos de retorno com cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823
Gerenciamento de estado em aplicações ASP.NET . . . . . . . . . . . . . . . . . . . . . . . 825
Gerenciando estados com cookies. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 825
Trabalhando com ViewState . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 828
Gerenciamento de estado de seção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 830
Armazenando dados de sessão em um servidor de estado de sessão . . . . . 832
Armazenando dados de sessão no SQL Server. . . . . . . . . . . . . . . . . . . . . . . 833
Eventos de sessão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 834
Gerenciamento de estado da aplicação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 835
Utilização de Cache versus Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 837

34 Desenvolvendo controles ASP.NET Server personalizados 839


Controles de usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 840
Um controle muito simples de usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . 840
Examinando o controle simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842
Um controle de usuário para login de usuário . . . . . . . . . . . . . . . . . . . . . . 845
Controles Web. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 848
Construindo um controle Web muito simples . . . . . . . . . . . . . . . . . . . . . . 848
Valores persistentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 851
Adicionando alguma renderização personalizada. . . . . . . . . . . . . . . . . . . . 853
XXII Guia do desenvolvedor de Delphi for .NET

Determinando o tipo de bloco HTML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855


Tratando dados post-back . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856
TPostBackInputWebControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 857
Controles compostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 862
Implementando um controle composto – TNewUserInfoControl . . . . . . . 862
O autor

Xavier Pacheco é presidente da Xapware Technologies Inc., que fundou em janeiro de


1988. Xavier e a equipe da Xapware ajudam empresas a serem bem-sucedidas no desen-
volvimento de softwares por meio do produto Active! Focus da Xapware, um conjunto
de gerenciamento para desenvolvimento de softwares em grupo. Xavier possui mais de
16 anos de experiência profissional no desenvolvimento de soluções de softwares como
sistemas distribuídos, arquiteturas de aplicação e processos e metodologias de projetos.
Xavier é desenvolvedor, autor, consultor e professor internacionalmente reconhecido.
Ele escreveu vários livros sobre Delphi, costuma escrever artigos e fazer apresentações em
conferências dessa área. Xavier e sua esposa Anne moram com seus filhos em Colorado
Springs, Colorado.

Os autores colaboradores
Steven Beebe é diretor-chefe de operações e consultor sênior na Xapware Technologies
Inc., provedor do Active! Focus, uma solução prática para gerenciar aspectos dos proje-
tos de softwares. Steve tem desenvolvido softwares e gerenciado o desenvolvimento de
softwares há mais de 15 anos. A experiência do Steve varia do gerenciamento de equi-
pes de 10 a 120 profissionais, tanto em novas empresas como em empresas na Fortune
20. Steve, sua esposa e filhas moram em Colorado Springs, Colorado.
Alessandro Federici é um membro altamente reconhecido e respeitado da comunida-
de Delphi. Em 2002, ele fundou a RemObjects Software Inc., que se estabeleceu como
uma das principais empresas na área de acesso remoto e acesso a dados com o Delphi.
Alessandro tem mais de 14 anos de experiência em programação e atualmente especiali-
za-se no projeto e desenvolvimento de sistemas e arquiteturas distribuídos. De origem
italiana (Milano), Alessandro agora mora em Hoffman Estates, Illinois, com sua esposa e
gatos.
Nick Hodges é consultor e professor Delphi e CTO (Chief Technology Officer) da Le-
manix Corporation. Nick é autor de capítulos em vários livros e de artigos em revistas,
também é palestrante freqüente na Borland Conference. Ele é membro do TeamB da
Borland e do Borland Conference Advisory Board. Nick também tem um MS em Infor-
mation Technology Management na escola Naval Postgraduate. Nick e a sua família mo-
ram em St. Paul, Minnesota.
Brian Long é professor e consultor para as comunidades de Delphi, C++Builder e .NET
e palestrante regular em conferências internacionais. Além de ser o autor, em 1994, de
um livro sobre a solução de problemas em Borland Pascal e contribuir para um livro ini-
cial sobre Delphi, Brian tem uma coluna de perguntas e repostas na Delphi Magazine e
contribui em outros periódicos e no http://bdn.borland.com. Brian foi indicado ao prêmio
Spirit of Delphi em 2000 e votado como o melhor palestrante na BorCon em maio de
2002.
XXIV Guia do desenvolvedor de Delphi for .NET

Rick Ross é consultor sênior na PILLAR Technology Group, LLC, onde ajuda clientes a
disponibilizar soluções que melhor utilizam a tecnologia. Ao longo dos seus 15 anos de
experiência profissional, Rick gosta de escrever aplicações sofisticadas como sistemas
distribuídos e aplicativos com multithread que abordam desafios de negócios específi-
cos. Rick é co-autor do Kylix 2 Development e escreve na Delphi Informant. Ele costuma
fazer apresentações em conferências em todo o mundo. Rick e a sua família moram em
Brighton, Michigan.
Steve Teixeira é CTO da Falafel Software, uma empresa de consultoria de desenvolvi-
mento de softwares. Previamente, Steve trabalhava como Director of Product Architec-
ture na Zone Labs, um importante produtor de soluções de segurança na Internet e CTO
para duas outras empresas de consultoria de software. Como engenheiro de pesquisa e
desenvolvimento de softwares na Borland, Steve foi um participante importante no de-
senvolvimento do Delphi e do C++Builder. Steve escreveu cinco livros premiados e nu-
merosos artigos em revistas sobre o desenvolvimento de softwares e é palestrante fre-
qüente em eventos mundiais da indústria.

O editor técnico
Hallvard Vassbotn é programador veterano em Delphi que mora em Oslo, Noruega.
Trabalha como desenvolvedor sênior de sistemas na Infront AS, onde desenvolve aplica-
ções para informações financeiras em tempo real pela Internet, visite http://www.theonli-
netrader.com/. Hallvard escreveu numerosos artigos na Delphi Magazine. Quando não está
dominando o Delphi ou o FCL, ele passa o tempo com suas três maravilhosas filhas.
Dedicatória
Este livro é dedicado aos meus filhos, Amanda, Zachary e Jessica.
É por meio deles que sou lembrado do amor infinito do meu Pai celestial;
apesar das minhas impropriedades, Ele confiou essas preciosidades ao
meu cuidado.
Segundo, este livro é para os homens e mulheres voluntários do serviço
das forças armadas norte-americanas, sabendo que eles podem, um dia,
ter de fazer o sacrifício final de modo que possamos gozar integralmente
nossas liberdades. Não merecemos o que vocês fazem por nós.

Agradecimentos
Esta é a minha parte favorita do trabalho de escrever um livro porque é onde consigo
agradecer as pessoas que REALMENTE o tornaram possível. Algumas dessas pessoas fize-
ram parte direta do próprio trabalho. Outras foram defensoras e encorajadoras. Ambas
merecem mais do que meras menções. Ambas têm minha profunda gratidão e respeito.
Invariavelmente, por causa da minha memória imperfeita, esqueço de mencionar
pelo nome muitos que contribuíram com suas cotas de trabalho duro e suporte. Perdoem-
me pelo meu descuido e aceitem meu cordial obrigado.
Quando escrevi meu primeiro livro, Delphi 1 Developer’s Guide, minha esposa Anne
lembra-se de ter ido dormir e despertado ao som da digitação. Desta vez não foi muito di-
ferente. Anne recebeu pouca atenção da minha parte, mas retribui com extrema atenção.
Eu não conversava muito, mas ela sim, com Deus, continuamente orando por mim. Ela
sempre me alegrou e encorajou a escrever o melhor que pudesse. Só posso esperar viver
de acordo com o exemplo de amor em Cristo que ela me mostrou. Este livro existe por
causa dela.
Obrigado, Michael Stephens, por assumir este projeto e entregá-lo a uma equipe ex-
traordinária a qual também agradeço. As pessoas nesta equipe são Loretta Yates, Songlin
Qiu, George Nedeff, Rhonda Tinch-Mize, Dan Sherf e as várias outras na Sams que eu
nunca conheci, mas cujo excelente trabalho posso ver no produto final.
Sou imensamente grato aos autores colaboradores cujos excelentes talentos e perícias
refletem-se nos capítulos que eles escreveram. Estes são Steven Beebe, Alessandro Federi-
ci, Brian Long, Nick Hodges, Rick Ross, Steve Teixeira e Hallvard Vassbotn. Hallvard me-
rece uma menção especial. Hallvard foi o revisor técnico deste livro, mas ele fez muito
mais do que isso. Hallvard dedicou muitas horas para assegurar que este livro tivesse a
mais alta qualidade técnica. Você encontrará seus textos e códigos em cada capítulo –
muitos dos quais simplesmente copiei e colei diretamente dos seus comentários.
Escrever este livro demandou esforços; houve aqueles que apoiaram minha família e
a mim neste momento. Primeiro, agradeço ao bom amigo e meu irmão Steven Beebe, sua
esposa Diane e seus filhos Hannah e Sarah. Obrigado pela ajuda quando Jessica nasceu;
XXVI Guia do desenvolvedor de Delphi for .NET

obrigado por sempre estarem disponíveis, flexíveis, dispostos e alegres quando precisei
de apoio. Também quero agradecer a meu sogro Bob, que ajudou em tudo nos primeiros
dias da Jessica, possibilitando algum tempo para que eu trabalhasse neste livro. Agradeço
ao reverendo Joseph Wheat pela sua liderança, sabedoria e pelas caminhadas às quar-
tas-feiras pela manhã. Muito obrigado a Bruce Beebe, Paul Adams, ao reverendo John
Rantal e Doug McIntire, meus irmãos de oração e conselheiros. Agradeço aos músicos,
escritores e todos envolvidos no grupo Indelible Grace (www.igracemusic.com) cuja música
escutei ao escrever este livro.
Acima de tudo, agradeço Àquele a quem devo minha vida, Jesus Cristo – meu Se-
nhor, meu Salvador e meu Deus. Você me deu tudo que tenho – minha esposa, filhos,
trabalho, habilidades e minha paixão em programação e para escrever livros como este.
Você me abençoou com prosperidade e provações, fielmente me refinando e amando.
Obrigado pelo Seu presente da vida eterna – Meu Deus; meu Rei!

Soli Deo Gloria!


Introdução

Guia do desenvolvedor de Delphi for .NET é um livro sobre desenvolvimento em .NET e,


especificamente, com o Delphi. Este livro não discute o conteúdo abrangido nos li-
vros Delphi Developer’s Guide anteriores. Com a exceção do Capítulo 5, todo o mate-
rial é novo. Neste livro, também escolhi uma abordagem um pouco diferente dos
meus outros trabalhos. Aqui, você encontrará mais exemplos que ilustram alguns
conceitos ou conceitos únicos em vez de listagens maiores que combinam conceitos.
Será mais fácil para você examinar a técnica sem ter de examinar o código não-rela-
cionado.
Além disso, intencionalmente, evitei o máximo possível a utilização de recursos das
IDEs. Por exemplo, nos capítulos sobre o ADO.NET, você verá que configurei as proprie-
dades programaticamente. Fiz isso para que você possa ver quais propriedades são utili-
zadas e evitar ter de escrever instruções sobre a usabilidade da IDE que terminam confun-
dindo o texto da técnica real. Entretanto, como a utilização da IDE é importante para a
produtividade do desenvolvimento, o Capítulo 3 destaca várias capacidades das IDEs
que ajudam você a tornar-se produtivo ao desenvolver. Essencialmente, meu objetivo foi
escrever um livro especificamente sobre o .NET orientado a técnicas, utilizando o Delphi,
em vez de um livro sobre Delphi for .NET.

Quem deve ler este livro


Como o título do livro informa, este livro é para desenvolvedores e, especificamente, de-
senvolvedores em .NET utilizando o Delphi. Em particular, este livro tem por objetivo
três grupos de pessoas:
— Desenvolvedores profissionais em Delphi que estão pensando em levar suas habi-
lidades para o próximo nível e começar a ter por alvo o .NET.
— Desenvolvedores em .NET que utilizam uma outra linguagem e querem aprender
sobre como o Delphi faz o .NET.
— Programadores iniciantes que querem aprender o .NET e desejam fazer isso utili-
zando uma poderosa linguagem intuitiva.

Como este livro está organizado


O Guia do desenvolvedor de Delphi for .NET é dividido em cinco partes:
— A Parte I fornece alguns capítulos abrangentes sobre o .NET em geral e sobre as es-
pecificidades do Framework.
— A Parte II aborda as especificidades do Delphi for .NET, a IDE e muitos aspectos
sobre a linguagem Delphi.
XXVIII Guia do desenvolvedor de Delphi for .NET

— A Parte III discute os conceitos básicos ao desenvolver na .NET Framework. Por


exemplo, nesta seção você encontrará capítulos sobre threading, reflection, strea-
ming, gerenciamento de memória e assim por diante.
— A Parte IV entra nos detalhes da utilização do Delphi juntamente com o ADO.NET
a fim de criar aplicações de banco de dados.
— A Parte V aprofunda-se nas várias técnicas para criar aplicações Web utilizando o
ASP.NET.

O que está no CD-ROM que acompanha este livro


Você encontrará todos os códigos-fonte e arquivos de projeto no CD-ROM que acompa-
nha este livro, bem como exemplos de códigos-fonte que não pudemos inserir no pró-
prio livro. O diretório original é dividido por capítulo e mais ainda como, por exemplo

\Code\Chapter 01\Ex01
\Code\Chapter 01\Ex02
\Code\Chapter 02\Ex01
...

Dentro dos capítulos e seguindo cada listagem, você encontrará uma referência que
mostra onde localizar a origem da listagem no CD.
No CD, você também encontrará o livro Delphi 6 Developer’s Guide completo com ar-
quivo .pdf individuais para cada capítulo. Você encontrará isso sob a pasta \D6DG. O códi-
go para o Delphi 6 Developers Guide também foi incluído. Em essência, você tem dois li-
vros em um. O código está localizado na pasta \D6DG\Code.
Por fim, você encontrará várias demos de ferramentas e componentes de terceiros
que poderá utilizar ao desenvolver em aplicações Delphi .NET.

Convenções utilizadas neste livro


As seguintes convenções tipográficas são utilizadas neste livro:
— Linhas de código, comandos, instruções, variáveis, saída de programa e qualquer
texto que você vê na tela aparecem em uma fonte com linhas numeradas para re-
ferência fácil.
— Qualquer coisa que você digite aparece em uma fonte em negrito.

— Os marcadores de lugar nas descrições de sintaxe aparecem em uma fonte em itá-


lico. Substitua o marcador de lugar pelo nome do arquivo real, parâmetro ou
qualquer que seja o elemento que ele representa.
— O negrito destaca termos técnicos quando eles aparecem no texto pela primeira
vez e o itálico é às vezes utilizado para enfatizar pontos importantes.
— Procedures e funções são indicadas por parênteses depois do nome da procedure
ou função. Embora essa não seja a sintaxe Pascal padrão, ajuda a diferenciá-las das
propriedades, variáveis e tipos.
Introdução XXIX

Dentro de cada capítulo, você encontrará várias Notas, Dicas e notas de Atenção que
ajudam a destacar os pontos importantes e a permanecer longe das armadilhas.

O site Web Delphi for .NET Developer’s Guide


O site Web deste livro está localizado em www.DelphiGuru.com, onde você encontrará atuali-
zações e informações extras sobre erratas deste livro. Você também encontrará um índice
de tópicos para as várias listagens do livro e exemplos de código.
Página em branco
Prefácio à edição em português

por FERNANDO VASCONCELOS MENDES


Revisor técnico

Era começo de tarde de uma sexta-feira qualquer, volta do almoço, hora de ler os e-mails
e apagar as dezenas de spams normalmente recebidos. Eis que um dos emails, quase apa-
gado por engano, trazia-me um convite do grupo Pearson para ser o revisor técnico de
tradução. Tal foi minha surpresa quando soube que, este livro que o leitor tem em mãos,
seria a obra alvo desse trabalho. Na minha opinião, esta obra figura entre as melhores e
mais bem-sucedidas séries sobre Delphi já publicada.
Pois então, chegou o .NET, avassaladoramente, como qualquer revolução. Um mundo
completamente novo foi entregue aos programadores da plataforma Win32. Uma enorme
gama de conceitos emergiu dentro de uma nova filosofia de projeto, desenvolvimento e
distribuição de software. Os Web Services, os dispositivos móveis, a programação RAD
para Web, as soluções distribuídas e desconectadas são alguns dos horizontes mais
amplamente divulgados dentro da plataforma .NET. E, o que é melhor, tudo isso de
maneira eficiente e produtiva.
O lançamento do Delphi for .NET capacitou toda a comunidade Delphi espalhada
pelo mundo a entrar na plataforma .NET. Com isso, vem a necessidade de atualização
das empresas e suas equipes, é preciso preparação para atuar nos novos mercados. Há
uma demanda crescente por soluções modernas e descentralizadas, processos estabeleci-
dos e bem implantados e profissionais capacitados, é justamente aí que entra este livro.
Xavier Pacheco, junto aos demais autores que contribuíram, fez um excelente traba-
lho ao planejar e escrever este livro. Nele podemos encontrar praticamente todos os
principais tópicos relacionados ao desenvolvimento de aplicações comerciais. Alguns
paralelos com a programação Delphi for Win32 são traçados, mas de forma sutil, sem
comprometer a fluência da leitura.
O livro traz uma visão geral da plataforma .NET, bem como as alterações no Delphi
decorrentes do suporte a essa nova arquitetura. O novo framework para aplicações GUI –
o Windows Forms – é apresentado em detalhes, além da já tradicional VCL, que agora
em sua versão .NET, chama-se VCL for .NET.
.NET Remoting, Web Services, ASP.NET e ADO.NET são tópicos extensivamente
cobertos com bastante clareza, mesmo em seus aspectos mais complexos. Também
contamos com uma ampla cobertura sobre GDI+, expondo as novidades e poderosos
recursos dessa nova camada gráfica.
Dotado dessas qualidades, a que se soma um excelente conteúdo técnico, este livro
torna-se peça fundamental para qualquer profissional que deseja manter-se em sintonia
com as novas tendências. Ele certamente será muito últil tanto para profissionais inician-
tes como para os mais experientes.
XXXII Guia do desenvolvedor de Delphi for .NET

Na qualidade revisor técnico, tive de tomar algumas decisões. Como trata-se de um


tema relativamente novo, mais ainda dentro da comunidade Delphi, alguns termos fo-
ram mais complicados para optar entre traduzir e deixá-los em inglês. Na regra geral, dei-
xei em inglês os nomes que referenciavam entidades da arquitetura .NET e não tinham
uma equivalência consolidada em português. Para deixar a tradução o mais agradável
possível, algumas vezes fiz enquetes junto a colegas profissionais da área sobre a suavida-
de de certos termos. Mas como este é um livro pioneiro, é possível que alguns termos aca-
bem sendo amplamente traduzidos para o português no futuro, já outros deverão se
manter sempre em inglês.
Por fim, parabenizo você leitor por esta aquisição e desejo que a leitura dela seja para
você tão agradável quanto foi para mim.
PARTE I:
CAPÍTULO 1 INTRODUÇÃO AO .NET
FRAMEWORK

Introdução ao .NET 1 Introdução ao .NET

2 Visão geral sobre o .NET


Framework

A Internet está se tornando cada vez mais uma parte NESTE CAPÍTULO
fundamental de cada aspecto de nossas vidas. Os
— A iniciativa .NET
negócios contam com ela. A educação desenvolve-se
por meio dela e nossas vidas pessoais são simplificadas — Desenvolvimento distribuído
por ela. Sites Web modernos não são apenas por meio de Web Services
informativos, mas altamente interativos.
Podemos facilmente planejar uma viagem, comprar
uma passagem aérea, alugar um quarto de hotel e obter
informações sobre eventos e pontos turísticos em
questão de minutos. Podemos até mesmo obter análises
sobre vários negócios, serviços e locais.
Agora, as empresas podem alcançar e vender para
mercados mundiais mais amplos. A Internet é
provavelmente a fonte mais substancial de informações
atualizadas em tempo real, cruciais para aqueles que são
responsáveis por decisões de negócio.
Com toda essa capacidade, talvez pareça irritante
inferir que as coisas poderiam melhorar. O fato é que a
Internet ainda é uma miscelânea de tecnologias não
cooperantes entre si. Quanto a Internet poderia
melhorar se os serviços obtidos por meio dela pudessem
funcionar conjuntamente no geral, aumentando a
experiência e produtividade do usuário final?
Talvez, por exemplo, seus planos de viagem possam
incluir recomendações sobre restaurantes com base nas
suas preferências pessoais. A partir disso, você poderia
reservar sua mesa imediatamente. Suponha que você
esteja em uma lista de espera de um vôo melhor. Você
poderia receber um alerta por meio do seu dispositivo
móvel, celular ou PDA, de modo que pudesse fazer
rapidamente quaisquer ajustes à sua agenda – em tempo
real. Como um usuário do negócio, você não apenas
poderia querer saber quem tem os preços mais baixos
para um item particular, mas também quando um
fornecedor oferece um item a um custo mais baixo sem
realizar uma pesquisa manual.
2 Capítulo 1 Introdução ao .NET

A verdade é que, tecnicamente, essa capacidade existe hoje em dia. Realmente é ape-
nas uma questão de esses serviços serem desenvolvidos e consumidos, de modo que eles
possam ser utilizados por toda a comunidade.

A iniciativa .NET
De acordo com a Microsoft,

“O .NET é a solução da Microsoft para Web Services, a próxima geração de software que conecta
nosso mundo de informações, dispositivos e pessoas de uma maneira unificada e personalizada”.

Quando a Microsoft começou a falar sobre o .NET (e mesmo depois do seu lança-
mento), havia uma grande confusão sobre o que ele era exatamente. As pessoas não
tinham certeza se o .NET era um novo framework, um sistema operacional ou uma lin-
guagem de programação. Na realidade, o .NET é uma marca da visão da Microsoft para o
desenvolvimento distribuído baseado na sua nova tecnologia. Chamamos essa tecnolo-
gia de .NET Framework e é por meio dela que desenvolvemos Web Services.
Embora pareça que construir sistemas distribuídos na forma de Web Services seja o
principal objetivo do .NET, há muito mais na iniciativa .NET. Nas seções a seguir, tentei
esclarecer e fornecer uma visão geral de alto nível sobre o .NET e o que ele significa para
nós, desenvolvedores, e para os usuários finais dos sistemas que desenvolvemos.

A visão .NET
Certamente, há muitos objetivos na iniciativa .NET. Os objetivos a seguir parecem ser
mais proeminentes na Microsoft e em outras distribuições e eles serão abordados em de-
talhes:
— Desenvolvimento mais rápido e mais fácil

— Distribuição simplificada das aplicações

— Acesso a serviços e dados a qualquer momento e em qualquer lugar

— Aplicações colaborativas

Desenvolvimento mais rápido e mais fácil


Para os desenvolvedores, o .NET é uma nova e animadora plataforma de desenvolvimen-
to que supera muitas limitações das plataformas existentes. Em geral, essa nova plataforma
aumenta a produtividade do desenvolvimento ajudando os desenvolvedores a se
tornarem mais eficientes. Em outras palavras, o desenvolvimento tornou-se mais fácil,
permitindo aos desenvolvedores agir mais rapidamente. Essa nova plataforma é chama-
da .NET Framework.
O .NET Framework inclui um rico e extensível conjunto de classes que oferece aos
desenvolvedores várias ferramentas poderosas de desenvolvimento. Essas classes torna-
ram disponíveis desde funções de sistema de baixo nível até elegantes controles de inter-
face com o usuário.
A iniciativa .NET 3

O .NET Framework vai, porém, muito além de bibliotecas de classe. Seus componen-
tes centrais permitem aos desenvolvedores programar para quaisquer dispositivos utili-
zando qualquer linguagem que atenda à especificação do .NET. Ele libera o desenvolvedor
da preocupação com tarefas subjacentes como gerenciamento de memória e, com algumas
linguagens .NET, segurança de tipo. Ele permite que o desenvolvedor se concentre na
solução de desenvolvimento. Nenhuma outra plataforma torna tão fácil compartilhar o
código entre linguagens. Por exemplo, os desenvolvedores não terão mais de converter
manualmente arquivos de cabeçalho para utilizar tipos definidos em uma outra lingua-
gem.
O Microsoft .NET também é suportado por mais de 20 linguagens, incluindo, natu-
ralmente, a linguagem Delphi (Delphi Language), também conhecida como Object Pas-
cal, que este livro aborda.

Distribuição simplificada das aplicações


Qualquer pessoa que tenha lido sobre o .NET viu ou ouviu a expressão “Inferno da DLL”.
Qualquer um que desenvolva softwares para o Windows sabe exatamente o que essas
pessoas estão falando. Para alguns, como eu, inferno é uma viagem a um shopping cen-
ter; para outros, nada pode invocar o terror tal qual descobrir que aplicações importan-
tes, ou o próprio Windows, não conseguem executar depois que uma nova aplicação foi
instalada no seu sistema.
O Windows, assim como as aplicações que executam nele, utiliza e distribui as
Dynamic Link Libraries (DLLs). Outras aplicações utilizam freqüentemente funções que
residem nessas DLLs. Os problemas ocorrem quando um usuário instala ou atualiza uma
aplicação que sobrescreve uma DLL utilizada por essas outras aplicações. Se a nova fun-
cionalidade da DLL for diferente da original, outras aplicações poderão não mais se com-
portar da maneira esperada.
Um outro ponto do inferno da DLL é escrever e manter programas de instalação para
o Windows. Conheço isso de primeira mão, tendo escrito programas de instalação e ten-
do de lidar com o labirinto das interdependências do Windows. Instalar DLLs servidoras
e lidar com o registro e a segurança de usuário, tanto em computadores pessoais como
em computadores dentro de um domínio de uma rede, são apenas algumas das batalhas
com as quais você precisa lidar. Então, quando sua instalação finalmente estiver funcio-
nando em uma instalação limpa do Windows, em execução sob uma máquina virtual
(que normalmente não corresponde ao sistema de ninguém), será necessário escrever
um programa de desinstalação para tentar desfazer a confusão que você criou. Os progra-
mas de instalação convencionais afirmam que podem resolver esse problema; entretan-
to, eles não fornecem o meio de realizar rotinas de baixo nível que você, de qualquer ma-
neira, acaba tendo de codificar.
Com aplicações cuspindo pedaços de si mesmas por todo seu sistema, é natural que
o Windows e as aplicações que nele encontram-se executando tornem-se instáveis ao
longo do tempo e tenham uma reputação de serem… falhas.
O Microsoft .NET tenta resolver esse problema mudando a maneira de lidar com a
distribuição de aplicações .NET. Esse é um outro recurso do .NET Framework que discuti-
remos na Parte III.
4 Capítulo 1 Introdução ao .NET

Acesso a serviços e dados a qualquer momento e em qualquer lugar


Os Web Services são o componente do .NET com o qual a Microsoft, e outras empresas,
afirmam que irão revolucionar a maneira como os desenvolvedores criam soluções e como
os usuários adquirem e utilizam essas soluções. Com a realidade dos poderosos computa-
dores pessoais e outros dispositivos e a perspectiva de acesso onipresente à Internet, essas
afirmações não estão longe de serem alcançadas. Três elementos dividem essa idéia de “a
qualquer momento, em qualquer lugar”. Primeiro, a Internet sempre está acessível. Segun-
do, os Web Services fornecem funcionalidades via a Internet. Terceiro, os Web Services po-
dem ter por alvo qualquer dispositivo: um computador pessoal, uma agenda de mão, um te-
lefone celular ou qualquer outro dispositivo projetado para funcionar com os Web Services.
Em geral, os desenvolvedores podem criar soluções incorporando as funcionalida-
des existentes disponibilizadas pelos Web Services. Portanto, em vez de escrever ou com-
prar um pacote de cálculos contábeis, os desenvolvedores podem utilizar um que existe
na Internet. Por exemplo, sem a necessidade de programar essas e outras funções em um
pacote de folha de pagamento on-line, os desenvolvedores podem focalizar mais a solu-
ção do negócio e menos o funcionamento interno para implementá-lo. O 28 discute a
construção de Web Services com o Delphi for .NET.

Aplicações colaborativas
Os Web Services permitem às organizações continuar a utilização de seus serviços auto-
matizados existentes que atualmente não podem se comunicar entre si. Como os Web
Services estão baseados na tecnologia XML, eles podem ser utilizados para integrar esses
sistemas proprietários. Em muitos sistemas, a abordagem típica para integração envolve
a exportação de dados para um arquivo legível pelo código personalizado utilizado na
importação para um outro sistema. Fornecendo uma camada colaborativa por meio dos
Web Services/XML, a natureza proprietária dos sistemas pode ser contida e, ao mesmo
tempo, expor suas funcionalidades. Outros sistemas podem atrelar essa funcionalidade,
permitindo que organizações criem soluções de negócios completas por toda a empresa.

Os componentes do .NET Framework – o Common


Language Runtime e as bibliotecas de classe
O .NET Framework é a tecnologia central para construção e execução de aplicações. Ela
consiste principalmente em dois componentes: o Common Language Runtime e uma
biblioteca de classes.

NOTA
Seria impossível abranger todo o .NET Framework neste livro. Entretanto, você verá os elemen-
tos-chave do Framework discutidos nos vários capítulos deste livro. A Microsoft tem uma docu-
mentação completa on-line sobre o .NET Framework disponível em
http://msdn.microsoft.com/library/

— Common Language Runtime (CLR) – O CLR é um ambiente de tempo de execu-


ção (runtime) que fornece os serviços necessários a todas as aplicações .NET.
Alguns desses serviços incluem compilação, gerenciamento de memória e garba-
A iniciativa .NET 5

ge collection (coleta de lixo). O CLR também impõe um sistema comum de tipos.


Tendo isso dentro do CLR, ele pode trabalhar com aplicações escritas em qualquer
linguagem .NET. De fato, no momento em que o CLR tiver de lidar com o código,
ele já terá sido compilado para uma linguagem intermediária (Intermediate Lan-
guage – IL) genérica e comum. Qualquer compilador compatível com CLR pode ge-
rar o código IL. Discutiremos esse processo em mais detalhes no Capítulo 2.
— Biblioteca de classes – O .NET Framework inclui um rico conjunto de assemblies
compondo sua biblioteca de classes. Esses assemblies contêm vários tipos cuja
funcionalidade está disponível para os desenvolvedores e com a qual eles podem
desenvolver quaisquer aplicações listadas na seção a seguir. Algumas dessas tec-
nologias de que você provavelmente já ouviu falar são ASP.NET e ADO.NET. Ago-
ra a Borland, com o Delphi for .NET, introduziu a VCL for .NET, que estende o
componente de interface com o usuário do .NET Framework e torna a migração
de aplicações Delphi já existentes mais rápida e mais fácil.

NOTA
A VCL for .NET é a Visual Component Library (VCL) original das versões anteriores do Delphi porta-
da para a plataforma .NET. A Borland portou a VCL para tornar o desenvolvimento de aplicações
.NET mais fácil aos atuais desenvolvedores em Delphi, uma vez que eles serão capazes de criar e
executar com o .NET utilizando todas as classes com as quais já estão familiarizados.

Tipos de aplicações .NET


Você pode criar vários tipos de aplicações com o .NET Framework: aplicações Web
Forms, Windows Forms, Web Services e aplicações console. As seções a seguir discutirão
cada uma delas.

Web Forms (ASP.NET)


Aplicações Web Form são aplicações cuja GUI está baseada em HTML dinamicamente
gerado. Normalmente, o HTML mostrado seria gerado por meio de consultas complexas
obtidas de servidores de bancos de dados ou outras fontes. O ASP.NET é a tecnologia da
Microsoft baseada em servidor (server based), que permite desenvolver páginas Web di-
nâmicas e interativas. A Parte V deste livro ilustra como tirar proveito do ASP.NET utili-
zando o Delphi for .NET.

Windows Forms
Quando são necessária sinterfaces com o usuário altamente funcionais, o .NET forne-
ce as poderosas capacidades do ambiente desktop do Windows. Os desenvolvedores
podem separar os dados e as regras de negócios da layer (camada) de apresentação.
Muitos dos exemplos utilizados nos vários capítulos deste livro demonstram o uso co-
mum de Windows Forms. O 13 discute especificamente os Windows Forms e também
aborda componentes VCL for .NET da Borland para a criação de aplicações Windows
Forms.
6 Capítulo 1 Introdução ao .NET

Aplicações Web Services


Em termos simples, os Web Services são chamadas de procedimento remoto feitas na
Internet utilizando XML como o formato de mensagem de comunicação e HTTP/HTTPS
como o meio de comunicação. Um Web Service pode incluir funcionalidades específicas
utilizadas ao criar uma aplicação ou pode incluir uma aplicação inteira. De qualquer ma-
neira, os Web Services são considerados aplicações em si e por si sós. Discutiremos os
Web Services um pouco mais neste e por todo este livro.

Aplicações console
Ocasionalmente, os desenvolvedores têm a necessidade de escrever aplicações/utilitários
simples que não requerem nenhuma interface gráfica/ interativa com o usuário. Nesse
caso, você pode desenvolver aplicações console sob o .NET, ou usando o Delphi for .NET,
pode ainda recorrer ao compilador Win32 que irá gerar executáveis nativos não baseados
no .NET Framework ou no CLR.

Aplicações Windows Service


Aplicações Windows Service são aplicações que executam durante um longo período de
tempo. Elas também executam quando ninguém está conectado ao computador e são
iniciadas quando o computador inicializa. É possível criar essas aplicações no .NET.

Componentes .NET
O .NET Framework contém componentes que você pode utilizar ao construir quaisquer ti-
pos de aplicação anteriormente citados. Além disso, você, como um desenvolvedor, pode
estender o .NET Framework escrevendo seus próprios componentes reutilizáveis, permi-
tindo padronizar determinadas funcionalidades por toda sua organização. Você também
pode vender seus componentes à comunidade .NET como vários fornecedores fazem atu-
almente. Embora os componentes não sejam especificamente considerados aplicações,
eles são um produto final criado quase como aplicações por desenvolvedores .NET.

NOTA
Muitos componentes de terceiros estendem os componentes existentes já disponíveis no .NET
Framework. Você pode realizar uma pesquisa no google.com sobre “.NET Components” ou pes-
quisar o CD que acompanha este livro, que contém muitos produtos desses fornecedores.

Explicação sobre a VCL for .NET


Quando a Borland aderiu a essa tendência vitoriosa do .NET, uma das decisões que a em-
presa tomou foi suportar sua base de usuários existente no Delphi ao migrar para o .NET.
Ela apresentou uma implementação no .NET da Visual Component Library. A VCL for
.NET pode ser utilizada de maneira vantajosa em pelo menos três áreas. Na primeira, ela
pode servir como um meio de ajudar os desenvolvedores em Delphi existentes a apren-
derem o .NET. Na segunda, pode ser utilizada para ajudá-los a portar aplicações existen-
tes baseadas em VCL para o .NET Framework. Por fim, é possível escrever uma aplicação
de origem única que tem por alvo o Win32, .NET e possivelmente o Linux (utilizando
Kylix e CLX). O Capítulo 13 discute a VCL for .NET em mais detalhes.
Desenvolvimento distribuído por meio de Web Services 7

Desenvolvimento distribuído por


meio de Web Services
A idéia por trás dos Web Services não é nova. Desde que trabalhamos com computação
distribuída, tem sido benéfico dividir os componentes funcionais dos sistemas. Isso permi-
te melhor distribuição de cargas de trabalho, escalabilidade, segurança e assim por diante.
Muitos modelos de desenvolvimento distribuído ainda são baseados em sistemas fun-
cionais totalmente integrados e fechados. A integração de sistemas continua a ser terrivel-
mente problemática, no entanto, a necessidade é significativa. Um exemplo seria a integra-
ção de sistemas de contabilidade de larga escala a um sistema de processo de manufatura.
Por meio dos Web Services, componentes funcionais (que daqui para frente chama-
remos serviços), são expostos a vários consumidores. Um consumidor, por exemplo, po-
deria ser um site Web por meio do qual uma pessoa pode planejar suas férias. Esse site
Web consumiria serviços fornecidos por outros provedores de serviço. Por exemplo, ele
poderia utilizar o MapPoint Web Service da Microsoft para obter mapas e orientações, o
serviço de reserva de restaurantes de uma outra pessoa e mesmo outros serviços para
vôos, táxis e assim por diante.
Os consumidores não precisam ser sites Web. A capacidade de computação por todo
o sistema de uma empresa pode ser integrada por meio dos serviços. As equipes de ven-
das poderiam ter acesso a um sistema CRM em uma interface Web. Esse mesmo sistema
CRM poderia ser integrado ao sistema financeiro do departamento de contabilidade com
o qual os usuários trabalham em uma aplicação Web Forms. Esse sistema financeiro po-
deria funcionar, nos bastidores, com um sistema separado de controle de inventário por
meio de outros Web Services etc.
Os elementos básicos dos Web Services são os próprios serviços, clientes, ferramen-
tas de desenvolvimentos e servidores.

Definição de Web Services


Os Web Services são aplicações discretas, desacopladas e reutilizáveis que expõem suas
funcionalidades por meio de uma interface Web. Por interface Web, não tenho em men-
te uma página Web. Em vez disso, Web Services se comunicam com o mundo externo
por meio de chamadas de procedimento remoto (Remote Procedure Calls – RPC) por um
protocolo como o HTTP. A forma de comunicação é a Extensible Markup Language
(XML) e, mais especificamente, o Simple Object Access Protocol (SOAP).
Essa idéia de RPC por meio da Internet não tem nada de novo. Mas o que torna os
Web Services inovadores é a idéia de que qualquer cliente e qualquer servidor podem uti-
lizar esses serviços independentemente da linguagem de implementação e do dispositi-
vo em que eles residem. Um Web Service pode ser desenvolvido em Delphi for .NET e
pode disponibilizar a funcionalidade residente em um servidor para clientes escritos em
C#, Perl ou dispositivos WAP móveis. Os Web Services são sistemas verdadeiramente de-
sacoplados e diversificados, diferentemente de outras tecnologias distribuídas.
Os Web Services permitem que aplicações compartilhem dados e funcionalidades de
uma maneira revolucionária pelo fato de que não importa como essas aplicações discre-
tas foram implementadas ou em qual plataforma, SO ou dispositivo elas residem.
8 Capítulo 1 Introdução ao .NET

Universalidade dos Web Services


Utilizando protocolos padrão da indústria, os Web Services são universais quando ex-
postos na Internet. Estes incluem XML, SOAP e Universal Description, Discovery e Inte-
gration (UDDI). Esses padrões são os definidos pelo World Wide Web Consortium
(W3C).

NOTA
O World Wide Consortium (W3C) é uma agência reguladora sem fins lucrativos baseada em qua-
dros de associados para definição e desenvolvimento de tecnologias (especificações, diretrizes,
softwares e ferramentas) relevantes ao uso da World Wide Web. Informações sobre o W3C podem
ser obtidas no site Web em www.w3c.org.

Você deve conhecer os seguintes termos relacionados aos Web Services utilizados
por todo este livro:
— XML – Extensible Markup Language. XML é um formato flexível, baseado em tex-
to, originalmente derivado da SGML para o propósito de publicação por meios
eletrônicos. A riqueza e a autodefinição do formato da XML a tornam ideal para
utilização na passagem de mensagens entre consumidores do Web Service.
— SOAP – Simple Object Access Protocol. SOAP é o protocolo para Web Service base-
ado no padrão XML para invocar chamadas de procedimento remoto pela Inter-
net/intranet. SOAP especifica o formato da solicitação e o formato dos parâmetros
passados na solicitação. SOAP só é específico à mensagem e não impõe nenhum
requisito sobre a implementação do serviço Web ou do seu consumidor.
— WSDL – Web Service Description Language. WSDL é uma linguagem baseada em
XML utilizada para descrever os recursos de um Web Service. Ela inclui todos os
vários métodos e seus parâmetros, bem como a localização do Web Service. Os
consumidores de um Web Service podem entender a linguagem WSDL e podem
determinar a funcionalidade fornecida pelo Web Service.
— UDDI – Universal Description, Discovery, and Integration. O UDDI é um registro
público para armazenamento de informações sobre e para publicação de Web Ser-
vices. Você pode visitar o UDDI em www.uddi.org.

Os benefícios da comunicação dos Web Services


Os Web Services oferecem um componente de ligação para aplicações desconectadas
permitindo uma melhor integração desses sistemas discretos.
Essa integração permite que organizações integrem seus sistemas internos de modo
muito mais transparente. Utilizando os benefícios do .NET como independência de lin-
guagem e plataforma, os negócios podem focalizar menos a implementação e as comple-
xidades da integração e mais as informações e funcionalidades que fazem sentido
integrar. Qualquer pessoa que já participou da integração de sistemas entende as dificul-
dades de lidar com diferentes formatos de dados entre sistemas. Blindando os detalhes
da implementação e definição desses sistemas com um meio comum de comunicação,
essas questões podem tornar-se transparentes.
Desenvolvimento distribuído por meio de Web Services 9

As organizações também podem se beneficiar integrando recursos externos. Pense


em uma empresa de software que desenvolve e vende seus próprios produtos. Em vez de
adquirir ou construir um sistema de gerenciamento de questões e que se integra ao seu
pacote CRM, a empresa poderia encontrar um recurso que forneça essa integração por
meio dos Web Services.
Os Web Services também se integram a clientes. Os clientes tornam-se consumido-
res do Web Service, por exemplo, quando começam a utilizar os serviços de Internet que
utilizam os Web Services. O exemplo do site de planejamento de viagem mencionado
anteriormente é um exemplo do que isso poderia ser.

Clientes Web Services


Para entender como aplicações se comunicam via Web Services, examine a Figura 1.1.
Na Figura 1.1, você vê clientes conectando-se a Web Services que estão conectados a
servidores. No caso de um cliente que se conecta a um Web Service para obter dados a
partir de um servidor, o cliente é o consumidor do Web Service. Também observe que
um servidor pode utilizar um Web Service para acessar dados de outro servidor. Isso tam-

Computador
PDA

Web
Service

Computador

Web
Web Service
Telefone celular Service

Servidor Servidor

Servidor
FIGURA 1.1 Topologia dos Web Services.
10 Capítulo 1 Introdução ao .NET

bém torna o servidor que invoca um consumidor. Por fim, os Web Services podem utili-
zar outros Web Services. Embora isso ilustre um possível layout de um sistema, não ilus-
tra como alguns desses Web Services podem ser internos enquanto outros são externos.
Conseqüentemente, a disponibilidade dos Web Services permitirá criar um sistema de
negócios altamente complexo.

NOTA
A Microsoft introduziu a noção dos Smart Clients. Smart Clients são produtos de softwares que uti-
lizam intensamente as tecnologias do Web Service tanto do ponto de vista da funcionalidade
como da distribuição. Por exemplo, um produto Smart Client poderia invocar uma aplicação de
calendário/agenda, gerenciamento de contatos e gerenciamento de correio eletrônico a partir de
três Web Services diferentes. Ele também poderia realizar auto-atualizações a partir de um servidor
centralizado e ainda ter a capacidade de funcionar off-line. Tudo isso está previsto para ser inde-
pendente de dispositivo de modo que funcione em um PDA e com um computador desktop.

Ferramentas de desenvolvimento de Web Services


O Delphi for .NET é uma das ferramentas que os desenvolvedores podem utilizar para de-
senvolver Web Services. O 28 discute como utilizar o Delphi for .NET a fim de desenvolver
tanto aplicações Web Services como aplicações que consomem esses serviços.

Capacidade de reutilização
Desenvolvendo sistemas como Web Services distintos, as equipes de desenvolvimento
podem tirar maior vantagem da capacidade de reutilização do código. Esse tópico foi im-
portante quando a programação orientada a objetos tornou-se uma “moda”. A idéia era
que o desenvolvedor pudesse desenvolver um componente que outros desenvolvedores
poderiam utilizar vinculando-o aos seus códigos-fonte. Os Web Services levam esse con-
ceito para um outro nível. Agora, os desenvolvedores simplesmente utilizam a funciona-
lidade fornecida pelo Web Service por meio de uma interface comum. Reutilizando os
serviços internos e externos, a produtividade geral do desenvolvimento aumenta.

Neutralidade de linguagem
Um outro benefício dos Web Services (e desenvolvimento .NET em geral) é sua neutralida-
de de linguagem. Os desenvolvedores não precisam se preocupar como ou em que lingua-
gem os Web Services são desenvolvidos. Esse é um benefício para grandes empresas com
múltiplas equipes de desenvolvimento que poderiam utilizar diferentes linguagens de de-
senvolvimento e bancos de dados. Por meio dos Web Services, a comunicação/integração
pode ocorrer por toda a empresa sem levar em consideração esses sistemas distintos.

Servidores
Para fornecer efetivamente uma solução de Web Services, você deve entender como im-
plantá-los e em que tipos de servidores eles devem ser implantados. Se esses servidores
devem atender às demandas impostas pelos Web Services, eles deverão funcionar inti-
mamente com a linguagem (XML) dos Web Services e ser capazes de serem desenvolvi-
dos com ferramentas de desenvolvimento de Web Services como o Delphi for .NET.
NESTE CAPÍTULO
CAPÍTULO 2 — Do desenvolvimento
à execução

Visão geral sobre o — O Common Language


Runtime (CLR)
.NET Framework — O Common Type System
(CTS)
— A Common Language
Specification (CLS)
Este capítulo fornece uma visão geral sobre o — .NET Framework
componente central do .NET – o .NET Framework. Class Library (FCL)
O .NET Framework é a entidade técnica que permite aos
desenvolvedores disponibilizarem as soluções de
negócios discutidas no Capítulo 1.

Do desenvolvimento à execução
Listando de forma simples, os passos para desenvolver e
executar uma aplicação .NET são

1. Escrever um programa em uma linguagem .NET


de sua escolha.
2. Compilar seu código gerando Intermediate
Language (IL) que reside em um módulo
gerenciado.
3. Combinar seus módulos gerenciados para formar
um assembly.

4. Distribuir/implantar seu assembly na plataforma


alvo.

5. Invocar o CLR que carrega, compila, executa e


gerencia seu código.

Para que isso tudo funcione, são necessários vários


componentes do .NET Framework. A Figura 2.1 mostra
os componentes do Framework. Esses componentes são
as linguagens de programação .NET que aparecem na
linha superior da Figura 2.1. A Common Language
Specification (CLS) são regras para os desenvolvedores
de linguagem .NET como a Borland, a Framework Class
Library (FCL), o Common Language Runtime (CLR) e os
ambientes de desenvolvimento como o Delphi for .NET
e o Visual Studio .NET. Este capítulo discute cada
componente em mais detalhes.
12 Capítulo 2 Visão geral sobre o .NET Framework

Delphi C# C++ VB Outros...

Ambientes de desenvolvimento
Common Language Specification (CLS)

Application Class Libraries


Framework
Class Library
Base Class Library

Common Language Runtime

FIGURA 2.1 O .NET Framework.

O Common Language Runtime (CLR)


O Common Language Runtime (CLR) é um componente-chave do .NET Framework.
O CLR trata a execução de aplicações .NET e fornece os serviços necessários em runtime
(tempo de execução) para essas aplicações. O CLR, quando invocado, realiza as opera-
ções exigidas para compilar, alocar memória e gerenciar o código da aplicação invocado-
ra. Embora mais ou menos semelhante à maneira como as linguagens interpretadas fun-
cionam, o CLR é qualquer coisa, exceto um sistema interpretado.
O CLR opera em uma representação intermediária do código original escrito em algu-
mas linguagens compatíveis com o .NET como Delphi ou C#. Esse código, chamado Micro-
soft Intermediate Language (MSIL) ou simplesmente Intermediate Language (IL), está conti-
do em um arquivo chamado módulo gerenciado (managed module). Módulos gerenciados
contêm outras informações requeridas pelo CLR e serão abrangidos nas subseções a seguir.

Módulos gerenciados
Módulos gerenciados são gerados pelos compiladores de linguagens .NET. Um módulo
gerenciado é um Windows Portable Executable (PE) e consiste em quatro partes:
— O cabeçalho do PE – um Windows PE Header padrão.
— O cabeçalho do CLR – Informações sobre o cabeçalho específicas ao CLR e para se-
rem utilizadas pelo CLR.
— Metadados – os metadados consistem em tabelas que descrevem os tipos, mem-
bros e referências a outros módulos.
— Código gerenciado – o código gerenciado está em uma linguagem comum que os
compiladores .NET geram a partir de linguagens compatíveis com o .NET. Essa
linguagem é chamada Microsoft Intermediate Language (MSIL).
Mesmo que módulos gerenciados sejam PEs (Portable Executables), eles exigem que
o CLR seja executado e, mesmo assim, não podem ser executados independentemente,
mas primeiro precisam ser incorporados a um assembly.
O Common Language Runtime (CLR) 13

Assemblies
Essencialmente, os assemblies são uma unidade de empacotamento e distribuição para
aplicações .NET. Eles podem conter aplicações .NET completas ou funcionalidades com-
partilháveis por outras aplicações .NET. Uma aplicação .NET é, de fato, um assembly.
Os assemblies podem conter um ou mais módulos gerenciados. Os assemblies tam-
bém podem conter arquivos de recursos como imagens, html e assim por diante. Os as-
semblies contêm suas aplicações .NET e permitem melhor segurança em torno do aces-
so ao código, definição de tipo, escopo, distribuição e controle de versão. Os assemblies
são semelhantes aos clássicos arquivos executáveis do Win32 (*.exe), biblioteca de vín-
culo dinâmico (*.dll) e a um package (pacote) do Delphi (*.bpl). Eles são diferentes pelo
fato de superarem muitos dos problemas associados com arquivos específicos do
Win32.
Primeiro, os assemblies apresentam diferentes versões e estas podem residir no mes-
mo sistema. Portanto, duas aplicações poderiam utilizar duas versões diferentes de um
assembly – sem o inferno da DLL.
Segundo, os assemblies podem executar lado a lado, o que não é facilmente possível
ao utilizar DLLs.
Por fim, instalar assemblies é uma questão de copiar o arquivo para o diretório em
que você quer que eles residam.
O Capítulo 6 discute em detalhes a criação e implementação dos assemblies.

Código gerenciado e não-gerenciado


O código que é gerenciado pelo CLR é chamado código gerenciado. O código gerenciado
conta com os serviços de runtime do CLR como verificação de tipos, gerenciamento de
memória e garbage collection (coleta de lixo). Uma vantagem do código gerenciado é
que ele permite ao CLR cuidar de muitas tarefas de limpeza e manutenção que, do con-
trário, o programador teria de tratar.
Entretanto, ainda é possível escrever código não-gerenciado em uma aplicação
.NET. Fazendo isso, a responsabilidade de alocar e liberar memória recai sobre o progra-
mador. Além disso, o código não-gerenciado não tira proveito dos serviços de runtime
oferecidos para gerenciar o código. Contudo, às vezes é necessário escrever um código
não-gerenciado para tratar tarefas de baixo nível ou utilizar uma funcionalidade só dis-
ponível fora dos serviços do CLR.

NOTA
O compilador do Delphi for .NET (DCCIL) não suporta código não-gerenciado. Na realidade, atual-
mente somente o C++.NET da Microsoft suporta uma combinação de código gerenciado e
não-gerenciado no mesmo módulo. Você pode, porém, utilizar o Delphi 7 para Win32 a fim de es-
crever código não-gerenciado em uma DLL separada (expondo objetos COM ou rotinas simples).
As rotinas nessa DLL podem ser invocadas a partir do código DCCIL gerenciado utilizando P/Invo-
ke ou COM Interop. Além disso, a utilização de código não-gerenciado torna seu código essencial-
mente dependente da plataforma. COM Interop é abordado no Capítulo 16.
14 Capítulo 2 Visão geral sobre o .NET Framework

Compilação e execução MSIL e JIT


A MSIL, às vezes chamada Common Intermediate Language (CIL), é um formato relacio-
nado ao código de assembly, mas independente de qualquer plataforma/CPU alvo. Lin-
guagens como Delphi e C# são pré-compiladas no MSIL. Como a MSIL é distribuída, os
desenvolvedores podem escrever aplicações .NET em várias linguagens. De fato, o códi-
go IL, junto com os metadados que o descrevem, permite que essas linguagens interajam
diretamente com quaisquer outras linguagens compatíveis com o .NET. Em outras pala-
vras, podemos escrever uma classe no Visual Basic .NET que pode ser utilizada direta-
mente pelo nosso código em .NET ou C# para Delphi. Discutiremos alguns exemplos es-
pecíficos disso mais adiante.
O ponto em que o paradigma .NET/MSIL difere das tecnologias interpretadas tem a
ver com a compilação just-in-time (JIT). Veja a Figura 2.2, que ilustra a seqüência de
carregamento, compilação e execução do CLR.
Primeiro, um programador utiliza um compilador da linguagem .NET como o
Delphi for .NET ou C# a fim de gerar um assembly (código IL com metadados). Quando o

Delphi C# C++

Código
Compilador Compilador Compilador
não-gerenciado

IL IL IL

Common Language Runtime

Compilador JIT

Código nativo

Sistema operacional

FIGURA 2.2 A seqüência de carregamento, compilação e execução do CLR.


O Common Language Runtime (CLR) 15

código é executado, ou mais precisamente, quando o CLR executa o código, ele trata al-
gumas tarefas importantes. O CLR carrega o código (classes e seus membros), aloca e or-
ganiza memória e realiza a verificação de tipos. Basicamente, o class loader prepara o có-
digo para compilação. O class loader faz isso utilizando um mecanismo sob demanda.
Portanto, as classes não são carregadas a menos que sejam referenciadas. Quando esse có-
digo é preparado, o compilador JIT está pronto para compilar o código IL no código nativo
de máquina. O compilador JIT também utiliza um mecanismo por demanda porque ele
compila os métodos somente se eles estiverem referenciados e se ainda não tiverem sido
compilados. Portanto, depois que um método é compilado no código de CPU nativo, ele
não precisa ser recompilado. Depois de compilado, o mecanismo de execução do CLR exe-
cuta o código compilado. Também observe que o carregamento do código e da compilação
JIT são iniciados a partir do Execution Engine (mecanismo de execução). O CLR talvez seja
suficientemente sofisticado para colocar em linha o código de máquina gerado diretamen-
te no ponto de invocação. Isso poderia resultar em código menor e/ou mais rápido.

O .N E T E M T O D O S O S L U G A R E S ?
Atualmente, a Microsoft fornece implementações do CLR para unidades móveis como o Poc-
ket PC destinadas a diferentes arquiteturas de CPU como ARM, MIPS e SH3. Além disso, há es-
forços a caminho para portar o .NET Framework para outras plataformas como Linux, FreeBSD
e Unix. Se esses esforços forem bem-sucedidos, é possível que vejamos o CLR executar nosso
mesmo código em múltiplas máquinas, da maneira como o Java faz hoje, mas com muito mais
eficiência.

Outros subcomponentes do CLR são mostrados na Figura 2.3.


Esses subcomponentes funcionam conjuntamente para formar os serviços de runti-
me necessários para aplicações .NET.

FIGURA 2.3 Os subcomponentes do CLR.


16 Capítulo 2 Visão geral sobre o .NET Framework

Por exemplo, o Execution Engine é o componente que trata a execução real do códi-
go gerenciado. Ele obtém código compilado de modo que possa ser executado. O execu-
tion engine do CLR funciona em conjunção com os outros componentes do CLR como o
Garbage Collector (GC). O code manager (gerenciador de código) aloca espaço para obje-
tos gerenciados no managed heap (pilha gerenciada). Quando esses objetos não são mais
referenciados, o GC cuida de removê-los do heap. O GC muda o que estamos acostuma-
dos a fazer ao lidar com gerenciamento de memória no Win32 para desenvolvimento em
Delphi. O Capítulo 9 discute o GC em mais detalhes.

O Common Type System (CTS)


O CTS é um rico conjunto padronizado de tipos e operações dentro do CLR. De acordo
com a Microsoft, o CTS suporta implementação completa da maioria das linguagens de
programação. Muitas linguagens utilizarão algum tipo de atribuição de alias para se refe-
rirem a um tipo específico dentro do CTS. Por exemplo, o tipo System.Int32 na CTS é um
integer em Delphi e um int em C#. Embora diferentemente identificado no nível da lin-
guagem, os dois mapeiam para o mesmo tipo subjacente. Esse aspecto do CTS contribui
com a interoperabilidade da linguagem.
Os benefícios do CTS são, no mínimo, triplicados. Primeiro, ele fornece um nível de
interoperabilidade de linguagem. Segundo, como são derivados de uma classe básica, to-
dos os tipos têm uma interface comum. Terceiro, o CTS garante segurança de tipos com
qualquer código que você desenvolva.
O CTS é embutido no CLR e define, verifica e gerencia a utilização em runtime
desses tipos. Todos os tipos são derivados de uma classe básica, System.Object. A partir
dessa classe, os tipos entrarão em uma entre duas categorias: Value Types e Reference
Types. A Figura 2.4 representa um gráfico que mostra o relacionamento hierárquico
entre esses tipos.

Tipos

Tipos por valor Tipos por referência


(Value types) (Reference types)

Tipos por valor


predefinidos Tipos de objeto Tipos de ponteiro Tipos de interface
(Object types) (Pointer types) (Interface types)
Tipos por valor
definidos
pelo usuário Tipos de classe
Arrays
(Class type)
Tipos
enumerados

FIGURA 2.4 Tipos no CTS.


A Common Language Specification (CLS) 17

Value types
Value Types são tipos de dados simples que podem ser mantidos na pilha e contêm seus
dados diretamente. A Figura 2.4 mostra que há três derivações do Value Type: predefini-
dos, definidos pelo usuário e tipos enumerados.
Value Types predefinidos são tipos de dados primitivos como integer, double e boo-
lean. Tipos definidos pelo usuário são basicamente records. Como são mantidos na pi-
lha, faz sentido mantê-los pequenos. Um tipo enumerado (enumeration) é um conjunto
de constantes integer identificadas em que cada uma delas representa um valor especifi-
cado dentro do conjunto.

Reference types
Os Reference Types são alocados no heap. A variável de um Reference Type armazena no
heap o endereço de uma instância de seu tipo. Se você já desenvolveu com Delphi, estará
bem familiarizado com tipos armazenados no heap porque eles são praticamente sinôni-
mos do tipo TObject do Delphi. Os três Reference Types básicos são Object Types, Poin-
ter Types e Interface Types. Como mostrado na Figura 2.4, classes e arrays são basicamen-
te descendentes de um Object Type. Ocasionalmente, você ouvirá Object Types referidos
como autodescritivos.

A Common Language Specification (CLS)


O CLS define um subconjunto das regras do CTS em torno dos tipos, convenções para
atribuição de nomes e outros atributos da implementação de linguagem que permitem
uma portabilidade entre diferentes linguagens. Essas regras estão delineadas na Partition
I da proposta de padronização Common Language Infrastructure da European Compu-
ter Manufactures Association (ECMA). Essas são as regras a que os projetistas de lingua-
gens devem obedecer se quiserem que suas linguagens tenham por alvo o CLR e intera-
jam com outras linguagens. Isso não significa que as linguagens, ou suas aplicações que
utilizam essas linguagens, devem obedecer a restrições somente do CLS. De fato, muitos
recursos das linguagens, embora válidos no CTS, não são válidos no CLS. Se desejar ga-
rantir compatibilidade entre diferentes linguagens, você deverá codificar de acordo com
o CLS somente o código que pretende expor para outras linguagens.

DICA
É possível verificar a compatibilidade do seu código com o CLS utilizando ferramentas como
FxCop. O download da FxCop pode ser feito em

http://www.gotdotnet.com/team/fxcop/.
Além disso, as informações sobre o CLS podem ser obtidas em
http://msdn.microsoft.com/net/ecma/.

.NET Framework Class Library (FCL)


A .NET Framework Class Library (FCL) é uma rica biblioteca orientada a objetos compos-
ta de classes, interfaces, tipos e outros elementos para programação de aplicações .NET.
18 Capítulo 2 Visão geral sobre o .NET Framework

A FCL fornece aos desenvolvedores código e funcionalidade preexistente para realizar as


tarefas comuns de programação. A natureza da FCL é semelhante à da VCL no Delphi, à
MFC no Visual C++ e a outras dessas bibliotecas. Uma diferença importante é que a FCL
está disponível para todas as linguagens .NET. Pense no benefício de uma biblioteca de
classes unificada. Antes do .NET, os desenvolvedores em Delphi só podiam utilizar a fun-
cionalidade do Visual C++ se ela estivesse exposta por uma DLL ou COM. Agora, se co-
nhecer a FCL, você será capaz de utilizar diretamente as classes fornecidas pela FCL a par-
tir de qualquer linguagem. As subseções a seguir discutem alguns dos principais recursos
da FCL.

Namespaces
Os namespaces no .NET são unidades lógicas e organizacionais de tipos. Os namespaces
permitem a contenção de tipos e funcionalidades e são agrupados hierarquicamente em
níveis de especificidade lógica.

O namespace System
O namespace System contém as classes bases utilizadas por outras classes no framework.
Ele também define os tipos de dados por valor e por referência, eventos, handlers de
evento, interfaces, atributos e processamento de exceções. O namespace System declara a
ancestral de todas as classes, a classe Object. Todos os tipos no .NET, em última instância,
derivam da classe Object.

Namespaces primários do subsistema


A Tabela 2.1 mostra os namespaces primários do subsistema na FCL. À medida que de-
senvolve no .NET, você irá tornar-se mais familiarizado com os tipos dentro desses na-
mespaces. Muitos desses tipos serão utilizados nos exemplos em todo o livro.

TABELA 2.1 Namespaces da FCL


Nome Funcionalidades contidas
System.CodeDOM Classes e interfaces representando os elementos e a estrutura de
um documento de código-fonte.
System.Collections Classes e interfaces que definem uma coleção baseada em objetos
(listas, filas, arrays de bits, tabelas de hash e dicionários).
System.ComponentModel Classes e interfaces para suportar a implementação do
comportamento em design-time/runtime de um componente.
System.Configuration Classes e interfaces para acessar programaticamente o .NET
Framework e as definições de configuração da aplicação.
System.Data Classes e interfaces que compõem o framework do ADO.NET para
gerenciar dados a partir de múltiplas origens de dados.
System.DirectoryServices Classes para acessar o Active Directory por meio do código
gerenciado.
System.Drawing Acesso à funcionalidade de desenho da GDI+.
.NET Framework Class Library (FCL) 19

TABELA 2.1 Continuação


Nome Funcionalidades contidas
System.EnterpriseServices Acessibilidade COM+.
System.Globalization Classes para definir configurações relacionadas à cultura.
System.IO Tipos para ler e gravar arquivos, fluxos e manipulação de arquivos
e diretórios.
System.Management Acessibilidade ao Windows Management Instrumentation (WMI).
System.Messaging Classes para acessar, monitorar e administrar filas de mensagens
na rede.
System.Net Interface de programação para protocolos de rede.
System.Reflection Classes e interfaces que fornecem visualização e invocação de
tipos, métodos e campos em runtime.
System.Resources Classes e interfaces para gerenciar recursos, associados a uma
cultura, normalmente utilizados em internacionalização.
System.Runtime.CompilerServices Acesso ao comportamento de runtime do CLR utilizado por
escritores/programadores de compilador.
System.Runtime.InteropServices Acesso a objetos COM.
System.Runtime.Remoting Acesso a objetos e métodos em execução em um outro
AppDomain ou máquina.
System.Runtime.Serialization Utilizada a fim de serializar objetos para e a partir de fluxos de
dados (streams).
System.Security Acesso ao sistema de segurança base do CLR.
System.ServiceProcess Classes para suportar a criação e o gerenciamento dos serviços
Windows.
System.Text Classes que suportam capacidades de formatação de texto.
System.Threading Classes que permitem programação com múltiplos threads.
System.Timers A funcionalidade do timer.
System.Web Classes para comunicação entre navegador/servidor. Isso também
é conhecido como ASP.NET.
System.Windows.Forms Classes para suportar aplicações desktop Windows.
System.XML Suporte para programação XML.

Literalmente centenas de tipos são definidos dentro dos namespaces descritos na


Tabela 2.1. Dependendo do tipo de desenvolvimento que está fazendo, provavelmente
você utilizará alguns com mais freqüência do que outros. Por exemplo, se estiver desen-
volvendo aplicações GUI, você utilizará boa parte dos tipos definidos no namespace
System.Windows.Forms. Se estiver escrevendo aplicações de banco de dados, você utilizará
os tipos contidos no namespace System.Data. De fato, isso é apenas uma visualização de
alto nível dos namespaces da FCL. À medida que tornar-se mais familiarizado com a
FCL, você descobrirá que será capaz de navegar pelas várias hierarquias dos namespaces
a fim de encontrar o tipo específico de que você precisa para quase todas as tarefas de
programação.
Um exemplo simples fornecido na Listagem 2.1 ilustra a utilização de dois namespa-
ces na Tabela 2.1. Estes são os namespaces System.Windows.Forms e System.Drawing.
20 Capítulo 2 Visão geral sobre o .NET Framework

LISTAGEM 2.1 Primeiro exemplo do Delphi for .NET


1: program d4dgCh2Ex1;
2:
3: {%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\
➥v1.1.4322\System.Drawing.dll'}
4: {%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\
➥v1.1.4322\System.Windows.Forms.dll'}
5:
6: uses
7: System.Windows.Forms,
8: System.Drawing;
9:
10: type
11: TFirstForm = class(Form)
12: private
13: btnDrawRect: Button;
14: public
15: constructor Create;
16: procedure btnDrawRectClick(Sender: TObject; E: EventArgs);
17: end;
18:
19: constructor TFirstForm.Create;
20: begin
21: inherited Create;
22: Text := 'D4DN First example';
23: Size := System.Drawing.Size.Create(400, 300);
24:
25: btnDrawRect := Button.Create;
26: btnDrawRect.Text := 'Draw Rect';
27: btnDrawRect.Parent := self;
28: btnDrawRect.Location := Point.Create(10, 10);
29: btnDrawRect.Size := System.Drawing.Size.Create(70, 25);
30: btnDrawRect.add_Click(btnDrawRectClick);
31: end;
32:
33: procedure TFirstForm.btnDrawRectClick(Sender: TObject; E: EventArgs);
34: var
35: grGraphics: Graphics;
36: rct: Rectangle;
37: pn: Pen;
38: begin
39: grGraphics := Graphics.FromHwnd(Handle);
40: rct := Rectangle.Create(40, 40, 200, 200);
41: pn := Pen.Create(Color.Red);
42: grGraphics.DrawRectangle(pn, rct);
43: end;
.NET Framework Class Library (FCL) 21

LISTAGEM 2.1 Continuação


44:
45: begin
46: Application.Run(TFirstForm.Create);
47: end.
48:
49:

u Localize o código no CD: \Code\Chapter2\Ex01.

Esse exemplo simples cria um formulário no Windows com um botão que, quando
pressionado, desenha um retângulo na superfície do formulário (ver Figura 2.5).

FIGURA 2.5 Formulário First example – desenhando um retângulo.

Se já programou em Delphi, você deve conhecer esse código, embora haja algumas
diferenças sutis. Se você ainda não trabalhou em Delphi, agüente firme até a Parte II, que
abrange a linguagem de programação do Delphi for .NET, a Delphi Language. Por en-
quanto, só quero indicar como as classes de um namespace são acessadas em um progra-
ma escrito no Delphi for .NET.
Primeiro, observe o bloco de código,

uses
System.Windows.Forms,
System.Drawing;

Esse código instrui o compilador a fornecer acesso às classes contidas nos namespa-
ces System.Windows.Forms e System.Drawing. Fazendo isso, podemos utilizar as classes Applica-
tion, Form e Button, que estão contidas no namespace System.Windows.Forms e os objetos
Graphics, Rectangle, Size e Pen contidos no namespace System.Drawing. O evento de clique
para o objeto Button cria dinamicamente os objetos de que preciso e então os utiliza para
desenhar um retângulo na superfície do formulário.
O compilador do Delphi for .NET utiliza implicitamente o namespace System do
.NET, que fornece acesso à classe EventArgs. Da mesma forma, ele sempre utiliza implicita-
22 Capítulo 2 Visão geral sobre o .NET Framework

mente a unit Borland.Delphi.System, que fornece acesso à classe TObject . TObject é simples-
mente um alias para a classe System.Object. Neste momento, você não precisa entender es-
ses detalhes. Boa parte disso será abordada no Capítulo 5.
Boa parte do .NET opera ao longo dessas linhas, isto é, você obtém acesso às classes
nos namespaces e as utiliza para obter a funcionalidade necessária – seja acesso ao banco
de dados utilizando componentes em System.Data ou geração de página Web, no lado do
servidor (server-side), utilizando componentes em System.Web. Além disso, você pode es-
tender o framework criando suas próprias classes e posicionando-as em seus próprios
namespaces. Mais adiante, no Capítulo 6, você aprenderá a implantar/instalar suas pró-
prias classes como assemblies, permitindo estender o framework original com seu
próprio conjunto de classes.
PARTE II:
CAPÍTULO 3 A LINGUAGEM DE
PROGRAMAÇÃO DO
DELPHI FOR .NET
Introdução ao Delphi 3 Introdução ao Delphi for

for .NET e a nova IDE .NET e a nova IDE

4 Programas, units e
namespaces

5 A linguagem Delphi
O Delphi for .NET da Borland foi algo há muito
tempo esperado – pelo menos nos corações e mentes
NESTE CAPÍTULO
dos vários desenvolvedores leais ao Delphi no mundo
todo. A Borland respondeu a esse chamado criando um — Delphi for .NET – um cenário
ambiente de desenvolvimento integrado para construir maior
aplicações .NET no Delphi e ela respondeu bem. Este — Introdução ao Integrated
capítulo introduz o produto Delphi for .NET e como a Development Environment
nova IDE aprimora a produtividade. (IDE)

Delphi for .NET – um cenário maior


Portanto, o que é exatamente o Delphi for .NET? Delphi
for .NET é um dos componentes da ampla visão da
Borland sobre como os desenvolvedores criam soluções
de negócios. Essa visão tende a prover um processo
completo englobando todo o ciclo de vida da aplicação,
o chamado Application Lifecycle Development (ALM),
direto nas mesas das equipes de desenvolvimento,
dando-lhes um escopo completo das ferramentas
necessárias para desenvolver aplicações. Essas
ferramentas formam um conjunto de produtos em
torno desse conceito, o ALM, dando-lhes o que eles
precisam para definir, projetar, desenvolver, testar,
implantar e gerenciar projetos de softwares.
O foco deste livro é a fase de “desenvolvimento” do
ALM e o Delphi for .NET é apenas uma das ferramentas
que a Borland fornece para atender a esse propósito.
Contudo, visto que uma boa idéia é saber como o
Delphi for .NET se encaixa nesse esquema grandioso, é
recomendável a leitura dos vários “whitepapers” e
artigos tanto no site principal da Borland como no site
community.
24 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

DICA
O site Web da Borland é http://www.borland.com. O site community é http://bdn.borland.com/.
O site deste autor, dedicado ao desenvolvimento em Delphi e particularmente ao desenvolvimen-
to .NET em Delphi, é http://www.delphiguru.com. Esse site também será o local em que você
pode obter atualizações de código e páginas de erratas sobre este livro.

Introdução ao Integrated Development


Environment (IDE)
Semelhante às versões anteriores do Delphi, sua IDE é um ambiente poderoso por meio
do qual você projeta e desenvolve aplicações. Ele inclui várias ferramentas necessárias
para fazer isso e é extensível, permitindo que fornecedores independentes aprimorem as
capacidades da IDE. Este capítulo discute os componentes-chave da IDE.

NOTA
Este será o único capítulo em que discutiremos como utilizar a IDE. De fato, em muitos códigos
neste livro, faço atribuições a propriedades programaticamente, em vez de configurá-las dentro
da IDE. Faço isso de modo que fique claro quais atribuições foram feitas às várias propriedades.
Normalmente, você faria essas atribuições utilizando o Object Inspector da IDE. Para mais dicas so-
bre a usabilidade da IDE, visite http://www.delphiguru.com, onde periodicamente postarei tutori-
ais sobre a utilização do Delphi for .NET.

Os principais componentes para a IDE do Delphi são


— Welcome Page

— Formulário principal

— Área do Designer

— Forms

— Tool Palette/Code Snippets

— Object Inspector

— Code Editor

— Project Manager

— Data Explorer

— Code Explorer

— To-Do List

— Object Repository

Este capítulo discute cada um desses componentes e então faz com que você inicie
sua primeira aplicação simples em Delphi for .NET.
Introdução ao Integrated Development Environment (IDE) 25

Welcome Page
A página Welcome é a primeira coisa que você vê quando a IDE do Delphi é iniciada (ver
Figura 3.1). Essa página serve mais como um centro rápido de carregamento em que você
pode criar novos projetos, abrir projetos recentes e acessar os vários recursos no Delphi
for .NET.

DICA
Se, por alguma razão, a Welcome Page não aparecer, você pode reexibi-la clicando em View, Wel-
come Page.

FIGURA 3.1 A Welcome Page.

Observe que a Welcome Page tem um navegador web incorporado. Portanto, você
pode digitar um URL válido no combobox de URLs para carregar o site web que digitou.

DICA
Você pode modificar o arquivo default.htm no diretório Welcome Page a fim de personalizar sua
página Welcome – incluindo seus próprios links – alterar a imagem mostrada e assim por diante.

Designer
O Designer é a área da IDE que você vê ao criar uma nova aplicação Windows Form, Web
Form ou VCL Form mostrada no painel central, e após clicar na guia Design na parte in-
26 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

ferior desse painel (ver Figura 3.2). O Designer é a área em que você cria seu design visual
dentro da IDE. Você pode arrastar e soltar componentes da paleta Tool para seus formu-
lários a fim de criar o design visual das suas aplicações. Ao trabalhar com VCL Forms, o
Designer parece e comporta-se de maneira diferente. Por exemplo, você observará que
clicar com o botão direito do mouse no menu é diferente.
Alguns componentes são visuais, enquanto outros não. Ao colocar componentes vi-
suais em um formulário, como um TextBox, eles são colocados exatamente onde você os
posicionou no formulário (Figura 3.2). Os componentes não-visuais, como SqlDataAdap-
ter e SqlConnection, são posicionados na Component Tray, que é a parte inferior do Desig-
ner (ver Figura 3.2). Esses componentes só são visíveis em tempo de design para que você
possa selecioná-los a fim de modificar suas propriedades. Dentro da barra de status do
Designer, você verá informações sobre o status do arquivo em que está trabalhando, por
exemplo, se ele foi ou não modificado. Você também verá a posição do cursor e se o edi-
tor está no modo de inserção ou sobrescrição. Você também verá os botões de gravação
de Macro na barra de status, que serão discutidos mais adiante.
Ao trabalhar dentro do design, você irá querer tirar proveito de alguns recursos que a
IDE contém para aprimorar sua produtividade. Eis alguns:
— Locking Control – Depois de posicionar os controles no formulário da maneira
que você quer, freqüentemente, uma boa idéia é bloqueá-los em um lugar de

Project Manager
Object Inspector Main Form/Main Menu

Barra de status Component Tray Designer Tool Palette/Code Snippets

FIGURA 3.2 O Designer.


Introdução ao Integrated Development Environment (IDE) 27

modo que você não os mova acidentalmente à medida que os seleciona com o
mouse. Para fazer isso, simplesmente clique com o botão direito do mouse para
invocar o menu local e selecione a opção Lock Controls. Esse bloqueio é por for-
mulário. Além disso, o Designer na VCL tem apenas controles Lock e Edit globais,
para todos os formulários.
— Tab Order – Algo esquecido com freqüência é a configuração da ordem de tabula-
ção dos controles. Você pode examinar a ordem de tabulação dos controles no
seu formulário clicando com o botão direito do mouse para invocar o menu local
e selecionar Tab Order. Você pode ajustar a ordem de tabulação de cada controle
clicando com o mouse em qualquer lugar no controle que contém o número da
ordem de tabulação.
— Não deixe de tirar proveito das barras de ferramentas Align, Position e Spacing
para organizar os controles no seu formulário. Essas barras de ferramentas são ati-
vadas quando há múltiplos controles selecionados no formulário.
— Docking – A IDE permite reposicionar/encaixar as várias janelas quase em qual-
quer local que você desejar. Esse excelente recurso permite ajustar seu espaço de
trabalho da maneira como preferir.
— Layouts – Há três layouts predefinidos de área de trabalho: Default, Classic (para
aqueles que preferem o clássico layout desencaixado do Delphi) e Debug. O layout
Default é mostrado na Figura 3.2. A Figura 3.3 exibe o layout Debug que mostra
várias janelas de depuração (Call Stack, Watch List e Local Variables). O IDE alter-

FIGURA 3.3 Layout Debug da IDE.


28 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

na para o layout Debug sempre que você começa a depurar uma aplicação. Você
também pode alterar e salvar seus próprios layouts personalizados.
— Personalizando o menu File – Agora, você pode personalizar o menu File, New
para adicionar itens que precisa criar freqüentemente. Por exemplo, se precisar
criar Web Forms com freqüência, você poderá adicionar um WebForm ao menu
File, New diretamente, de modo que não precise carregar o Object Repository em
que normalmente selecionaria esse formulário.

Formulários
Os formulários são as principais entidades GUIs que você cria. Eles podem ser uma janela
ou um diálogo com que os usuários interagem ou uma página Web. No Delphi for .NET,
você pode trabalhar com três tipos de formulários: Windows Forms, Web Forms e VCL
Forms.
O Windows Forms permite utilizar as classes dentro do .NET Framework para criar
aplicações GUI gerenciadas e funcionalmente ricas. Você cria uma aplicação Windows
Forms selecionando File, New, Windows Forms Application no menu principal. A IDE
então apresentará um Windows Form com uma área na qual você pode colocar os vários
controles da interface com o usuário na paleta Tool para criar o design do formulário
principal da aplicação. Em geral, suas aplicações irão consistir em vários Windows
Forms. Este livro utiliza vários exemplos criados com Windows Forms.
Web Forms são blocos de construções de aplicações Web ASP.NET. Para criar Web
Forms, selecione File, New, ASP.NET Web Application no menu principal. A IDE exibirá
então o designer do Web Form, que é seu espaço de trabalho para as páginas do seu site
Web. A Parte V aborda o desenvolvimento de aplicações ASP.NET em detalhes.
Os VCL Forms são semelhantes aos Windows Forms pelo fato de que eles oferecerem
as ferramentas e classes necessárias para construir aplicações GUIs gerenciadas. Entretan-
to, os VCL Forms utilizam o conjunto de classes .NET nativas da própria Borland. Os VCL
Forms, sendo incorporados ao framework VCL da Borland, também permitem facilmen-
te portar aplicações Win32 existentes para aplicações .NET gerenciadas. Por meio da
VCL.NET, você será capaz de criar aplicações que têm por alvo tanto o ambiente geren-
ciado do .NET como o Win32 nativo. Para criar uma aplicação VCL Forms, selecione File,
New, VCL Forms Application no menu principal. A IDE exibirá uma área de design de
formulário em que você pode posicionar componentes na paleta Tool para construir
aplicações funcionais e interativas.

NOTA
O projeto Mono é um esforço bem adiantado para criar um runtime .NET de código-fonte aberto
para o sistema operacional Linux. O objetivo do Mono é ser capaz de executar qualquer assembly
gerenciado e isso inclui os assemblies do Delphi. Teoricamente, por causa do projeto Wine utiliza-
do para emular Win32 no Linux, há uma esperança de executar aplicações VCL for .NET tal como
são no Linux/Mono/Wine. Até o momento, isso não foi conseguido. Você pode encontrar infor-
mações sobre o projeto Mono em http://www.go-mono.com/.
Introdução ao Integrated Development Environment (IDE) 29

Tool Palette/Code Snippets


Por padrão, a Tool Palette está localizada no painel inferior direito (ver Figura 3.2). Seu
conteúdo depende da visualização em que você trabalha. Se estiver trabalhando com a
exibição Designer, a Tool Palette conterá vários componentes que você pode adicionar
ao seu formulário arrastando-os e soltando-os no formulário ou dando um clique duplo
neles. A lista de componentes disponíveis depende do tipo de arquivo que você está edi-
tando (por exemplo, arquivo Win Form, Web Form, VCL Form ou HTML). Se você esti-
ver trabalhando no Code Editor (editor de código), a Tool Palette conterá uma lista de
trechos de código.
Os trechos de código são pequenos blocos de código reutilizáveis. Você pode adicio-
nar seus próprios trechos de código à Tool Palette. Além disso, você pode categorizar o
arranjo dos seus trechos de código. Para adicionar uma categoria, simplesmente clique
com o botão direito do mouse dentro da Tool Palette e selecione Add New Category. Insi-
ra o nome de uma categoria e clique em OK. Para adicionar código do Code Editor à sua
lista de trechos de código reutilizáveis, selecione seu código, pressione a tecla Alt e arras-
te-o para a Tool Palette. Para adicionar trechos de código a uma nova categoria, primeiro
você deve adicioná-los à categoria Code Snippets padrão. Depois de adicionados aí, eles
podem ser arrastados para uma categoria personalizada. A ordem é
1. Arraste o código para a categoria Code Snippets padrão.

2. Clique com o botão direito do mouse em Add New Category.

3. Com a tecla Alt pressionada arraste o novo trecho do Code Snippets para sua nova
categoria.

Uma categoria vazia também será removida quando a Tool Palette sair de foco.

Object Inspector
Por meio do Object Inspector (ver Figura 3.2), você modifica as propriedades dos compo-
nentes e anexa handlers de evento a esses componentes. Isso permite especificar em tem-
po de design vários atributos que afetam a aparência visual e o comportamento dos com-
ponentes. Por exemplo, depois de posicionar um componente Label em um Windows
Form, você pode mudar sua propriedade Text para algo mais específico e sua cor de fonte
para vermelho. Isso é feito na Properties Page do Object Inspector. A página Events é
onde você anexa o código aos vários eventos de cada componente. Por exemplo, um
componente Button tem um evento Click que é disparado quando o usuário clica no bo-
tão. Dando um clique duplo na parte de edição do Object Inspector desse evento, a IDE
cria automaticamente o handler de evento e posiciona seu cursor nesse handler de even-
to dentro do Code Editor.
Vários atributos do Object Inspector, como cor, aparência e comportamento, são
configuráveis. Simplesmente clique com o botão direito do mouse no Object Inspector
para invocar o menu local e selecione o menu Properties. Isso carregará a caixa de diálo-
go Object Inspector Properties mostrada na Figura 3.4.
30 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

FIGURA 3.4 Caixa de diálogo Object Inspector Properties.

Code Editor
Se tiver a oportunidade de participar de uma demonstração da Borland de um dos seus pro-
dutos, você sempre ouvirá a reivindicação de que você é capaz de fazer coisas fantásticas
com os produtos “sem escrever uma única linha de código”. Bem, a verdade é que você não
pode fazer nada de útil sem escrever muitas linhas de código. Felizmente, a Borland disponi-
biliza Code Editors de primeira linha nas suas ferramentas de desenvolvimento. O Code Edi-
tor do Delphi for .NET não foge a essa regra. Eis alguns recursos úteis do Code Editor:
— Code Folding – Inquestionavelmente, esse é um dos recursos mais “legais” da
IDE. Dentro do Code Editor, você observará sinais de subtração (-) e de adição (+)
à direita dos blocos de código. Clicando em um sinal de subtração, você irá reco-
lher o bloco de código. Clicando em um sinal de adição, você expandirá o bloco
de código. A Figura 3.5 mostra um bloco de código recolhido e expandido. O
Code Folding é ativado automaticamente para funções, funções aninhadas, tipos
estruturados e blocos de código.

DICA
A IDE fornece alguns atalhos pelo teclado para as pessoas cansadas de utilizar o mouse (utilizando
o mapeamento padrão de teclado). Estes são:
— Ctrl+Shift+K+U – Expande o bloco mais próximo.
— Ctrl+Shift+K+E – Recolhe o bloco mais próximo.
— Ctrl+Shift+K+A – Expande todos os blocos nesse arquivo.
— Ctrl+Shift K+T – Alterna para o bloco mais próximo.
— Ctrl+Shift+K+O – Ativa/desativa o agrupamento de código. (Depois de ativá-lo, você precisa
mudar para um outro arquivo e voltar para vê-lo.)
Introdução ao Integrated Development Environment (IDE) 31

FIGURA 3.5 Code folding.

— Regions – Você pode utilizar as diretivas $REGION/$ENDREGION para especificar seus


próprios blocos de agrupamento de código (code folding). A sintaxe para utilizar
essas diretivas é

{$REGION 'Region Title'}


code
{$ENDREGION}

A Figura 3.6 mostra como é isso na IDE.

FIGURA 3.6 Regions.

— Class Completion – Essa é uma excelente ferramenta de produtividade que permite


definir uma classe, e então via uma tecla de atalho, o Code Editor cria automatica-
mente os métodos de implementação. Por exemplo, suponha que você defina a
classe mostrada aqui:
32 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

TMyClass = class (System.Object)


private
procedure Foo;
public
property MyInt: Integer;
property MyString: String;
end;

Depois de pressionar a combinação de teclas Shift+Ctrl+C, o Code Editor cria a


declaração de classe a seguir:

TMyClass = class (System.Object)


private
procedure Foo;
public
FMyString: &String;
FMyInt: Integer;
procedure set_MyInt(const Value: Integer);
procedure set_MyString(const Value: &String);
public
property MyInt: Integer read FMyInt write set_MyInt;
property MyString: String read FMyString write set_MyString;
end;

Você observará que os métodos acessores da propriedade foram definidos. A im-


plementação desses métodos também é criada na seção de implementação da
unit como mostrado aqui:

{TMyClass}
procedure TMyClass.Foo;
begin
end;

procedure TMyClass.set_MyInt(const Value: Integer);


begin
FMyInt := Value;
end;

procedure TMyClass.set_MyString(const Value: &String);


begin
FMyString := Value;
end;

— Code Block Indentation – Um outro recurso favorito da IDE é o aumento/diminui-


ção de recuo de bloco de código, usado para ajustes no recuo do código. Simples-
mente selecione um bloco de código e utilize a combinação de teclas Ctrl+K+I
para aumentar o recuo do bloco de código selecionado. Utilize Ctrl+ K+U para di-
minuir o recuo do bloco de código. Alternativamente, você pode utilizar
Ctrl+Shift+I e Ctrl+Shift+U.
Introdução ao Integrated Development Environment (IDE) 33

— Code Browsing – Se quiser localizar a declaração de uma função particular no


código, simplesmente mantenha a tecla Ctrl pressionada enquanto clica com o
botão esquerdo do mouse sobre a chamada de função. Como uma alternativa,
você pode utilizar a combinação de teclas Alt+Up Arrow para realizar a mesma
coisa. Para navegar rapidamente da definição de uma função para sua imple-
mentação, utilize a combinação Shift+Ctrl+Up Arrow/Shift+Ctrl+Down Arrow.
Além disso, a IDE mantém um histórico dos links de código navegados. Utili-
zando Alt+Left e Alt+Right você pode mover-se de um lado a outro no histórico
de navegação.

Project Manager
No Project Manager, você gerencia vários arquivos que estão contidos dentro dos seus
Project Groups. Ele contém três guias: Project Manager, Model View e Data Explorer. A
Figura 3.7 mostra o Project Manager desencaixado.
Um Project Group é um grupo de projetos relacionados. Por exemplo, você tem um
grupo de projeto composto de múltiplos projetos de assemblies separados. A Figura 3.7
mostra um Project Group, ProjectGroup1. Ele contém os projetos Project1.exe, Package1.dll
e Console1.exe. A guia Project Manager permite gerenciar grupos de projetos fornecendo
opções de menu para adicionar e remover projetos e para compilar e criar projetos conti-
dos no grupo.
A guia Project Manager também permite gerenciar projetos fornecendo menus para
adicionar e remover arquivos e opções de compilação.

FIGURA 3.7 Project Manager (desencaixado).


34 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

Model View
A Model View fornece uma visualização aninhada das units do projeto, classes e outros
membros contidos dentro dos seus projetos. A Figura 3.8 mostra a Model View. Observe
que as Model Views são acessadas de dentro da janela Project Manager selecionando sua
guia na parte inferior da janela Project Manager.

FIGURA 3.8 Janela Model View.

Data Explorer
O Data Explorer (ver Figura 3.9) também é acessível de dentro da janela Project Manager.
No Data Explorer, você pode gerenciar várias conexões com as origens de dados
por meio dos Database Providers. Você pode realizar várias funções como criar novas
conexões, criar projetos a partir de conexões, navegar pelos bancos de dados e outras.
A ajuda on-line do Delphi for .NET fornece exemplos de como realizar essas funções.
O desenvolvimento de aplicações de bancos de dados será abrangido em mais detalhes
na Parte IV.

Object Repository
O Object Repository é o local em que você pode utilizar itens reutilizáveis dentro das suas
aplicações. No Object Repository, você pode selecionar um tipo de aplicação ou elemen-
tos que utilizará dentro da sua aplicação, como Windows Forms, ASP.NET Forms, classes
e assim por diante. Para abrir o Object Repository, simplesmente selecione File, New,
Other que carrega o formulário mostrado na Figura 3.10.
A lista à esquerda representa as várias categorias dos itens a partir das quais você
pode selecionar. A visualização à direita exibe os itens para uma categoria selecionada.
Por exemplo, selecionando a categoria Delphi for .NET Projects, você verá uma lista dos
Introdução ao Integrated Development Environment (IDE) 35

FIGURA 3.9 O Data Explorer exibe conexões.

FIGURA 3.10 O Object Repository é utilizado para selecionar itens reutilizáveis.

vários projetos que pode criar no Delphi for .NET, como Window Forms Application,
VCL Forms Application, Package, Library (dll) e assim por diante. Ao selecionar um item
que cria uma nova aplicação, a IDE criará a aplicação e os arquivos de aplicação para
você. Se selecionar um elemento como um formulário, a IDE criará e adicionará esse item
à sua aplicação existente na qual você está trabalhando no momento.
36 Capítulo 3 Introdução ao Delphi for .NET e a nova IDE

Code Explorer
O Code Explorer pode ser útil para navegar por grandes units. O Code Explorer exibe o
layout estrutural de uma unit incluindo classes, métodos, componentes e outros. Para
navegar por um item particular, simplesmente dê um clique duplo nele e ele posicionará
o cursor no código correspondente ao item em questão. O Code Explorer é mostrado na
Figura 3.11.

FIGURA 3.11 Utilize o Code Explorer para navegar por uma unit.

To-Do List
A To-Do List é uma maneira conveniente de monitorar as tarefas pendentes. Elas são lis-
tadas em uma janela encaixável e mantidas nos seus arquivos de origem.
Para utilizar To-Dos, simplesmente clique com o botão direito do mouse em qual-
quer lugar no Code Editor e selecione Add To-Do Item no menu local. Fazer isso invocará
a caixa de diálogo Add To-Do Item.
Ao pressionar OK depois de adicionar o To-Do, ele será adicionado à To-Do List
que aparece na parte inferior da IDE (ver Figura 3.12). Se a To-Do List não for exibida,
selecione View, To-Do List no menu principal.
Introdução ao Integrated Development Environment (IDE) 37

FIGURA 3.12 A To-Do List é exibida na parte inferior da IDE.


NESTE CAPÍTULO
— Estruturas do módulo CAPÍTULO 4
gerenciado
— Namespaces Programas, units e
namespaces

T odas as aplicações em Delphi for .NET são


estruturadas em torno da clássica construção
unit/program do Delphi. Alguns termos “Delphi”
correspondem a termos .NET. Por exemplo, uma unit
(unidade de código) Delphi define um namespace .NET
e um programa Delphi é um módulo gerenciado .NET.
Embora, estruturalmente, as correspondências sejam
“vagas”, a linguagem de programação Delphi foi
significativamente estendida para se adaptar ao
paradigma .NET. Neste capítulo, discutiremos como
construir vários programas utilizando o Delphi for .NET.
Utilizaremos a terminologia .NET para explicar as várias
partes.

Estruturas do módulo gerenciado


Aplicações em Delphi são compiladas como módulos
gerenciados. As seções a seguir discutem a estrutura de
um programa Delphi.

A estrutura do programa
Um programa é a unidade básica (base unit) que forma
uma aplicação; chamamos isso módulo principal. Em
geral, um programa pode consistir em várias units e
também pode utilizar código de bibliotecas e/ou
packages (pacotes). Para utilizar a terminologia .NET,
um programa Delphi pode vincular diretamente código a
partir de quaisquer units e importar código de um ou
mais assemblies.
Estruturas do módulo gerenciado 39

NOTA
Um recurso do Delphi for .NET, que não existe em outras linguagens .NET, é a capacidade de vin-
cular código de um assembly Delphi diretamente ao seu módulo gerenciado. Essa capacidade
simplifica ainda mais a distribuição porque você só teria de instalar um único executável. Isso é
particularmente útil com relação a componentes de terceiros escritos em Delphi. Discutiremos
mais sobre isso no capítulo que trata de assemblies.

A Listagem 4.1 mostra um exemplo de esqueleto de um arquivo de programa, algu-


mas vezes mencionado como arquivo de projeto.

LISTAGEM 4.1 Esqueleto de um arquivo de programa (módulo principal)


1: program MyProgram;
2: {$APPTYPE CONSOLE}
3: uses
4: SysUtils;
5: var
6: s: String;
7: begin
8: s := 'lower case string';
9: Console.Writeline(UpperCase(s));
10: Console.Readline;
11: end.

u Localize o código no CD: \Code\Chapter4\Ex01.

O arquivo de programa consiste em um título, uma cláusula uses e o bloco de de-


claração e execução. O título do programa simplesmente declara o nome do progra-
ma (também chamado namespace padrão de projeto, detalhes sobre isso mais adian-
te). Nesse exemplo, o nome do programa é MyProgram. A cláusula uses especifica as
units cujo código será vinculado ao MyProgram. No exemplo, vinculamos o código
contido na unit SysUtils, que dá acesso à função UpperCase( ) utilizada no bloco de
execução.

NOTA
A unit Borland.Delphi.System (ou namespace) é vinculada automaticamente aos seus arquivos
de origem. Adicioná-la explicitamente é proibido pelo compilador DCCIL.

O bloco de declaração e execução do arquivo de programa começa com a pala-


vra-chave var e termina com palavra-chave end. O código que é executado aparece entre
as instruções begin..end. Nesse exemplo, o código é muito simples.

A estrutura da unit
Units Delphi são contêineres organizacionais para classes, tipos, rotinas, variáveis, cons-
tantes e outras construções de linguagem. Quando um programa, ou uma outra unit,
40 Capítulo 4 Programas, units e namespaces

adiciona um nome de unit à sua cláusula uses, ele instrui o compilador a criar as constru-
ções contidas dentro da unit utilizada disponível localmente. A Listagem 4.2 mostra uma
unit contendo uma classe para escrever string.

LISTAGEM 4.2 Exemplo de uma unit – MyUnit.pas


1: unit MyUnit;
2: interface
3:
4: type
5: TMyStringWriter = class(TObject)
6: procedure WriteMyString(aMyString: String);
7: end;
8: var
9: msw: TMyStringWriter;
10:
11: implementation
12:
13: procedure TMyStringWriter.WriteMyString(aMyString: String);
14: begin
15: Console.Writeline(aMyString);
16: end;
17:
18: initialization
19: msw := TMyStringWriter.Create;
20:
21: finalization
22: msw.Free;
23: end.

u Localize o código no CD: \Code\Chapter4\Ex02.

NOTA
Em um contexto .NET puro, a instrução msw.Free na linha 22 seria desnecessária. Para código por-
tável para diversas plataformas (para Win32) ou se a classe TMyStringWriter tivesse implementa-
do a interface IDisposable (tendo um destrutor explícito), a chamada Free faria sentido. Aqui, ela
é utilizada apenas para ilustrar a seção de finalização.

O cabeçalho da unit
Como ocorre com o arquivo de programa, a unit consiste em várias seções. O cabeçalho da
unit nomeia a unit com o identificador seguindo a palavra-chave unit (linha 1, Listagem 4.2).

A seção de interface
A interface existe entre as palavras-chave interface e implementation (linhas 2–11 na Lista-
gem 4.2). Ela está dentro dessa seção em que você define os elementos que quer expor
para os usuários da sua unit. A Listagem 4.3 mostra esse conceito.
Estruturas do módulo gerenciado 41

LISTAGEM 4.3 Exemplo de utilizando MyUnit.pas


1: program MyProgram;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: MyUnit;
6: var
7: s: String;
8: begin
9: s := 'Hello .NET!';
10: msw.WriteMyString(s);
11: end.

u Localize o código no CD: \Code\Chapter4\Ex02.

Esse código é semelhante àquele na Listagem 4.1. A Listagem 4.3 contém MyUnit na
cláusula uses. Isso informa o compilador a disponibilizar tipos contidos na seção de in-
terface de MyUnit para MyProgram. Na verdade, você pode ver no bloco de código que utiliza-
mos a variável msv, que é declarada na seção de interface de MyUnit. A seção de interface é
onde você declara tipos e classes. Sua implementação ocorre na seção de implementação
apropriadamente identificada.

A seção de implementação
A seção de implementação aparece entre as palavras-chave implementation e initialization.
Ela contém o código para todas as rotinas, classes etc. declaradas na seção de interface. A
seção de implementação também pode declarar tipos, rotinas etc., que não são declara-
dos na seção de interface, sem expô-los a units externas, mas tornando-os acessíveis à
unit que os declara.
A cláusula uses da seção de interface lista as units cujas declarações estão disponíveis
tanto para as seções de interface como de implementação. Observe que isso não expõe
nenhuma unit utilizada ao módulo externo. Portanto, mesmo que MyOtherUnit seja incluí-
da em MyUnit, os tipos contidos em MyOtherUnit não são expostos a MyProgram. MyProgram teria
de incluir MyOtherUnit na sua própria cláusula uses para poder acessar seus tipos.
A seção de implementação pode conter sua própria cláusula uses. Se contiver, ela será

implementation
uses
UnitA, UnitB, UnitC;

A cláusula uses da seção de implementação lista as units cujas declarações só estão


disponíveis para essa seção.

As seções de inicialização e de finalização


A seção de inicialização é uma seção opcional em que você pode fornecer o código execu-
tado quando o programa é carregado. Da mesma forma, a seção de finalização (também
opcional) é executada quando o programa é desligado.
42 Capítulo 4 Programas, units e namespaces

ATENÇÃO
Ao escrever blocos de inicialização e de finalização, é recomendável não posicionar dependências
na inicialização e finalização de outras units. Isso é particularmente verdade na finalização. O com-
pilador/linker do Delphi e a RTL determinam a ordem de execução dos blocos de inicialização e de
finalização das diferentes units. Em última instância, a ordem é determinada pela ordem da cláu-
sula uses do programa e pelas ordens das cláusulas uses das próprias units. Assim, embora a ordem
seja determinista, não é fácil ou óbvio controlar a ordem de execução. A sincronização da execu-
ção de finalização está associada ao evento System.AppDomain.CurrentDomain.ProcessExit. Esse
evento é disparado quando o processo do application domain padrão é encerrado.

Sintaxe da cláusula uses


A cláusula uses pode ocorrer em arquivos de programa, arquivos de biblioteca e units. Em
uma unit, ela poderia aparecer tanto nas seções de interface como de implementação.
Uma cláusula uses pode conter tantas referências à unit quantas forem requeridas. Cada
entrada é separada por uma vírgula com a última terminando com um ponto-e-vírgula:

Uses UnitA, UnitB, UnitC, UnitD;

Eliminando a ambigüidade de tipos


Suponha que você tivesse a seguinte cláusula uses em um programa:

Uses UnitA, UnitB;

Também suponha que ambas as units declararam dois tipos diferentes que foram
nomeados TMyType. Por fim, suponha que você precise referenciar o TMyType declarado na
UnitA em uma declaração de variável:

Var
Mt: TMyType;

Aqui, o problema é que o TMyType utilizado é o contido na UnitB porque ela é a última
na lista de units utilizadas. Essa ambigüidade pode ser resolvida prefixando o nome da
unit seguido por um ponto (.) para o identificador de tipo em qualquer lugar que ele é re-
ferenciado. Portanto, sua declaração de variável seria escrita como

Var
Mt: UnitB.TMyType;

Referência circular de unit


Ocasionalmente, você irá se deparar com uma situação em que UnitA utiliza UnitB que uti-
liza UnitC que utiliza UnitA – todas de dentro da seção de interface. Isso é chamado referên-
cia circular de unit e o compilador Delphi reclama disso. Em geral, isso significa que você
modularizou seu código incorretamente.
Em um exemplo mais simples, poderíamos dizer que UnitA utiliza um tipo em UnitB
(TypeZ). UnitB precisa utilizar um tipo em UnitA (TypeY). Essa referência circular de unit é
mostrada na Figura 4.1.
Estruturas do módulo gerenciado 43

FIGURA 4.1 Uma referência circular de unit.

FIGURA 4.2 Uma referência circular de unit corrigida.


44 Capítulo 4 Programas, units e namespaces

Para resolver isso, você teria de mover TypeY da UnitA para uma nova unit, UnitC (ver
Figura 4.2). Agora, tanto UnitA como UnitB podem utilizar UnitC sem a referência circular
porque UnitC não utiliza nem UnitA nem UnitB.

Namespaces
As units Delphi fornecem a capacidade de agrupar tipos lógicos fisicamente em arquivos
separados. Os namespaces levam esse agrupamento ainda mais longe mostrando ser pos-
sível agrupar units Delphi hierarquicamente. Isso se ajusta bem à maneira como o .NET
faz isso, pois tipos .NET nativos já são agrupados dessa maneira. Os benefícios disso são:
— Os namespaces podem ser aninhados para formar uma hierarquia lógica de tipos.

— Os namespaces podem ser utilizados para retirar a ambigüidade de tipos com o


mesmo nome.
— Os namespaces podem ser utilizados para retirar a ambigüidade de units com o
mesmo nome em que residem em packages diferentes.

Declaração de namespaces
Um arquivo de projeto declara seu próprio namespace padrão, que chamamos namespa-
ce padrão de projeto. Portanto, um projeto declarado como

program MyProject.MyIdeas;

cria o namespace MyProject, enquanto a instrução

program MyProject.MyIdeas.FirstIdea

declara o namespace MyProject.MyIdeas. Observe que o ponto (.) é utilizado para criar uma
hierarquia dentro dos namespaces. Na instrução anterior, FirstIdea é membro de MyIdeas,
que é membro de MyProject.

DICA
Para determinar o namespace padrão de projeto, remova o último identificador, incluindo o pon-
to, do cabeçalho do programa.

As units declaram seus namespaces na instrução de cabeçalho da unit. Por exemplo,

unit MyProject.MyIdeas.MyUnitA

declara uma unit que é membro do namespace MyProject.MyIdeas.


Considere os títulos de arquivos de programa e de units a seguir:

program MyProject.MyIdeas;
unit MyProject.MyIdeas.FirstIdea;
unit MyProject.MyIdeas.SecondIdea;
unit MyProject.MyIdeas.ThirdIdea;
Namespaces 45

unit MyProject.MyTasks.Task1;
unit MyProject.MyTasks.Task1.SubTask1;
unit MyProject.MyTasks.Task2;

Essas units formariam uma hierarquia de namespace, como mostrado na Figura 4.3.
Uma unit genérica é uma unit que não declara explicitamente seu próprio namespace.

FIGURA 4.3 Uma hierarquia de namespaces.

Utilização de namespaces
Os namespaces são inseridos no contexto de uma unit por meio da cláusula uses da unit.
Portanto, para utilizar os tipos em MyProject.MyIdeas.FirstIdea, a cláusula uses de uma unit
deve conter

uses
MyProject.MyIdeas.FirstIdea;

Os tipos declarados em MyProject.MyIdeas não estão disponíveis; isso exigiria uma in-
clusão explícita desse namespace na cláusula uses da unit:

uses
MyProject.MyIdeas;
MyProject.MyIdeas.FirstIdea;

Da mesma forma, ter MyProject.MyIdeas em uma cláusula uses não insere os namespa-
ces de seus membros (FirstIdea, SecondIdea, …) no contexto da unit em uso.
Depois que um namespace é utilizado dentro de uma unit, você pode acessar identi-
ficadores de tipo da maneira normal. Entretanto, dado um tipo com o mesmo nome
dentro de dois namespaces diferentes aos quais você tem acesso, você deve prefixar o
nome completamente qualificado do namespace ao identificador ambíguo:

writeln(MyProject.MyIdeas.Identifier1);
46 Capítulo 4 Programas, units e namespaces

A cláusula de namespaces
Um projeto pode declarar uma lista de namespaces separados por vírgulas e terminados
por um ponto-e-vírgula a fim de que o compilador faça uma pesquisa ao converter no-
mes de identificadores a partir de units genéricas. Essa declaração aparece no arquivo de
projeto seguindo a declaração de programa (ou package):

program MyProject.MyIdeas;
namespaces MyProject.HerIdeas, MyProject.HisIdeas;

Essa cláusula deve preceder todas as outras cláusulas dentro do arquivo de projeto.

Convertendo namespaces genéricos


O compilador Delphi deve converter identificadores utilizados de outras units nas units
que os contém. Ele faz isso iterando pelas units listadas nas cláusulas uses. Para units com
namespaces completamente qualificados, encontrar a unit é simples porque seu escopo é
conhecido. Para units genéricas, o compilador converte nomes de identificadores per-
correndo a seguinte ordem de pesquisa:
1. Namespace atual da unit (se houver um)

2. Namespace padrão de projeto (se houver um)

3. A cláusula de namespaces do projeto (em ordem inversa em que aparecem na cláu-


sula)

4. Namespaces nas opções de compilador

Dada uma unit com uma cláusula uses de

unit MyProject.MyIdeas.FirstIdea;
uses MyProject.MyIdeas.SecondIdea, BadIdeas, GoodIdeas;

A ordem de pesquisa para BadIdeas seria

1. BadIdeas

2. MyProject.MyIdeas

3. MyProject (ponto de partida para uma unit genérica)

4. MyProject.HisIdeas

5. MyProject.HerIdeas

6. Opções de compilador

Aliases de unit
É possível que suas hierarquias tornem-se bem complexas. Nesse caso, você pode decla-
rar aliases de unit a fim de fornecer um nome mais curto que pode ser utilizado ao quali-
ficar identificadores dentro do seu código-fonte. A linha de código a seguir ilustra isso:
Namespaces 47

uses MyProgram.MyFramework.MyClasses.FileClasses as aFC;

A instrução as aFC cria um nome mais curto que pode ser utilizado no seu códi-
go-fonte:

MyFile := aFC.File.Create;

Essa instrução é equivalente ao seguinte:

MyFile := MyProgram.MyFramework.MyClasses.FileClasses.File.Create;

Tenha em mente que os aliases de unit não devem entrar em conflito com outros
identificadores.
NESTE CAPÍTULO
— Tudo sobre o .NET CAPÍTULO 5
— Comentários

— Procedures e funções A Linguagem Delphi


— Variáveis
por Steve Teixeira
— Constantes

— Operadores

— Tipos na linguagem Delphi


E ste capítulo aborda a linguagem por trás da ferramenta
Delphi, o Object Pascal. Primeiro, introduziremos os
— Tipos definidos pelo usuário princípios básicos de linguagem Delphi, como regras e
construções da linguagem. Em seguida, iremos examinar
— Typecasting e conversão
alguns aspectos mais avançados, como classes e
de tipos
tratamento de exceções. Este capítulo supõe que você já
— Strings de recursos tem alguma experiência em outras linguagens de
— Testando condições
programação de alto nível. Portanto, ele não ensina os
conceitos associados com as linguagens de programação,
— Loops mas a expressar esses conceitos no Delphi. Depois de ler
— Procedures e funções este capítulo, você entenderá de que modo funcionam
no Delphi conceitos de programação como variáveis,
— Escopo
tipos, operadores, loops, cases, exceções e objetos e
— Units e namespaces quantos desses elementos se relacionam ao interior do
.NET Framework. Para fornecer uma base prática
— Packages e assemblies
adicional, iremos delinear comparações, onde
— Programação orientada a apropriado, com os primos do Delphi, no .NET, mais
objetos amplamente utilizados:
— Utilizando objetos no Delphi C# e Visual Basic .NET.

— Tratamento estruturado de
exceções Tudo sobre o .NET
O Delphi 8 cria aplicações que executam completamente
dentro do contexto do Microsoft .NET Framework.
Portanto, as capacidades e os recursos do compilador
Delphi 8 devem estar sujeitos às capacidades e aos recursos
internos do .NET Framework. Essa noção poderia ser
vagamente desconcertante para aqueles que, oriundos do
mundo do código nativo, começam a trabalhar com o
.NET. Essencialmente, um compilador de código nativo
pode fazer tudo o que quiser – suas capacidades só são
limitadas pelos desejos do fornecedor do compilador. No
desenvolvimento em .NET, tudo que podemos literalmente
fazer – até mesmo algo tão trivial quanto adicionar dois
inteiros – se resume ao compilador que gera o código que
manipula recursos e tipos do .NET Framework.
Comentários 49

Em vez de gerar código nativo, um compilador .NET, como o Delphi 8, gera código
em um formato chamado Microsoft Intermediate Language, ou MSIL, que é a representa-
ção de mais baixo nível das instruções da aplicação.

NOTA
O Capítulo 2 discute a natureza da compilação just-in-time (JIT) do .NET Framework. Agora talvez
seja o momento ideal de revisar essas informações.

Comentários
A linguagem Delphi suporta três tipos de comentários: comentários entre chaves, co-
mentários entre parênteses e asteriscos e comentários com duas barras invertidas. Exem-
plos de cada tipo de comentário são:

{ Comentário que utiliza chaves }


(* Comentário que utiliza parênteses e asteriscos *)
// Comentário com duas barras invertidas

O comportamento dos dois primeiros tipos de comentários é praticamente idêntico.


O compilador considera o comentário como tudo o que está entre os delimitadores de
abertura e fechamento. Para comentários com barras duplas invertidas, tudo o que
estiver após as duas barras invertidas até o final da linha é considerado um comentário.

NOTA
Você não pode aninhar comentários do mesmo tipo. Embora aninhar comentários de tipos diferen-
tes seja sintaxe válida, essa prática não é recomendável. Eis alguns exemplos:

{ (* This is legal *) }
(* { This is legal } *)
(* (* This is illegal *) *)
{ { This is illegal } }

Uma outra técnica útil para desativar um trecho de código, particularmente se diferentes tipos de
comentários forem utilizados no código, é utilizar a diretiva de compilador $IFDEF. O código a se-
guir utiliza $IFDEF para desativar um bloco de código:

{$IFDEF DONTCOMPILEME}
// imagine alguns códigos aqui
{$ENDIF}

Como o identificador DONTCOMPILEME não está definido, o código dentro da $IFDEF é efetivamente
desativado.

Procedures e funções
Uma vez que procedures e funções são tópicos relativamente universais no que diz respeito
às linguagens de programação, não entraremos em muitos detalhes sobre elas aqui. Simples-
mente queremos demonstrar alguns recursos únicos ou pouco conhecidos nessa área.
50 Capítulo 5 A Linguagem Delphi

NOTA
Funções sem valor de retorno (um retorno de void na terminologia C#) são chamadas procedures,
enquanto funções com valor de retorno são chamadas funções. Freqüentemente, o termo rotina
será utilizado para descrever tanto procedures como funções e o termo método para descrever
uma rotina dentro de uma classe.

Parênteses em chamadas
Um dos recursos menos conhecidos da linguagem Delphi é o fato de que os parênteses
são opcionais ao chamar uma procedure ou função que não recebe nenhum parâmetro.
Portanto, os dois exemplos de sintaxe a seguir são válidos:

Form1.Show;
Form1.Show( );

Seguramente, esse não é um nenhum recurso surpreendente, mas é particularmente


interessante para aqueles que dividem seu tempo entre o Delphi e uma linguagem como
C#, em que os parênteses são exigidos. Se não puder investir 100% do seu tempo no
Delphi, esse recurso significará que você não precisa lembrar-se de utilizar uma sintaxe
diferente de chamada de função para diferentes linguagens.

Sobrecarga
A linguagem Delphi suporta o conceito de sobrecarga (overloading) de funções, isto é, a
capacidade de atribuir a múltiplas procedures ou funções o mesmo nome com diferentes
listas de parâmetros. É necessário que todos os métodos sobrecarregados sejam declara-
dos com a diretiva overload, como mostrado aqui:

procedure Hello(I: Integer); overload;


procedure Hello(S: string); overload;
procedure Hello(D: Double); overload;

Observe que as regras para sobrecarregar métodos de uma classe são um pouco dife-
rentes e serão explicadas na seção “Sobrecarregando métodos”.

Parâmetros com valor padrão (default)


Os parâmetros com valor padrão (default) também são suportados na linguagem Delphi;
isto é, a capacidade de fornecer um valor padrão para um parâmetro de função ou de pro-
cedure sem precisar passar esse parâmetro ao chamar a rotina. Para declarar uma procedu-
re ou função que contenha parâmetros com valores padrão, depois do tipo de parâmetro
acrescente um sinal de igual e o valor padrão, como mostrado no exemplo a seguir:

procedure HasDefVal(S: string; I: Integer = 0);

A procedure HasDefVal( ) pode ser chamada de duas maneiras. Primeiro, você pode
especificar dois parâmetros:

HasDefVal('hello', 26);
Variáveis 51

Segundo, você pode especificar somente o parâmetro S e utilizar o valor padrão para I:

HasDefVal('hello'); // valor padrão utilizado para I

Você deve seguir várias regras ao utilizar parâmetros com valores padrão:
— Parâmetros com valores padrão devem aparecer no final da lista de parâmetros.
Parâmetros sem valores padrão não podem preceder parâmetros com valores pa-
drão em uma lista de parâmetros da procedure ou função.
— Parâmetros com valores padrão podem ser strings, ordinais, pontos flutuantes, pon-
teiros ou tipos definidos. Classes, interfaces, arrays dinâmicos, rotinas e referências
de classe também são suportados, mas somente se o valor padrão for igual a nil.
— Parâmetros com valores padrão devem ser passados por valor ou como const. Eles
não podem ser referências (var, out) ou parâmetros não-tipificados.

Um dos maiores benefícios dos parâmetros com valores padrão está em adicionar
funcionalidades a funções e procedures existentes sem sacrificar a retrocompatibilidade.
Por exemplo, suponha que você publique uma unit que contém uma função revolucio-
nária chamada AddInts( ) que adiciona dois números:

function AddInts(I1, I2: Integer): Integer;


begin
Result := I1 + I2;
end;

A fim de acompanhar a concorrência, você percebe que precisa atualizar essa função
de modo que ela tenha a capacidade de adicionar três números. Entretanto, você tem re-
ceio de fazer isso, pois adicionar um parâmetro fará com que o código que chama essa
função não compile. Graças aos parâmetros padrão, você pode aprimorar a funcionalida-
de de AddInts( ) sem comprometer a compatibilidade. Eis um exemplo:

function AddInts(I1, I2: Integer; I3: Integer = 0);


begin
Result := I1 + I2 + I3;
end;

DICA
Geralmente, você deve examinar as rotinas sobrecarregadas ao optar entre adição de valores padrão
ou rotinas sobrecarregadas como um meio de incorporar novos recursos sem quebrar a retrocom-
patibilidade. Sobrecargas são executadas um pouco mais eficientemente e são mais compatíveis
com outras linguagens do .NET uma vez que parâmetros com valores padrão não são suportados
em C# ou C++ gerenciado (C++ managed).

Variáveis
Talvez você esteja acostumado a declarar variáveis de improviso: “Preciso de um outro
inteiro, vou então simplesmente declarar um aqui no meio desse bloco de código”. Essa é
52 Capítulo 5 A Linguagem Delphi

uma noção perfeitamente razoável se você vier de uma outra linguagem como C# ou
Visual Basic .NET. Se essa for sua prática, você precisará exercitar-se um pouco mais para
utilizar variáveis na linguagem Delphi. O Delphi exige que você declare todas as variáveis
antecipadamente em seções próprias antes de começar procedures, funções ou progra-
mas. Talvez você esteja acostumado a escrever código livre de restrições como este:

public void foo( )


{
int x = 1;
x++;
int y = 2;
float f;
//... etc ...
}

No Delphi, esse tipo de código deve ser associado e estruturado um pouco mais para
que se pareça a isto:

procedure Foo;
var
x, y: Integer;
f: Double;
begin
x := 1;
inc(x);
y := 2;
//... etc ...
end;

DISTINÇÃO ENTRE MAIÚSCULAS E MINÚSCULAS E O USO DE MAIÚSCULAS


A linguagem Delphi – como ocorre com o Visual Basic .NET, mas diferentemente do C# – não faz
distinção entre letras maiúsculas e minúsculas. Caixas-alta e caixas-baixa são utilizadas por clareza;
use, portanto, seu melhor critério, como o estilo adotado neste livro recomenda. Se o nome do
identificador contiver várias palavras misturadas, lembre-se de utilizar, para obter clareza, a ca-
mel-cap, colocando em maiúsculo a primeira letra de cada palavra no identificador. Por exemplo,
o nome a seguir é confuso e difícil de ler:

procedure thisprocedurenamemakesnosense;
Entretanto, este código é bem legível:
procedure ThisProcedureNameIsMoreClear;

Talvez você se pergunte qual é o motivo e o benefício deste tipo de estrutura.


Você, porém, descobrirá que o estilo estruturado da linguagem Delphi para declara-
ção de variável serve para criar um código mais legível, sustentável e menos falho do
que outras linguagens que contam com uma convenção, em vez de uma regra, para
impor a razão.
Constantes 53

Observe como o Delphi permite agrupar mais de uma variável (ou, nesse sentido, pa-
râmetro formal) do mesmo tipo na mesma linha com a sintaxe a seguir:

VarName1, VarName2: SomeType;

Utilizar esse recurso tende a tornar o código muito mais compacto e legível se com-
parado com uma linguagem como C#, em que cada variável ou parâmetro deve ter o tipo
escrito por extenso.
Lembre-se de que, ao declarar uma variável no Delphi, o nome da variável precede o
tipo e há um dois-pontos entre as variáveis e os tipos. Para variáveis locais, a inicialização
de variável sempre é separada da declaração de variável.
O Delphi permite a inicialização de variáveis globais dentro de um bloco var. Eis al-
guns exemplos que demonstram a sintaxe para fazer isso:

var
i: Integer = 10;
S: string = 'Hello world';
D: Double = 3.141579;

NOTA
A pré-inicialização de variáveis só é permitida para variáveis globais, não para variáveis que são lo-
cais para uma procedure ou função.

INICIALIZAÇÃO COM ZERO


O CLR percebe que todas as variáveis são automaticamente inicializadas com zero. Quando sua
aplicação inicia, ou uma função é inserida, todos os tipos de inteiros serão 0, tipos de ponto flutuan-
te serão 0.0, objetos serão nil, strings serão vazias etc. Portanto, não é necessário inicializar variá-
veis com zero no seu código-fonte.
Diferentemente das versões Win32 do Delphi, isso é verdade para variáveis que são locais para fun-
ções e para variáveis globais.

Constantes
Constantes no Delphi são definidas em uma cláusula const, que se comporta de maneira
semelhante à palavra-chave const do C#. Eis um exemplo de três declarações de constan-
tes em C#:

public const float ADecimalNumber = 3.14;

public const int i = 10;

public const String ErrorString = "Danger, Danger, Danger!";

A principal diferença entre constantes em C# e constantes na linguagem Delphi é


que este, como ocorre com o Visual Basic .NET, não requer que você declare o tipo da
constante com o valor na declaração. O compilador Delphi aloca automaticamente o
tipo apropriado à constante com base no seu valor, ou, no caso de constantes escalares
54 Capítulo 5 A Linguagem Delphi

como Integer, monitora os valores à medida que ele funciona e nunca aloca espaços. Eis
um exemplo:
const
ADecimalNumber = 3.14;
i = 10;
ErrorString = 'Danger, Danger, Danger!';

Opcionalmente, você também pode especificar um tipo de constante na declaração.


Isso fornece controle total sobre o modo como o compilador trata suas constantes:
const
ADecimalNumber: Double = 3.14;
I: Integer = 10;
ErrorString: string = 'Danger, Danger, Danger!';

A SEGURANÇA DE TIPO DE UMA CONSTANTE TIPIFICADA


Constantes tipificadas têm uma vantagem distinta em relação à variedade não-tipificada: a falta,
nas constantes não-tipificadas, de uma tipificação clara, em tempo de compilação, impede que
elas suportem chamadas a métodos, não podendo ser tratadas como um objeto. Por exemplo, o
código a seguir é válido:

const
I: Integer = 19710704;
S: string;
begin
S := I.ToString;
Enquanto este código não irá compilar:
const
I = 19710704;
S: string;
begin
S := I.ToString;

A linguagem Delphi permite o uso de funções em tempo de compilação nas declara-


ções const e var. Essas rotinas incluem Ord( ), Chr( ), Trunc( ), Round( ), High( ), Low( ),
Abs( ), Pred( ), Succ( ), Length( ), Odd( ), Round( ), Trunc( ) e SizeOf( ). Por exemplo, todos
os códigos a seguir são válidos:
type
A = array[1..2] of Integer;

const
w: Word = SizeOf(Byte);

var
i: Integer = 8;
j: SmallInt = Ord('a');
L: Longint = Trunc(3.14159);
Operadores 55

x: ShortInt = Round(2.71828);
B1: Byte = High(A);
B2: Byte = Low(A);
C: Char = Chr(46);

ATENÇÃO
Para retrocompatibilidade, o compilador fornece uma opção que permite que constantes tipifica-
das sejam atribuíveis, tais como variáveis. Essa opção é acessível através da página Compiler da cai-
xa de diálogo Project Options, ou utilizando a diretiva de compilação $WRITEABLECONST (ou $J).
No entanto, esse comportamento deve ser oficialmente considerado inapropriado e evitado dei-
xando essa funcionalidade no seu estado desativado padrão.

NOTA
Como ocorre com o C# e o Visual Basic .NET, a linguagem Delphi não tem um pré-processador
como a linguagem C. Não há nenhum conceito de macros no Delphi e, portanto, nenhum equi-
valente no Delphi à #define do C para declaração de constantes. Embora seja possível utilizar a di-
retiva de compilador $define do Delphi para compilações condicionais semelhantes à #define
do C, você não pode utilizá-la para definir constantes. Utilize const no Delphi onde você utilizaria
#define para declarar uma constante em C.

Operadores
Os operadores são símbolos no seu código que lhe permitem manipular todos os tipos de
dados. Por exemplo, há operadores para adicionar, subtrair, multiplicar e dividir dados
numéricos. Há também operadores para resolver um elemento particular de um array.
Esta seção explica alguns operadores do Delphi e suas correlações com suas contrapartes
em C# e CLR.

Operadores de atribuição
A menos que você tenha alguma experiência em Pascal, o operador de atribuição do
Delphi talvez seja uma das coisas mais difíceis às quais se acostumar. Para atribuir um va-
lor a uma variável, utilize o operador := da maneira como utilizaria o operador = em C#
ou no Visual Basic .NET. Programadores em Delphi costumam chamar isso de operador
de atribuição ou “gets” (obter) e a expressão

Number1 := 5;

é lida como “Number1 obtém (gets) o valor 5” ou “Number1 é atribuído o valor 5.”

Operadores de comparação
Se você já programou no Visual Basic .NET, deverá sentir-se bem à vontade com os opera-
dores de comparação do Delphi porque eles são praticamente idênticos. Esses operadores
estão relativamente padronizados por todas as linguagens de programação, portanto só
serão abrangidos rapidamente nesta seção.
56 Capítulo 5 A Linguagem Delphi

A linguagem Delphi utiliza o operador = para realizar comparações lógicas entre duas
expressões ou valores. O operador = do Delphi é análogo ao operador == do C#; assim
uma expressão em C# escrita como

if (x == y)

é escrita desta maneira no Delphi:

if x = y

NOTA
Lembre-se de que na linguagem Delphi, o operador := é utilizado para atribuir um valor a uma va-
riável e o operador = compara os valores entre dois operandos.

O operador “não igual a” do Delphi é < > e seu propósito é idêntico ao do operador !=
do C#. Para determinar se duas expressões não são iguais, utilize este código:

if x < > y then FazAlgo

Operadores lógicos
O Delphi utiliza as palavras and e or como operadores “e” e “ou” lógicos enquanto o C#
utiliza os símbolos && e ¦¦, respectivamente, para esses operadores. A utilização mais co-
mum dos operadores and e or é como uma parte de uma instrução if ou loop, como de-
monstrado nos dois exemplos a seguir:

if (Condição 1) and (Condição 2) then


FazAlgo;

while (Condição 1) or (Condição 2) do


FazAlgo;

O operador “not” lógico do Delphi é not, utilizado para inverter uma expressão boo-
leana. Ele é análogo ao operador ! do C#. Também é freqüentemente utilizado como
uma parte das instruções if, como mostrado aqui:

if not (condição) then (faz algo); // se a condição for falsa então...

A Tabela 5.1 fornece uma referência fácil sobre como os operadores no Delphi são
mapeados para operadores correspondentes em C# e no Visual Basic.NET.

Operadores aritméticos
Você já deve conhecer a maioria dos operadores aritméticos da linguagem Delphi, pois,
geralmente, eles são semelhantes àqueles utilizados na maioria das linguagens comuns.
A Tabela 5.2 mostra todos os operadores aritméticos do Delphi e suas contrapartes em C#
e no Visual Basic.NET.
Operadores 57

TABELA 5.1 Operadores de atribuição, comparação e lógicos


Operador Delphi C# Visual Basic.NET
Atribuição := = =
Comparação = == = ou Is*
Não igual a < > != < >
Menor que < < <
Maior que > > >
Menor ou igual a <= <= <=
Maior ou igual a >= >= >=
Lógico and and && And
Lógico or or ¦¦ Or
Lógico not not ! Not
XOR lógico xor ^ Xor

TABELA 5.2 Operadores aritméticos


Operador Delphi C# Visual Basic.NET
Adição + + +
Subtração - - -
Multiplicação * * *
Divisão de ponto flutuante / / /
Divisão de inteiro div / \
Módulo mod % mod
Potência exponencial Nenhum Nenhum ^

Talvez você perceba que o Delphi e o Visual Basic .NET fornecem diferentes operadores
de divisão para cálculo de ponto flutuante e de inteiro, embora isso não seja o caso em C#.
O operador div trunca automaticamente qualquer resto ao dividir duas expressões de inteiro.

NOTA
Lembre-se de utilizar o operador de divisão correto para os tipos de expressões com que você tra-
balha. O compilador Delphi fornece um erro se você tentar dividir dois números de ponto flutuan-
te com o operador div de inteiro ou atribuir a um inteiro o resultado de dois inteiros divididos com
operador / de ponto flutuante, como ilustra o código a seguir:
var
i: Integer;
d: Double;
begin
i := 4 / 3; // Essa linha causará um erro de compilador
d := 3.4 div 2.3; // Essa linha também causará um erro
end;
Se precisar dividir dois inteiros utilizando o operador /, você poderá atribuir o resultado a um intei-
ro se converter a expressão em um inteiro com as funções Trunc( ) ou Round( ).
58 Capítulo 5 A Linguagem Delphi

Operadores binários
Os operadores binários permitem modificar bits individuais de uma dada variável inte-
gral. Os operadores binários comuns permitem alternar os bits para a esquerda ou direita
ou realizar operações binárias “and” “not” “or” e “OR exclusivo” (exclusive or – XOR)
com dois números. Os operadores Shift-left e Shift-right são shl e shr, respectivamente, e
são semelhantes aos operadores << and >> do C#. Os outros operadores binários do
Delphi são suficientemente fáceis de lembrar: and, not, or e xor. A Tabela 5.3 lista os opera-
dores de binários.

TABELA 5.3 Operadores binários


Operador Delphi C# Visual Basic.NET

And and & And


Not not ~ Not
Or or ¦ Or
Xor xor ^ Xor
Shift-left shl << Nenhum
Shift-right shr >> Nenhum

Procedures para incrementar e decrementar


Os operadores de incremento e decremento fornecem um meio conveniente de adicio-
nar ou subtrair a partir de uma dada variável inteira. Na verdade, o Delphi não fornece
operadores de incremento e decremento verdadeiramente bons como os operadores ++ e
-- do C#, mas fornece procedures chamadas Inc( ) e Dec( ) concebidas para esse propósi-
to.
Você pode chamar Inc( ) ou Dec( ) com um ou dois parâmetros. Por exemplo, as
duas linhas de código a seguir incrementam e decrementam variable, respectivamente,
1:

Inc(variable);

Dec(variable);

As duas linhas a seguir incrementam ou decrementam variable por 3:

Inc(variable, 3);

Dec(variable, 3);

A Tabela 5.4 compara os operadores de incremento e decremento de diferentes lin-


guagens.
Tipos na linguagem Delphi 59

TABELA 5.4 Operadores de incremento e decremento


Operador Delphi C# Visual Basic.NET
Incremento Inc( ) ++ Nenhum
Decremento Dec( ) -- Nenhum

Observe que os operadores ++ e -- do C# retornam um valor, enquanto Inc( ) e Dec( )


não. As funções Succ( ) e Pred( ) do Delphi retornam um valor; entretanto, esses valores
não modificam o parâmetro.

Operadores do tipo “fazer e atribuir”


Não estão presentes na linguagem Delphi os úteis operadores “fazer e atribuir” como os
encontrados em C#. Esses operadores, como += e *=, realizam uma operação aritmética
(neste caso, uma adição e uma multiplicação) antes de fazer a atribuição. No Delphi, esse
tipo de operação deve ser realizado utilizando dois operadores separados. Portanto, este
código em C#:

x += 5;

torna-se este no Delphi

x := x + 5;

os úteis operadores “fazer e atribuir” presentes na linguagem Delphi

Tipos na linguagem Delphi


Um dos melhores recursos da linguagem Delphi, a tipificação forte, torna-a excelente para
a plataforma .NET. Isso significa que variáveis reais passadas para procedures e funções de-
vem ser do mesmo tipo dos parâmetros formais identificados na definição de procedure
ou de função – muito pouco foi feito em termos de conversão de tipo implícita. A natureza
da tipificação forte do Delphi permite que ele realize uma verificação de razão do seu códi-
go – para assegurar que você não está tentando colocar um quadrado em algo redondo.
Afinal de contas, as correções de bug mais fáceis são aquelas que o compilador informa!

Objetos em todos os lugares!


Uma das noções mais importantes com relação a tipos fundamentais na versão .NET do
compilador Delphi é que todos são tipos por valor capazes de sofrer uma conversão im-
plícita para classes. Essa conversão entre valor e objeto e entre objeto e valor é conhecida,
na terminologia .NET, como empacotar [boxing] e desempacotar [unboxing]. Inteiros,
strings, pontos flutuantes e toda a parte restante não são implementados como conceitos
primitivos no compilador, como no Win32; são mapeados para tipos por valor forneci-
dos pelo .NET Framework ou pela RTL ou VCL da Borland. Diferentemente do Delphi
Win32, esses tipos por valor podem ter suas próprias procedures e funções além daquelas
disponíveis nas classes correspondentes criadas pelo empacotamento e desempacota-
60 Capítulo 5 A Linguagem Delphi

mento implícito desses tipos. Isso permite um tipo de sintaxe que talvez seja estranho
para aqueles oriundos de compiladores nativos (embora talvez familiar para aqueles com
experiência em linguagens como Java ou Smalltalk) :
var
S: string;
I: Integer;
begin
I := 42;
S := I.ToString; // pode chamar um método em um Integer!
end

NOTA
O tipo string é utilizado intensamente no desenvolvimento. Isso não significa que outros tipos
não o sejam, mas há algumas considerações especiais ao utilizar strings. O Capítulo 11 discute es-
pecificamente strings, portanto, você não verá uma discussão detalhada sobre elas neste capítulo.

Uma comparação de tipos


O Delphi abrange a maioria dos tipos primitivos disponíveis no CLR. A Tabela 5.5 com-
para e contrasta os tipos básicos da linguagem Delphi com os tipos do C# e CLR. Essa ta-
bela também indica se cada tipo é compatível com a CLS.

TABELA 5.5 Uma comparação de tipos entre Delphi, C# e CLR


Intervalo variável Delphi C# CLR Compatível
com CLS?
inteiro de 8 bits com sinal ShortInt sbyte System.SByte Não
inteiro de 8 bits sem sinal Byte byte System.Byte Sim
inteiro de 16 bits com sinal SmallInt short System.Int16 Sim
inteiro de 16 bits sem sinal Word ushort System.UInt16 Não
inteiro de 32 bits com sinal Integer int System.Int32 Sim
inteiro de 32 bits sem sinal Cardinal uint System.UInt32 Não
inteiro de 64 bits com sinal Int64 long System.Int64 Sim
inteiro de 64 bits sem sinal UInt64 ulong System.UInt64 Não
número de pontos flutuantes Single float System.Single Sim
de precisão simples
número de pontos flutuantes Double double System.Double Sim
de precisão dupla
ponto decimal fixo Nenhum decimal System.Decimal Sim
ponto decimal fixo do Delphi Currency Nenhum Nenhum Não
data/hora TDateTime* Nenhum System.DateTime Sim

*TDateTime é um registro que empacota um System.DateTime com sobrecargas de métodos e de operadores, o que faz que
ele se comporte quase como System.DateTime, mas com um comportamento adicional que é compatível com o tipo Tdate-
Time do Delphi Win32.
Tipos na linguagem Delphi 61

TABELA 5.5 Continuação


Intervalo variável Delphi C# CLR Compatível
com CLS?
variante Variant, Nenhum Nenhum Não
OleVariant
caractere de 1 byte AnsiChar Nenhum Nenhum Não
caractere de 2 bytes Char, WideChar char System.Char Sim
string de byte de ShortString Nenhum Nenhum Não
comprimento fixo
string de 1 byte dinâmica AnsiString Nenhum Nenhum Não
string de 2 bytes dinâmica string, string System.String Sim
WideString
booleano Boolean bool System.Boolean Nenhum

Caracteres
O Delphi fornece três tipos de caracteres:

— WideChar – Este caractere compatível com CLS tem dois bytes de tamanho e repre-
senta um caractere Unicode.
— Char – Este é um alias para WideChar. Nas versões Win32 do Delphi, Char era um
AnsiChar.

— AnsiChar – Um caractere ANSI de um byte da velha escola.

Nunca tente supor o tamanho de um Char (ou, nesse sentido, de qualquer outro tipo)
no seu código. Em vez disso, você deve utilizar a função SizeOf( ) onde apropriado.

NOTA
A procedure padrão SizeOf( ) retorna o tamanho, em bytes, de um tipo ou variável.

Tipos Variant
A classe Variant fornece uma implementação de um tipo capaz de servir como uma espé-
cie de contêiner para muitos outros tipos. Isso significa que uma variante pode alterar o
tipo de dados a que ela se refere em tempo de execução (runtime). Por exemplo, o código
a seguir irá compilar e executar adequadamente:

var
V: Variant;
I: IInterface;
begin
V := 'Delphi is Great!'; // Variante contém uma string
V := 1; // Variante agora contém um Integer
62 Capítulo 5 A Linguagem Delphi

V := 123.34; // Variante agora contém um ponto flutuante


V := True; // Variante agora contém um booleano
V := I; // Variante agora contém uma interface
end;

As variantes suportam uma ampla variedade de tipos, incluindo tipos complexos


como arrays e interfaces. O código a seguir na unit Borland.Vcl.Variants mostra os vários
tipos suportados, identificados por valores conhecidos como TVarTypes:

type
TVarType = Integer;

const
// Decimal ¦ Outros nomes para esse tipo
varEmpty = $0000; // = 0 ¦ Sem sinal, Nil
varNull = $0001; // = 1 ¦ Null, System.DBNull
varSmallInt = $0002; // = 2 ¦ I2, System.Int16
varInteger = $0003; // = 3 ¦ I4, System.Int32
varSingle = $0004; // = 4 ¦ R4, System.Single
varDouble = $0005; // = 5 ¦ R8, System.Double
varCurrency = $0006; // = 6 ¦ Borland.Delphi.System.Currency
varDate = $0007; // = 7 ¦ Borland.Delphi.System.TdateTime
varString = $0008; // = 8 ¦ WideString, System.String
varError = $000A; // = 10 ¦ Exception, System.Exception
varBoolean = $000B; // = 11 ¦ Bool, System.Boolean
varObject = $000C; // = 12 ¦ TObject, System.Object
varDecimal = $000E; // = 14 ¦ System.Decimal
varShortInt = $0010; // = 16 ¦ I1, System.SByte
varByte = $0011; // = 17 ¦ U1, System.Byte
varWord = $0012; // = 18 ¦ U2, System.UInt16
varLongWord = $0013; // = 19 ¦ U4, System.UInt32
varInt64 = $0014; // = 20 ¦ I8, System.Int64
varUInt64 = $0015; // = 21 ¦ U8, System.UInt64
varChar = $0016; // = 22 ¦ WideChar, System.Char
varDateTime = $0017; // = 23 ¦ System.DateTime;

varFirst = varEmpty;
varLast = varDateTime;

varArray = $2000; // = 8192 ¦ System.Array, Arrays dinâmicos


varTypeMask = $0FFF; // = 4095
varUndefined = -1;
Tipos na linguagem Delphi 63

Como uma função da atribuição de valor, as variantes são capazes de fazer uma coer-
ção de si próprias em outros tipos, conforme necessário. Por exemplo:

var
V: Variant;
I: Integer;
begin
V := '1';
// V contém string '1'
I := V;
// Converte implicitamente em Integer, I agora é 1
end;

Fazendo coerção de tipos em variantes


Você pode fazer uma coerção de tipo explícita de expressões no tipo Variant. Por exem-
plo, a expressão

Variant(X);

resulta em um tipo Variant cujo código de tipo corresponde ao resultado da expressão X,


que deve ser um inteiro, ponto flutuante, símbolo de moeda, string, caractere, data/hora,
classe ou tipo booleano.
Você também pode fazer uma coerção de tipo de uma variante em um tipo de dados
simples. Por exemplo, dada a atribuição

V := 1.6;

onde V é uma variável do tipo Variant, as expressões a seguir terão estes resultados:

S := string(V);
// S conterá a string '1.6';
// I é arredondado para o valor de Integer mais próximo, nesse caso: 2.
I := Integer(V);
B := Boolean(V);
// B contém True, uma vez que V não é igual a 0
D := Double(V);
// D contém o valor 1.6

Esses resultados são ditados por certas regras de conversão de tipos aplicáveis aos ti-
pos Variant. Essas regras são definidas em detalhes no Delphi Language Guide.
A propósito, no exemplo anterior, não é necessário fazer uma coerção de tipo da va-
riante em um outro tipo de dados a fim de fazer a atribuição. O código a seguir também
funcionaria:
64 Capítulo 5 A Linguagem Delphi

V := 1.6;
S := V;
I := V;
B := V;
D := V;

Se múltiplas variantes são utilizadas em uma expressão, pode haver uma lógica de
código muito mais oculta anexada a essas coerções de tipo implícitas. Nesses casos, se ti-
ver certeza do tipo que uma variante contém, é melhor fazer a coerção de tipo explicita-
mente para esse tipo a fim de acelerar a operação.

Variantes em expressões
Você pode utilizar variantes em expressões com os seguintes operadores: +, =, *, /, div, mod,
shl, shr, and, or, xor, not, :=, < >, <, >, <=, e >=. As funções Inc( ), Dec( ), Trunc( ) e Round( ) pa-
drão também são válidas com expressões de variantes.
Ao utilizar variantes em expressões, o Delphi sabe como realizar as operações com
base nos tipos contidos dentro de uma variante. Por exemplo, se duas variantes, V1 e V2,
contiverem inteiros, a expressão V1 + V2 resultará na adição dos dois inteiros. Entretanto,
se V1 e V2 contiverem strings, o resultado será uma concatenação das duas strings. O que
acontece se V1 e V2 contiverem dois tipos de dados diferentes? O Delphi utiliza certas re-
gras de promoção a fim de realizar a operação. Por exemplo, se V1 contiver a string '4.5' e
V2 contiver um número de ponto flutuante, V1 será convertida em um ponto flutuante e
então adicionada à V2. O código a seguir ilustra isso:

var
V1, V2, V3: Variant;
begin
V1 := '100';
// Um tipo String
V2 := '50';
// Um tipo String
V3 := 200;
// Um tipo Integer
V1 := V1 + V2 + V3;
end;

Com base no que acabamos de mencionar sobre regras de promoção, à primeira vis-
ta pareceria que o código anterior resultaria no valor 350 como um inteiro. Entretanto, se
examinar mais atentamente, você verá que esse não é o caso. Como a ordem de prece-
dência vai da esquerda para a direita, a primeira equação executada é V1 + V2. Como essas
duas variantes se referem a strings, uma concatenação de string é realizada, resultando
na string '10050'. Esse resultado é então adicionado ao valor de inteiro contido pela vari-
ante V3. Como V3 é um inteiro, o resultado '10050' é convertido em um inteiro e adiciona-
do à V3, fornecendo assim um resultado final de 10250.
Tipos na linguagem Delphi 65

O Delphi promove as variantes de acordo com o tipo numericamente maior na


equação para executar o cálculo com sucesso. Entretanto, quando tentamos uma opera-
ção em duas variantes que não fazem sentido ao Delphi, é levantada uma exceção EVari-
antTypeCast. O código a seguir ilustra isso:

var
V1, V2: Variant;
begin
V1 := 77;
V2 := 'hello';
V1 := V1 / V2;
// Levanta uma exceção.
end;

Como afirmado anteriormente, às vezes uma boa idéia é fazer explicitamente a coer-
ção de uma variante em um tipo de dados específico se você conhecer o tipo e se ele for
utilizado em uma expressão. Considere esta linha de código:

V4 := V1 * V2 / V3;

Antes de um resultado poder ser gerado para essa equação, cada operação é tratada
por uma função em tempo de execução que passa por vários ciclos para determinar a
compatibilidade dos tipos que as variantes representam. Em seguida, as conversões ocor-
rem nos tipos de dados apropriados. Isso resulta em um grande volume de overheads e
aumento do tamanho de código. Obviamente, uma solução melhor é não utilizar varian-
tes. Entretanto, quando necessário, você também pode fazer a coerção explícita das vari-
antes de modo que as operações sejam convertidas em tempo de compilação:

V4 := Integer(V1) * Double(V2) / Integer(V3);

Tenha em mente que isso supõe que você conhece os tipos de dados em que as va-
riantes podem ser convertidas com sucesso.

Empty e Null
Dois valores especiais para variantes merecem uma breve discussão. O primeiro é Unassig-
ned, que significa que um valor ainda não foi atribuído à variante. Esse é o valor inicial da
variante à medida que ela entra no escopo. O outro é Null, que é diferente de Unassigned
pelo fato de que ele na verdade representa o valor Null em oposição a uma falta de valor.
Essa distinção entre nenhum valor e um valor Null é especialmente importante quando
aplicada a valores de campos de uma tabela de banco de dados. Você aprenderá a progra-
mação com bancos de dados na Parte IV.
Uma outra diferença é que tentar realizar operações com uma variante que contém
um valor Unassigned resultará na conversão do valor em 0 para operações numéricas ou
em uma string vazia para operações de string. Entretanto, o mesmo não é verdade para
variantes que contém um valor Null. Se uma variante que faz parte de uma equação con-
66 Capítulo 5 A Linguagem Delphi

tiver um valor Null, operar na variante com esse valor poderá resultar em uma exceção
EVariantTypeCast .
Se quiser atribuir ou comparar uma variante com um desses dois valores especiais, a
unit System.Vcl.Variants definirá duas variantes, apropriadamente chamadas Unassigned e
Null, que podem ser utilizadas para atribuição e comparação.

C O N T R O L A N D O O CO M P O R T A M E N T O D E V A R I A N T E S
Você pode controlar se uma exceção é levantada ao realizar uma operação aritmética com uma
variante Null configurando a propriedade Variant.NullStrictOperations como True. Os outros
flags Variant que controlam o comportamento Null e as conversões string e Boolean incluem

NullEqualityRule: TNullCompareRule;
NullMagnitudeRule: TNullCompareRule;
NullAsStringValue: string;
NullStrictConvert: Boolean;
BooleanToStringRule: TBooleanToStringRule;
BooleanTrueAsOrdinalValue: Integer;

Tipos definidos pelo usuário


Inteiros, strings e números de ponto flutuante costumam não ser suficientes para repre-
sentar adequadamente variáveis nos problemas do mundo real que os programadores
devem tentar resolver. Nesses casos, você deve criar seus próprios tipos para melhor re-
presentar as variáveis no problema atual. No Delphi, esses tipos definidos pelo usuário
normalmente vêm na forma de registros (records) ou classes; você declara esses tipos utili-
zando a palavra-chave Type.

Arrays
A linguagem Delphi permite criar arrays de qualquer tipo de variável. Por exemplo, uma
variável declarada como um array de oito de inteiros é lida desta maneira:

var
A: Array[0..7] of Integer;

Essa instrução é equivalente à declaração em C# a seguir:

int A[8];

Ela também é equivalente à seguinte instrução no Visual Basic .NET:

Dim A(8) as Integer

Os arrays no Delphi têm uma propriedade especial que os diferencia de outras lin-
guagens: eles não têm de estar baseados em certo número. Você, portanto, pode declarar
um array de três elementos que inicia em 28, como no exemplo a seguir:
Tipos definidos pelo usuário 67

var
A: Array[28..30] of Integer;

Como não é garantido que os arrays no Delphi iniciam em 0 ou 1, você deve ter cui-
dado ao iterar por elementos de array em um loop for. O compilador fornece funções
predefinidas chamadas Low( ) e High( ), que retornam os limites inferior e superior de
uma variável de array ou tipo, respectivamente. Seu código estará menos propenso a er-
ros e mais fácil de manter se você utilizar essa função para controlar o loop for, como
mostrado aqui:

var
A: array[28..30] of Integer;
i: Integer;
begin
for i := Low(A) to High(A) do
// Não codifique manualmente o loop for!
A[i] := i;
end;

Para especificar múltiplas dimensões, utilize uma lista delimitada por vírgulas dos li-
mites:

var
// Array bidimensional de Integer:
A: array[1..2, 1..2] of Integer;

Para acessar um array multidimensional, utilize vírgulas para separar cada dimensão
dentro de um conjunto de colchetes:

I := A[1, 2];

Arrays dinâmicos
Arrays dinâmicos são arrays alocados dinamicamente pelo fato de as dimensões não se-
rem conhecidas em tempo de compilação. Para declarar um array dinâmico, simples-
mente declare um array sem incluir as dimensões, como este:

var
// array dinâmico de string:
SA: array of string;

Antes de poder utilizar um array dinâmico, você deve utilizar a procedure Set-
Length( ) para alocar memória ao array:
68 Capítulo 5 A Linguagem Delphi

begin
// alocar espaço a 33 elementos:
SetLength(SA, 33);

Depois que memória foi alocada, você pode acessar os elementos do array dinâmico
como um array normal:

SA[0] := 'Pooh likes hunny';


OutraString := SA[0];

NOTA
Os arrays dinâmicos sempre são baseados em zero.

Os arrays dinâmicos têm seu tempo de vida gerenciado pelo runtime do .NET, por-
tanto não há necessidade (e, de fato, nenhuma maneira) de liberá-los depois de utili-
zá-los; isso será feito pelo garbage collection (coleta de lixo) em algum momento depois
de saírem de escopo. Entretanto, pode chegar o momento em que você quer solicitar ao
runtime do .NET para remover o array dinâmico da memória antes de sair de escopo (por
exemplo, se ele utilizar uma grande quantidade de memória). Para fazer isso, você só pre-
cisa atribuir nil ao array dinâmico:

SA := nil; // solicita liberação de SA

Observe que atribuir nil não libera o SA; apenas libera uma referência a ele, uma vez
que poderia haver mais de uma variável referenciando o array indicado pelo SA. Quando
a última referência a SA for liberada, o garbage collection do .NET irá liberar a memória
ocupada pelo array na próxima verificação do garbage collection.
Arrays dinâmicos são manipulados com a semântica de referência, em vez da semân-
tica de valor, como ocorre com um array normal. Eis um teste rápido: qual é o valor de
A1[0] no final do fragmento de código a seguir?

var
A1, A2: array of Integer;
begin
SetLength(A1, 4);
A2 := A1;
A1[0] := 1;
A2[0] := 26;

A resposta correta é 26 porque a atribuição A2 := A1 não cria um novo array; em vez


disso, fornece ao A2 uma referência ao mesmo array de A1. Portanto, quaisquer modifica-
ções para A2 também afetarão A1. Se, em vez disso, quiser criar uma cópia completa de A1
em A2, utilize a procedure padrão Copy( ):

A2 := Copy(A1);
Tipos definidos pelo usuário 69

Depois que essa linha de código for executada, A2 e A1 serão dois arrays separados que
inicialmente irão conter os mesmos dados. As alterações em um deles não afetarão o ou-
tro. Você pode opcionalmente especificar o elemento inicial e o número de elementos a
ser copiado como parâmetros para Copy( ), como mostrado aqui:

// copia 2 elementos, iniciando no elemento um:


A2 := Copy(A1, 1, 2);

Os arrays dinâmicos também podem ser multidimensionais. Para especificar múlti-


plas dimensões, adicione um array of extra à declaração de cada dimensão:

var
// array dinâmico bidimensional de Integer:
IA: array of array of Integer;

Para alocar memória a um array dinâmico multidimensional, passe os tamanhos das


outras dimensões como parâmetros adicionais para SetLength( ):

begin
// IA será um array 5 x 5 de Integer
SetLength(IA, 5, 5);

Você acessa arrays dinâmicos multidimensionais da mesma maneira como acessa ar-
rays multidimensionais normais; cada elemento é separado por uma vírgula com um
único conjunto de colchetes:

IA[0,3] := 28;

A sintaxe no estilo do C para acessar arrays multidimensionais também é suportada:

IA[0][3] := 28;

Registros
Uma estrutura definida pelo usuário é chamada registro na linguagem Delphi e é o equi-
valente a struct em C# ou Type no Visual Basic .NET. Como um exemplo, eis uma defini-
ção de registro no Delphi e em definições equivalentes em C# e no Visual Basic .NET:

{ Delphi }
Type
MyRec = record
i: Integer;
d: Double;
end;

/* C# */
public struct MyRec
{
70 Capítulo 5 A Linguagem Delphi

int i;
double d;
}
'Visual Basic
Type MyRec
i As Integer
d As Double
End Type

Ao trabalhar com um registro, você utiliza o símbolo ponto para acessar seus cam-
pos. Eis um exemplo:

var
N: MyRec;
begin
N.i := 23;
N.d := 3.4;
end;

Também são suportados em registros métodos, sobrecargas de operadores e imple-


mentações com interface. Essa capacidade não era suportada nas versões anteriores do
compilador Delphi. Estes tópicos serão examinados em mais detalhes, com os tipos
class, mais adiante neste capítulo. O exemplo de código a seguir mostra, porém, a sinta-
xe para utilizar esses elementos com registros:

IBlah = interface
procedure bar;
end;

Foo = record(IBlah) // registro implementa a interface IBlah


AField: Integer;
procedure bar;
class operator Add(a, b: Foo): Foo; // operador + de sobrecarga
end;

Conjuntos
Conjuntos são tipos Delphi únicos sem nenhum equivalente em C# ou no Visual Basic
.NET. Os conjuntos fornecem um meio eficiente e conveniente de representar uma cole-
ção de valores AnsiChar ordinais ou enumerados. Você pode declarar um novo tipo de
conjunto utilizando as palavras-chave set of seguidas por um tipo ordinal ou subinterva-
lo de possíveis valores de conjuntos. Eis um exemplo:

type
TCharSet = set of AnsiChar; // possíveis membros: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday);
Tipos definidos pelo usuário 71

TEnumSet = set of TEnum; // pode conter qualquer combinação de membros TEnum

TSubrangeSet = set of 1..10; // possíveis membros: 1 - 10

Observe que um conjunto só pode conter até 256 elementos. Além disso, somente
tipos ordinais podem seguir as palavras-chave set of. Portanto, as declarações a seguir são
inválidas:

type
TIntSet = set of Integer; // Inválido: elementos em excesso
TStrSet = set of string; // Inválido: não é um tipo ordinal

Os conjuntos armazenam seus elementos internamente como bits individuais, o


que os torna muito eficientes em termos de velocidade e uso de memória.

NOTA
Se você estiver portando código Win32 para o .NET, lembre-se de que os caracteres têm 2 bytes
no mundo do .NET e 1 byte no Win32. Isso significa que uma declaração set of Char no .NET é re-
baixada pelo compilador a um conjunto de AnsiChar que, potencialmente, pode alterar o signifi-
cado do código. Nesse sentido, o compilador emitirá um alerta, recomendando a utilização de set
of AnsiChar explicitamente.

Utilizando conjuntos
Utilize colchetes ao construir um valor de conjunto literal a partir de um ou mais ele-
mentos. O código a seguir demonstra como declarar variáveis do tipo set e atribuir valo-
res a elas:

type
TCharSet = set of AnsiChar; // possíveis membros: #0 - #255
TEnum = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TEnumSet = set of TEnum; // pode conter qualquer combinação de membros TEnum

var
CharSet: TCharSet;
EnumSet: TEnumSet;
SubrangeSet: set of 1..10; // possíveis membros: 1 - 10
AlphaSet: set of 'A'..'z'; // possíveis membros: 'A' - 'z'

begin
CharSet := ['A'..'J', 'a', 'm'];
EnumSet := [Saturday, Sunday];
SubrangeSet := [1, 2, 4..6];
AlphaSet := [ ]; // Vazio; nenhum elemento
end;
72 Capítulo 5 A Linguagem Delphi

Operadores de conjuntos
A linguagem Delphi fornece vários operadores para manipular conjuntos. Você pode uti-
lizar esses operadores para determinar a associação, união, diferença e interseção de con-
juntos.

Utilize o operador in para determinar se um dado elemento está contido em


um conjunto particular. Por exemplo, o código a seguir seria utilizado para determinar se
o conjunto CharSet mencionado anteriormente contém a letra 'S':

if 'S' in CharSet then


// faz algo;

O código a seguir determina se falta o membro Monday em EnumSet:

if not (Monday in EnumSet) then


// faz algo;

Utilize os operadores + e – ou as procedures Include( ) e Exclude( )


para adicionar e remover elementos para e a partir de uma variável de conjunto:

Include(CharSet, 'a'); // adiciona 'a' ao conjunto


CharSet := CharSet + ['b']; // adiciona 'b' ao conjunto
Exclude(CharSet, 'x'); // remove 'z' do conjunto
CharSet := CharSet - ['y', 'z']; // remove 'y' e 'z' do conjunto

DICA
Quando possível, utilize Include( ) e Exclude( ) para adicionar e remover um único elemento para
e de um conjunto, em vez dos operadores + e –, uma vez que as procedures são mais eficazes.

Utilize o operador * para calcular a interseção de dois conjuntos. O resulta-


do da expressão Set1 * Set2 é um conjunto que contém todos os membros que Set1 e Set2
têm em comum. Por exemplo, o código a seguir poderia ser utilizado como um meio efi-
caz de determinar se um dado conjunto contém múltiplos elementos:

if ['a', 'b', 'c'] * CharSet = ['a', 'b', 'c'] then

// faz algo

Código inseguro
Neste ponto, aqueles que têm experiência em Delphi para Win32 poderiam pensar “até
aqui, tudo bem, mas o que aconteceu com os ponteiros?”. Embora os ponteiros estejam
na linguagem, eles são considerados inseguros (unsafe) do ponto de vista do .NET por-
que permitem acesso direto à memória. Portanto, para empregar ponteiros em suas apli-
Tipos definidos pelo usuário 73

cações, você precisará fazer com que o compilador saiba que ele deve permitir código in-
seguro. Para escrever código inseguro, você deve

1. Incluir a diretiva{$UNSAFECODE ON} à unit que contém o código inseguro.

2. Marcar as funções que contêm o código inseguro com a palavra-chave unsafe.

O código inseguro irá compilar no Delphi com sucesso:

{$UNSAFECODE ON}

procedure RunningWithScissors; unsafe;


var
A: array[0..31] of Char;
P: PChar; // PChar é um tipo inseguro
begin
A := 'safety first'; // preenche o array de caracteres
P := @A[0]; // aponta para o primeiro elemento
P[0] := 'S'; // altera o primeiro elemento
MessageBox.Show(A); // mostra o array alterado
end;

Observe o uso de @, ou operador de endereço, para obter o endereço de um bit de da-


dos.

DICA
O código inseguro é totalmente desencorajado no .NET, porque uma aplicação insegura não
atenderá aos requisitos da ferramenta PEVerify do .NET e, portanto, poderá estar sujeita a maiores
restrições de segurança. Entretanto, o código inseguro pode ser uma excelente maneira de suprir
a lacuna entre o mundo do Win32 e o do .NET durante um período de migração. Talvez seja difícil
portar uma grande aplicação inteira do Win32 para o .NET de uma só vez; o código inseguro, po-
rém, permite portar uma parte por vez, mantendo o código antigo como inseguro até ele ser com-
pletamente movido para o código seguro.

Você pode assumir que o código nesta parte do capítulo deve ser compilado com as
diretivas $UNSAFECODE e unsafe.

Ponteiros
Um ponteiro é uma variável que contém uma posição de memória. Você já viu um
exemplo de um ponteiro no tipo PChar anteriormente neste capítulo. O tipo de ponteiro
genérico do Delphi é chamado, adequadamente, Pointer. Um Pointer às vezes é chamado
ponteiro não-tipificado porque contém somente um endereço de memória e o compila-
dor não mantém nenhuma informação sobre os dados para os quais ele aponta. Essa no-
ção, porém, vai contra a natureza segura dos tipos do Delphi, assim; normalmente, os
ponteiros no seu código serão ponteiros tipificados.
74 Capítulo 5 A Linguagem Delphi

NOTA
No .NET, o tipo System.IntPtr é utilizado para representar um ponteiro opaco não-tipificado.

Os ponteiros tipificados são declarados utilizando o operador (ou ponteiro) ^ na se-


ção Type do seu programa. Os ponteiros tipificados ajudam o compilador a monitorar
exatamente para qual espécie de tipo um ponteiro particular aponta, permitindo assim
que o compilador monitore o que você está fazendo (e pode fazer) com uma variável de
ponteiro. Eis algumas declarações típicas para ponteiros:

Type
PInt = ^Integer;// PInt agora é um ponteiro para um Integer
Foo = record // Um tipo de registro
GobbledyGook: string;
Snarf: Double;
end;
PFoo = ^Foo; // PFoo é um ponteiro para um tipo foo
var
P: Pointer; // Ponteiro não-tipificado
P2: PFoo; // Instância de PFoo

NOTA
Programadores em C/C++ perceberão a semelhança entre o operador ^ do Delphi e o operador *
do C. O tipo Pointer do Delphi corresponde ao tipo void * do C/C++.

Lembre-se de que uma variável de ponteiro armazena apenas um endereço na me-


mória. Alocação de espaço para qualquer coisa que o ponteiro aponta é seu trabalho
como programador. Versões anteriores do Delphi tinham várias funções que permitiam
ao desenvolvedor alocar e desalocar memória; entretanto, como a alocação direta de me-
mória raramente ocorre no .NET, em geral ela é realizada somente via a classe System.Run-
time.InteropServices.Marshal. O código a seguir demonstra como utilizar essa classe para
criar e liberar um bloco de memória e também como copiar alguns dados de array dentro
e fora do bloco.

{$UNSAFECODE ON}

type
TArray = array[0..31] of Char;

procedure ArrayCopy; unsafe;


var
A1: TArray;
A2: array of char;
P: IntPtr;
begin
A1 := 'safety first'; // preenche o array de caracteres
Tipos definidos pelo usuário 75

SetLength(A2, High(TArray) + 1);


P := Marshal.AllocHGlobal(High(TArray) + 1);
try
Marshal.Copy(A1, 0, P, High(TArray) + 1);// copia A1 para temp
Marshal.Copy(P, A2, 0, High(TArray) + 1);// copia temp para A2
MessageBox.Show(A2);// mostra o array alterado
finally
Marshal.FreeHGlobal(P);
end;
end;

Se quiser acessar os dados para os quais um ponteiro particular aponta, siga o nome
da variável de ponteiro com o operador ^. Esse método é conhecido como desreferencia-
mento de ponteiro. O código a seguir mostra como trabalhar com ponteiros:

procedure PointerFun; unsafe;


var
I: Integer;
PI: ^Integer;
begin
I := 42;
PI := @I; // aponta para I
PI^ := 24;// altera I
MessageBox.Show(I.ToString);
end;

O compilador Delphi emprega uma verificação estrita de tipos nos tipos de ponteiro.
Por exemplo, as variáveis a e b no exemplo a seguir não são compatíveis a tipos:

var
a: ^Integer;
b: ^Integer;

Por comparação, as variáveis a e b na declaração equivalente em C são compatíveis a


tipos:

int *a;
int *b

A linguagem Delphi cria um tipo único para cada declaração entre tipos e ponteiros;
assim, você deve criar um tipo identificado se quiser atribuir valores de a ao b, como mos-
trado aqui:

type
PtrInteger = ^Integer;// cria tipo identificado
76 Capítulo 5 A Linguagem Delphi

var
a: PtrInteger;
b: PtrInteger; // agora a e b são compatíveis

NOTA
Se um ponteiro apontar para nada (seu valor é zero), dizemos que seu valor é nil, e costuma ser
chamado ponteiro nil ou null.

Strings terminadas por caractere nulo


Anteriormente, este capítulo mencionou que o Delphi tem três tipos diferentes de
string terminada por caractere nulo: PChar, PAnsiChar e PWideChar. Como seus nomes indi-
cam, cada um representa uma string terminada por caractere nulo de cada um dos três
tipos de caracteres do Delphi. No .NET, PChar é um alias para PWideChar, enquanto PChar é
um alias para PAnsiChar no Win32. Neste capítulo, denominamos cada um desses tipos
de string genericamente como PChar. O tipo PChar no Delphi existe principalmente para
retrocompatibilidade com versões anteriores. Um PChar é definido como um ponteiro
para uma string seguido por um valor nulo (zero). Como é um ponteiro, não-ge-
renciado, inseguro e bruto, a memória para tipos PChar não é automaticamente alocada
e gerenciada pelo .NET.
Na versão Win32 do Delphi, PChars são atribuições compatíveis com strings. Entre-
tanto, no .NET, esses tipos não são mais compatíveis, o que torna a utilização deles muito
menos comum no mundo do .NET.

Registros variantes
A linguagem Delphi também suporta registros variantes, o que permite que diferentes
dados sobrescrevam a mesma parte da memória no registro. Os registros variantes permi-
tem que cada campo de dados sobrescrito seja acessado independentemente; não devem
ser confundidos com o tipo de dados Variant. Se sua formação for em C, você reconhece-
rá que os registros variantes apresentam o mesmo conceito de uma union dentro de uma
struct em C. O código a seguir mostra um registro variante em que um Double, um Integer
e um Char ocupam o mesmo espaço de memória:

type
TVariantRecord = record
NullStrField: PChar;
IntField: Integer;
case Integer of
0: (D: Double);
1: (I: Integer);
2: (C: Char);
end;
Tipos definidos pelo usuário 77

NOTA
As regras da linguagem Delphi afirmam que a parte variante de um registro não pode ser nenhum
tipo gerenciado pelo tempo de vida (lifetime-managed type). Isso inclui classes, interfaces, varian-
tes, arrays dinâmicos e strings.

Eis o equivalente em C à declaração de tipo anterior:

struct TUnionStruct
{
char * StrField;
int IntField;
union u
{
double D;
int i;
char c;
};
};

Como registros variantes lidam com um layout explícito de memória, eles também
são considerados tipos inseguros.

NOTA
O layout de memória do registro pode ser controlado pelo desenvolvedor em .NET usando os atri-
butos StructLayout e FieldOffset.

Classes e objetos
Uma classe é um tipo por valor que pode conter dados, propriedades, métodos e opera-
dores. O modelo de objetos do Delphi será discutido detalhadamente mais adiante na se-
ção “Utilizando objetos do Delphi” deste capítulo, portanto esta seção abrange somente
a sintaxe básica das classes Delphi. Uma classe é definida desta maneira:

Type
TChildObject = class(TParentObject)
public
SomeVar: Integer;
procedure SomeProc;
end;

Essa declaração é equivalente à declaração em C# a seguir:

public class TChildObject: TParentObject


{
public int SomeVar;
78 Capítulo 5 A Linguagem Delphi

public void SomeProc( ) { };


}
Os métodos são definidos da mesma maneira que as procedures e funções normais
(discutidas na seção “Procedures e funções”) com a adição do nome da classe e o símbolo
ponto:

procedure TChildObject.SomeProc;
begin
{ o código da procedure entra aqui }
end;

O símbolo . do Delphi é funcionalmente semelhante ao operador . do C# e Visual


Basic .NET ao referenciar membros.

Aliases de tipos
A linguagem Delphi tem a capacidade de criar novos nomes, ou aliases, para tipos já
definidos. Por exemplo, se quiser criar um novo nome para um Integer chamado
MyReallyNiftyInteger, você poderia fazer isso utilizando este código:

type
MyReallyNiftyInteger = Integer;

O alias do tipo definido recentemente é, em todos os aspectos, compatível com o


tipo ao qual ele é um alias, o que significa, nesse caso, que você pode utilizar MyReally-
NiftyInteger em qualquer lugar em que utilizaria Integer.
É possível, porém, definir aliases fortemente tipificados que são considerados tipos
novos e únicos pelo compilador. Para fazer isso, utilize a palavra reservada type desta ma-
neira:
type
MyOtherNeatInteger = type Integer;

Utilizando essa sintaxe, o tipo MyOtherNeatInteger será convertido em um Integer,


quando necessário, para fins de atribuição, mas MyOtherNeatInteger não será compatível
com Integer se utilizado em parâmetros var e out. Portanto, o código a seguir é sintatica-
mente correto:

var
MONI: MyOtherNeatInteger;
I: Integer;
begin
I := 1;
MONI := I;

Por outro lado, este código não irá compilar:


Typecasting e conversão de tipos 79

procedure Goon(var Value: Integer);


begin
// algum código
end;

var
M: MyOtherNeatInteger;
begin
M := 29;
Goon(M); // Erro: M não é compatível com Integer para passagem por referência

Além destas questões de compatibilidade de tipo impostas pelo compilador, este


também gera informações sobre tipos em tempo de execução para aliases fortemente ti-
pificados. Isso permite criar editores de propriedade únicos para tipos simples, como
veremos no Capítulo 8.

Typecasting e conversão de tipos


A técnica de coerção de tipo permite forçar o compilador a visualizar uma variável de um
tipo como um outro tipo. Por causa da natureza fortemente tipificada do Delphi, você
descobrirá que o compilador é muito meticuloso quanto à correspondência de tipos nos
parâmetros formais e reais de uma chamada de função. Daí, ocasionalmente você terá de
fazer a coerção de uma variável de certo tipo em uma variável de um outro tipo para agra-
dar o compilador. Suponha, por exemplo, que você precise atribuir o valor de um carac-
tere a uma variável Word:

var
c: char;
w: Word;
begin
c := 's';
w := c; // compilador reclama dessa linha
end.

Na sintaxe a seguir, uma coerção de tipo é requerida para converter c em uma Word.
Na realidade, uma coerção de tipo informa o compilador de que você realmente sabe o
que está fazendo e quer converter um tipo em outro:

var
c: Char;
w: Word;
begin
c := 's';
w := Word(c); // compilador não reclama desta linha
end.
80 Capítulo 5 A Linguagem Delphi

NOTA
Você pode fazer a coerção de uma variável de certo tipo em um outro tipo somente se o tamanho
dos dados das duas variáveis for o mesmo. Por exemplo, você não pode fazer a coerção de um
Double como um Integer. Para converter um tipo de ponto flutuante em um inteiro, utilize as fun-
ções Trunc( ) ou Round( ). Para converter um inteiro em um valor de ponto flutuante, utilize o
operador de atribuição: FloatVar := IntVar. Você também pode converter entre tipos se opera-
dores de conversão implícitos ou explícitos estiverem definidos para uma classe (outras informa-
ções sobre sobrecarga de operadores mais adiante).

A linguagem Delphi também suporta uma variedade especial de conversões entre ob-
jetos utilizando o operador as. O operador as funciona de maneira idêntica a uma coerção
de tipo padrão, exceto pelo fato de que ele produz null em vez de levantar uma exceção na
falha.

Strings de recursos
O Delphi fornece a capacidade de colocar strings de recursos (resource strings) direta-
mente no código-fonte utilizando a cláusula resourcestring. Strings de recursos são
strings literais (normalmente aquelas exibidas para o usuário) que estão fisicamente loca-
lizadas em um recurso anexado à aplicação ou biblioteca em vez de incorporadas no có-
digo-fonte. Seu código-fonte referencia as strings de recursos em vez de literais de string.
Separando as strings do código-fonte, sua aplicação pode ser convertida mais facilmente
por meio da adição de strings de recursos em uma linguagem diferente. As strings de re-
cursos são declaradas na forma de identificador = literal de string na cláusula resourcestring,
como mostrado aqui:

resourcestring
ResString1 = 'Resource string 1';
ResString2 = 'Resource string 2';
ResString3 = 'Resource string 3';

Sintaticamente, as strings de recursos podem ser utilizadas no seu código-fonte de


maneira semelhante a uma função que retorna uma string:

resourcestring
ResString1 = 'hello';
ResString2 = 'world';

var
String1: string;

begin
String1 := ResString1 + ' ' + ResString2;
.
.
.
end;
Testando condições 81

Testando condições
Esta seção compara construções if e case no Delphi com construções semelhantes em C#
e no Visual Basic .NET. Supomos que você já utilizou esses tipos de construções progra-
máticas, assim não investiremos muito tempo explicando-os.

A instrução if
Uma instrução if permite determinar se certas condições foram cumpridas antes de exe-
cutar um bloco particular do código. Como exemplo, eis uma instrução if no Delphi, se-
guida por definições equivalentes em C# e no Visual Basic .NET:

{ Delphi }
if x = 4 then y := x;

/* C# */
if (x == 4) y = x;

'Visual Basic
If x = 4 Then y = x

NOTA
Se houver uma instrução if que faz múltiplas comparações, certifique-se de incluir cada conjunto
de comparações entre parênteses para clareza do código. Faça isto:
if (x = 7) and (y = 8) then
Entretanto, não faça isto (o compilador fica chateado):
if x = 7 and y = 8 then

Utilize as palavras-chave begin e end no Delphi quase como você a utilizaria { e } em


C#. Por exemplo, use a construção a seguir se quiser executar múltiplas linhas de texto
quando uma dada condição é verdadeira:

if x = 6 then begin
DoSomething;
DoSomethingElse;
DoAnotherThing;
end;

Você pode combinar múltiplas condições utilizando a construção if..else:

if x =100 then
SomeFunction
else if x = 200 then
SomeOtherFunction
82 Capítulo 5 A Linguagem Delphi

else begin
SomethingElse;
Entirely;
end;

Utilização de instruções case


A instrução case no Delphi funciona quase da mesma maneira que a instrução switch em
C#. A instrução case fornece um meio de escolher uma das condições entre muitas possi-
bilidades sem uma grande construção if..else if..else if. Eis um exemplo da instrução
case do Delphi:

case SomeIntegerVariable of
101 : DoSomething;
202 : begin
DoSomething;
DoSomethingElse;
end;
303 : DoAnotherThing;
else DoTheDefault;
end;

NOTA
O seletor de tipos de uma instrução case deve ser um tipo ordinal. É inválido utilizar tipos
não-ordinais, como strings, como seletores case. Outras linguagens do .NET, como C#, permitem
que strings sejam utilizadas como seletores.

Eis a instrução switch em C# equivalente ao exemplo anterior:

switch (SomeIntegerVariable)
{
case 101: DoSomeThing( ); break;
case 202: DoSomething( );
DoSomethingElse( ); break
case 303: DoAnotherThing( ); break;
default: DoTheDefault( );
}

Loops
Um loop é uma construção que permite realizar repetidamente algum tipo de ação. As
construções de loop do Delphi são muito semelhantes àquilo que você deve conhecer a
partir de sua experiência em outras linguagens, assim não perderemos tempo discutindo
loops. Esta seção descreve as várias construções de loop que você pode utilizar no Delphi.
Loops 83

O loop for
Um loop for é ideal quando você precisa repetir uma ação um número predeterminado
de vezes. Eis um exemplo, embora não muito útil, de um loop for que adiciona o índice
de loop a uma variável dez vezes:

var
I, X: Integer;
begin
X := 0;
for I := 1 to 10 do
inc(X, I);
end.

O equivalente em C# ao exemplo anterior é este:

void main( )
{
int x = 0;
for(int i=1; i<=10; i++)
x += i;
}

Eis o equivalente no Visual Basic .NET ao mesmo conceito:

Dim X As Integer
For I = 1 To 10
X = X + I
Next I

O loop while
Utilize uma construção de loop while se você quiser que alguma parte de seu código seja
repetida quando alguma condição for verdadeira. As condições de um loop while são tes-
tadas antes de o loop ser executado e um exemplo clássico do uso de um loop while é rea-
lizar repetidamente alguma ação em um arquivo contanto que o final do arquivo não
seja encontrado. Eis um exemplo que demonstra um loop que lê uma linha por vez em
um arquivo e grava-o na tela:

program FileIt;

{$APPTYPE CONSOLE}

var
f: TextFile;
84 Capítulo 5 A Linguagem Delphi

// um arquivo de texto
s: string;
begin
AssignFile(f, 'foo.txt');
Reset(f);
try
while not EOF(f) do
begin
readln(f, S);
writeln(S);
end;
finally
CloseFile(f);
end;
end.

O loop while do Delphi funciona basicamente como o loop while do C# ou o loop Do


While do Visual Basic .NET.

repeat..until
O loop repeat..until aborda o mesmo tipo de problema de um loop while, mas de um ân-
gulo diferente. Ele repete um dado bloco de código até certa condição tornar-se True. Di-
ferentemente de um loop while, o código de loop sempre é executado pelo menos uma
vez porque a condição é testada no final do loop. O repeat..until do Delphi é mais ou me-
nos equivalente ao loop do..while do C#, exceto pelo fato de que a condição break é in-
vertida.
Por exemplo, o trecho de código a seguir repete uma instrução que incrementa um
contador até o valor do contador tornar-se maior que 100:

var
x: Integer;
begin
X := 1;
repeat
inc(x);
until x > 100;
end.

A instrução Break
Chamar Break de dentro de um loop while, for ou repeat faz com que o fluxo de seu progra-
ma pule imediatamente para o final do loop atualmente em execução. Esse método é útil
quando você precisa sair do loop imediatamente por causa de alguma circunstância que
talvez surja dentro do loop. A instrução Break do Delphi é análoga à break do C# e à instru-
Procedures e funções 85

ção Exit do Visual Basic .NET. O loop a seguir utiliza Break para terminar o loop depois de
cinco iterações:

var
i: Integer;
begin
for i := 1 to 1000000 do
begin
MessageBeep(0); // faz com que o computador emita um bip
if i = 5 then Break;
end;
end;

A instrução Continue
Chame Continue de dentro de um loop quando você quiser pular uma parte do código e
do fluxo de controle para continuar com a próxima iteração do loop. Observe no exem-
plo a seguir que o código depois de Continue não é executado na primeira iteração do
loop:

var
i: Integer;
begin
for i := 1 to 3 do
begin
writeln(i, '. Before continue');
if i = 1 then Continue;
writeln(i, '. After continue');
end;
end;

Procedures e funções
Como programador, você já deve conhecer os princípios básicos de procedures e fun-
ções. Uma procedure é uma parte discreta do programa que realiza alguma tarefa particu-
lar quando é chamada e então retorna a parte chamadora do seu código. Com uma fun-
ção é a mesma coisa, exceto pelo fato de que uma função retorna um valor depois da sua
saída da parte chamadora do programa.
A Listagem 5.1 demonstra um pequeno programa Delphi com uma procedure e uma
função.
86 Capítulo 5 A Linguagem Delphi

LISTAGEM 5.1 Um exemplo de uma função e de uma procedure


1: program FuncProc;
2:
3: {$APPTYPE CONSOLE}
4:
5: procedure BiggerThanTen(I: Integer);
6: { escreve algo na tela se I for maior que 10 }
7: begin
8: if I > 10 then
9: writeln('Funky.');
10: end;
11:
12: function IsPositive(I: Integer): Boolean;
13: { Retorna True se I for O ou positivo, False se I for negativo }
14: begin
15: Result := I >= 0;
16: end;
17:
18: var
19: Num: Integer;
20: begin
21: Num := 23;
22: BiggerThanTen(Num);
23: if IsPositive(Num) then
24: writeln(Num, ' Is positive.')
25: else
26: writeln(Num, ' Is negative.');
27: end.

RESULT
A variável local Result definida na função IsPositive( ) merece atenção especial. Cada função
do Delphi tem uma variável local implícita chamada Result que contém o valor de retorno da fun-
ção. Observe que, diferentemente da instrução de retorno do C#, a função não termina assim que
um valor é atribuído a Result.
Se quiser duplicar o comportamento de uma instrução de retorno do C#, você poderá chamar
Exit logo depois de atribuí-lo a Result. A instrução Exit termina imediatamente a rotina atual.
Você também pode retornar um valor a partir de uma função atribuindo o nome dessa função a
um valor dentro do código da função. Essa é uma sintaxe Delphi padrão herdada das versões ante-
riores do Borland Pascal. Se optar por utilizar o nome da função dentro do corpo, tenha cuidado e
observe que há uma diferença enorme entre utilizar o nome da função no lado esquerdo de um
operador de atribuição e utilizá-lo em outro lugar no seu código. Se utilizá-lo à esquerda, você atri-
bui o valor de retorno da função. Se usá-lo em outro lugar no seu código, você chama a função re-
cursivamente!
Observe que a variável Result implícita não é permitida se a opção Extended Syntax do compila-
dor estiver desativada na caixa de diálogo Project, Options, Compiler ou se você utilizar a diretiva
{$EXTENDEDSYNTAX OFF} (ou {$X-}).
Procedures e funções 87

Passando parâmetros
O Delphi permite passar parâmetros por valor ou por referência para funções e procedu-
res. Os parâmetros que você passa podem ser qualquer tipo básico ou tipo definido pelo
usuário ou um array aberto. (Os arrays abertos serão discutidos mais adiante neste capí-
tulo). Os parâmetros também podem ser constantes se seus valores não mudarem na pro-
cedure ou função.

Parâmetros por valor


Os parâmetros por valor são o modo padrão de passar parâmetros. Quando um parâme-
tro é passado por valor, significa que uma cópia local dessa variável é criada e a função ou
procedure opera nessa cópia. Considere o exemplo a seguir:

procedure Foo(I: Integer);

Ao chamar uma procedure dessa maneira, uma cópia de Integer I será criada, e Foo( )
irá operar na cópia local de I. Isso significa que você pode alterar o valor de I sem ne-
nhum efeito sobre a variável passada para Foo( ).

Parâmetros por referência


O Delphi permite passar variáveis para funções e procedures por referência; os parâme-
tros passados por referência também são chamados parâmetros variáveis. Passar por re-
ferência significa que a função ou procedure que recebe a variável pode modificar o valor
dessa variável. Para passar uma variável por referência, utilize a palavra-chave var na lista
de parâmetros da procedure ou da função:

procedure ChangeMe(var x: longint);


begin
x := 2; { x foi agora alterado na procedure chamadora }
end;

Em vez de criar uma cópia de x, a palavra-chave var faz com que o endereço do parâ-
metro seja copiado de modo que seu valor possa ser modificado diretamente.
Utilizar parâmetros var é equivalente a passar variáveis por referência em C# utili-
zando a palavra-chave ref ou, no Visual Basic .NET, a utilizar a diretiva ByRef.

Parâmetros out
Como ocorre com os parâmetros var, os parâmetros out fornecem um meio para que uma
rotina retorne um valor ao chamador na forma de um parâmetro. Entretanto, embora os
parâmetros var precisem ser inicializados para um valor válido antes de chamar a rotina,
os parâmetros out não fazem nenhuma suposição sobre a validade do parâmetro entran-
te. Para tipos por referência, isso significa que a referência será completamente descarta-
da ao entrar na rotina.
88 Capítulo 5 A Linguagem Delphi

procedure ReturnMe(out O: TObject);


begin
O := SomeObject.Create;
end;

Eis uma regra geral: utilize var para parâmetros in/out e out somente para parâmetros
out.

Parâmetros constantes
Se você não quiser que o valor de um parâmetro passado para uma função mude, pode
declará-lo com a palavra-chave const. Eis um exemplo de uma declaração de procedure
que recebe um parâmetro constante do tipo string:

procedure Goon(const s: string);

Parâmetros de array aberto


Parâmetros de array aberto oferecem a capacidade de passar um número variável de argu-
mentos para funções e procedures. Você pode declarar arrays abertos de algum tipo ho-
mogêneo ou arrays const de tipos diferentes. O código a seguir declara uma função que
aceita um array aberto de inteiros:

function AddEmUp(A: array of Integer): Integer;

Você pode passar para arrays abertos variáveis de rotinas, constantes ou expressões
de qualquer tipo de array (dinâmico, estático ou aberto entrante). O código a seguir de-
monstra isso chamando AddEmUp( ) e passando uma variedade de diferentes elementos:

var
I, Rez: Integer;
const
J = 23;
begin
I := 8;
Rez := AddEmUp([I, I + 50, J, 89]);

Você também pode passar um array diretamente para uma rotina de array aberto
como mostrado aqui:

var
A: array of integer;
begin
SetLength(A, 10);
for i := Low(A) to High(A) do
A[i] := i;
Rez := AddEmUpConst(A);
Escopo 89

Para trabalhar com um array aberto dentro da função ou procedure, você pode utili-
zar as funções High( ), Low( ) e Length( ) para obter as informações sobre o array. Para ilus-
trar isso, o código a seguir mostra uma implementação da função AddEmUp( ) que retorna a
soma de todos os números passados para A:

function AddEmUp(A: array of Integer): Integer;


var
i: Integer;
begin
Result := 0;
for i := Low(A) to High(A) do
inc(Result, A[i]);
end;

A linguagem Delphi também suporta um array of const, que permite passar tipos de
dados heterogêneos em um array para uma rotina. A sintaxe para definir uma rotina que
aceita um array of const é esta:

procedure WhatHaveIGot(A: array of const);

Você poderia chamar a procedure anterior com a sintaxe a seguir:

WhatHaveIGot(['Tabasco', 90, 5.6, 3.14159, True, 's']);

O compilador passa cada elemento do array como um System.Object, assim ele pode ser
facilmente tratado como tal na função receptora. Como um exemplo sobre como trabalhar
com array of const, a implementação a seguir para WhatHaveIGot( ) itera pelo array e mostra
uma mensagem para o usuário indicando em que índice o tipo de dados foi passado:

procedure WhatHaveIGot(A: array of const);


var
i: Integer;
begin
for i := Low(A) to High(A) do
WriteLn('Index ', I, ': ', A[i].GetType.FullName);
ReadLn;
end;

Escopo
O escopo refere-se a uma parte do seu programa em que uma dada função ou variável é
visível ao compilador. Por exemplo, uma constante global está em escopo em todos os
pontos no seu programa, enquanto uma variável local para uma procedure somente tem
escopo dentro dessa procedure. Examine a Listagem 5.2.
90 Capítulo 5 A Linguagem Delphi

LISTAGEM 5.2 Ilustração de um escopo


1: program Foo;
2:
3: {$APPTYPE CONSOLE}
4:
5: const
6: SomeConstant = 100;
7:
8: var
9: SomeGlobal: Integer;
10: D: Double;
11:
12: procedure SomeProc;
13: var
14: D, LocalD: Double;
15: begin
16: LocalD := 10.0;
17: D := D - LocalD;
18: end;
19:
20: begin
21: SomeGlobal := SomeConstant;
22: D := 4.593;
23: SomeProc;
24: end.

SomeConstant, SomeGlobal e D têm escopo global – seus valores são conhecidos pelo com-
pilador em todos os pontos dentro do programa. A procedure SomeProc( ) tem duas variá-
veis em que o escopo é local a essa procedure: D e LocalD. Se tentar acessar LocalD de fora de
SomeProc( ), o compilador exibirá um erro identificador não declarado. Se acessar D de
dentro de SomeProc( ), você estará se referindo à versão local; mas se acessar D de fora dessa
procedure, você estará se referindo à versão global.

Units e namespaces
Units são os módulos individuais de código-fonte que compõem um programa Delphi.
Uma unit é um local para agrupar funções e procedures que podem ser chamadas do seu
programa principal. Para ser uma unit, um módulo de código-fonte deve consistir em
pelo menos três partes:
— Uma instrução unit – cada unit deve conter em sua primeira linha, desprezando
espaços em branco e comentários, uma instrução informando que ela é uma unit
e identificando o nome dessa unit. O nome da unit sempre deve corresponder ao
nome do arquivo, sem a extensão de arquivo. Por exemplo, se você tiver um ar-
quivo identificado como FooBar.pas, a instrução seria
unit FooBar;
Units e namespaces 91

— A parte interface – Depois da instrução unit, a próxima linha funcional do código


da unit deve ser a instrução interface. Tudo o que segue essa instrução, até a ins-
trução implementation, são os tipos, variáveis, constantes, procedures e funções que
você quer disponibilizar no seu programa principal e em outras units. Somente
declarações – nunca corpos de rotina – podem aparecer na interface. A instrução
interface deve ser uma palavra em uma linha:

interface

— A parte implementation – Essa parte segue a parte interface da unit. Embora a parte
implementation da unit contenha principalmente procedures e funções, também é
onde você declara todos os tipos, constantes e variáveis que não quer disponibili-
zar fora dessa unit. A parte implementation é onde você deve definir quaisquer fun-
ções ou procedures que tenha declarado na parte interface. A instrução implementa-
tion deve ser uma palavra em uma linha:

implementation

Opcionalmente, uma unit também pode incluir duas outras partes:


— Uma parte initialization – Essa parte da unit, que está localizada próximo do final
do arquivo, contém qualquer código de inicialização da unit. Esse código será
executado antes de o programa principal começar a execução e será executado so-
mente uma vez.
— A parte finalization – Essa parte da unit, localizada entre initialization e end.,
contém qualquer código de limpeza executado quando o programa termina. A
seção finalization foi introduzida na linguagem no Delphi 2.0. No Delphi 1.0, a
finalização de unit foi realizada adicionando uma nova procedure de saída com
a função AddExitProc( ). Se estiver portando uma aplicação a partir do Delphi
1.0, você deverá mover suas procedures de saída para a parte de finalização das
suas units.

Uma unit também implica em um namespace. Um namespace fornece um meio hie-


rárquico lógico de organizar uma aplicação ou biblioteca. A notação de pontos pode ser
utilizada para criar namespaces aninhados a fim de evitar colisões de nomes. Uma práti-
ca comum é aninhar três níveis de namespaces: company.product.area; por exemplo, Bor-
land.Delphi.System ou Borland.Vcl.Controls.

NOTA
Quando várias units contêm código de initialization/finalization, a execução de cada seção
prossegue na ordem em que as units são encontradas pelo compilador (a primeira unit na cláusula
uses do programa, em seguida a primeira unit na cláusula uses dessa unit e assim por diante).
Além disso, não é recomendável escrever código de inicialização e de finalização que conte com
esse ordenamento, uma vez que uma pequena alteração na cláusula uses pode resultar em alguns
bugs difíceis de localizar!
92 Capítulo 5 A Linguagem Delphi

A cláusula uses
A cláusula uses é onde você lista os namespaces que quer incluir em um programa ou unit
particular. Por exemplo, se você tiver um programa chamado FooProg que utiliza funções
e tipos em dois namespaces, UnitA e UnitB, a declaração uses adequada é esta:

program FooProg;

uses UnitA, UnitB;

As units podem ter duas cláusulas uses: uma na seção interface e uma na seção imple-
mentation.
Eis o código para uma unit de exemplo:

unit FooBar;

interface

uses BarFoo;

{ declarações public aqui }

implementation

uses BarFly;

{ declarações private aqui }


{definição de rotinas declaradas na seção interface}

initialization
{ inicialização de unit aqui }
finalization
{ limpeza de unit aqui }
end.

NOTA
A cláusula uses poderia conter os namespaces completamente qualificados ou somente o nome
mais interno do namespace na cláusula uses, que o Delphi permite para retrocompatibilida-
de (por exemplo, Controls) utilizando o controle de prefixo do namespace do diálogo Tools,
Options, Delphi Options, Library.

Referência circular de unit


Ocasionalmente, você encontrará uma situação em que UnitA utiliza UnitB e UnitB utiliza
UnitA. Isso é chamado referência circular de unit. A ocorrência de uma referência circular
de unit costuma ser uma indicação de uma falha de projeto da sua aplicação; você deve
evitar estruturar seu programa com uma referência circular. Freqüentemente, a solução
Packages e assemblies 93

ótima é mover alguns dados que UnitA e UnitB precisam utilizar para uma terceira unit.
Entretanto, como ocorre com a maioria das coisas, às vezes você simplesmente não pode
evitar a referência circular de unit. Observe que referências circulares na seção interface
ou implementation são ilegais. Portanto, nesse caso, mova uma das cláusulas uses para a
parte implementation da sua unit e deixe a outra na parte interface. Normalmente, isso re-
solve o problema.

Packages e assemblies
Os packages do Delphi permitem posicionar partes de sua aplicação em módulos separa-
dos, que podem ser compartilhados por múltiplas aplicações como assemblies .NET.
Os packages e assemblies são descritos em detalhes no Capítulo 6.

Programação orientada a objetos


Muitos livros foram escritos sobre o tema da programação orientada a objetos (Ob-
ject-Oriented Programming – OOP). Freqüentemente, a OOP parece mais uma reli-
gião do que uma metodologia de programação, gerando argumentos suficientemente
impetuosos e acalorados sobre seus méritos (ou a falta deles) que fazem com que as
Cruzadas pareçam uma pequena divergência. Não somos defensores ortodoxos da
OOP e não iremos nos envolver nos méritos relativos da OOP; simplesmente quere-
mos fornecer a verdade sobre um princípio fundamental em que a linguagem Delphi
é baseada.
A OOP é um paradigma de programação que utiliza objetos discretos – contendo
dados e código – como blocos de construção de aplicação. Embora o próprio paradig-
ma da OOP não sirva necessariamente para escrever código de maneira mais fácil, o
resultado da utilização da OOP tradicional é um código fácil de manter. Agrupar
dados e código de objetos simplifica o processo de caçar bugs, corrigi-los com um
mínimo de efeito sobre outros objetos e melhorar seu programa uma parte por vez.
Tradicionalmente, uma linguagem OOP contém implementações de pelo menos três
conceitos OOP:
— Encapsulamento – Lida com a combinação de campos de dados relacionados e
ocultamento dos detalhes de implementação. As vantagens do encapsulamento
incluem modularidade e isolamento de código a partir de outro código.
— Herança – A capacidade de criar novos objetos que mantêm as propriedades e os
comportamentos de objetos ancestrais. Esse conceito permite criar hierarquias de
objetos como VCL – criando primeiramente objetos genéricos e então criando
descendentes mais específicos desses objetos que apresentam uma funcionalida-
de mais específica.

A vantagem da herança é o compartilhamento de código comum. A Figura 5.1


apresenta um exemplo de herança – como um dos objetos-raiz, fruta, é o objeto
ancestral de todas as frutas, incluindo o melão. O melão é ancestral de todos os
melões, incluindo a melancia. Você vai entender.
94 Capítulo 5 A Linguagem Delphi

Fruta

Maçãs Bananas Melões

Vermelhas Verdes Melancia Melão-americano

Gala Golden

FIGURA 5.1 Ilustração de uma herança.

— Polimorfismo – o polimorfismo significa literalmente “muitas formas”. Chama-


das a métodos virtuais de uma variável de objeto chamarão o código apropriado
para qualquer instância que na verdade está na variável em tempo de execução.

Observe que o .NET CLR e a linguagem Delphi não suportam o conceito de herança
múltipla no estilo do C++.
Você deve entender os três termos a seguir antes de continuar a explorar o conceito
de objetos:
— Campo – Também chamado definições de campo ou variáveis de instância, os
campos são variáveis de dados contidos dentro de objetos. Um campo em um ob-
jeto é como um campo em um registro do Delphi. Em C#, às vezes, os campos são
chamados membros de dados.
— Método – O nome para procedures e funções que pertencem a um objeto. Méto-
dos são chamados de funções membro em C#.
— Propriedade – uma entidade que atua como um acessor para dados e código conti-
dos dentro de um objeto. As propriedades funcionam como campos com a lógica
associada para isolar do usuário final os detalhes sobre a implementação de um
objeto.

NOTA
Geralmente, considera-se um estilo OOP ruim acessar os campos de um objeto diretamente, uma
vez que os detalhes da implementação do objeto talvez mudem. Em vez disso, utilize as proprieda-
des acessoras, que permitem uma interface padrão para o objeto sem precisar envolver-se nos de-
talhes de como os objetos são implementados. As propriedades são explicadas na seção “Proprie-
dades” mais adiante neste capítulo.

Utilizando objetos no Delphi


Como mencionado anteriormente, as classes são entidades que podem conter tanto da-
dos como código. Objetos são instâncias criadas dessas classes. As classes Delphi lhe dão
toda a força da programação orientada a objetos ao oferecer suporte completo a herança,
encapsulamento e polimorfismo.
Utilizando objetos no Delphi 95

Declaração e instanciação
Naturalmente, antes de utilizar um objeto, você precisa defini-lo com a palavra-chave
class. Como descrito anteriormente neste capítulo, as classes são declaradas na seção type
de uma unit ou programa:

type
TFooObject = class;

Além de uma declaração de classe, normalmente você também terá uma variável
desse tipo de classe, ou instância, declarada na seção var :

var
FooObject: TFooObject;

Você cria uma instância de um objeto no Delphi chamando um de seus construto-


res. Um construtor é responsável por criar uma instância do seu objeto e alocar qualquer
memória ou inicializar quaisquer campos necessários de modo que o objeto esteja em
um estado utilizável ao final do construtor. Os objetos Delphi sempre têm pelo menos
um construtor chamado Create( ) – embora seja possível que um objeto tenha mais de
um construtor. Dependendo do tipo de objeto, Create( ) pode receber diferentes núme-
ros de parâmetros. Este capítulo focaliza um caso simples em que Create( ) não recebe ne-
nhum parâmetro.
Os construtores de objeto na linguagem Delphi não são chamados automaticamen-
te e é dever do programador chamar o construtor de objeto. A sintaxe para chamar um
construtor é esta:

FooObject := TFooObject.Create;

Observe que a sintaxe para uma chamada de construtor é única. Você referencia o
construtor Create( ) da classe pelo tipo e não pela instância, como faria com outros méto-
dos. Isso a princípio talvez pareça estranho, mas faz sentido. FooObject, uma variável, per-
manece indefinida no momento da chamada, mas o código para TFooObject, um tipo, per-
manece estático na memória. Uma chamada estática a seu construtor Create( ) é, portan-
to, totalmente válida.
O ato de chamar um construtor para criar uma instância de um objeto é freqüente-
mente chamado instanciação.

NOTA
Quando uma instância de objeto é criada com o construtor, o CLR irá assegurar que todos os cam-
pos no seu objeto sejam inicializados com zero. Você pode seguramente assumir que todos os nú-
meros serão inicializados para 0, todos os objetos para nil, todos os valores booleanos para False
e todas as strings estarão vazias.

Destruição
Todas as classes .NET herdam um método chamado Finalize( ), que pode ser sobrescrito
para realizar qualquer limpeza de classe necessária. Finalize( ) é chamado automatica-
96 Capítulo 5 A Linguagem Delphi

mente para cada instância pelo garbage collection do .NET. Observe, porém, que há não
garantias de quando, na verdade, Finalize( ) será chamado ou mesmo se ele será executa-
do na sua totalidade em certas circunstâncias. Por essas razões, não é recomendável que
recursos críticos ou limitados – como grandes buffers de memória, conexões de bancos
de dados ou handles do sistema operacional – sejam liberados no método Finalize( ). Em
vez disso, os desenvolvedores em Delphi devem utilizar o Disposable Pattern sobrescre-
vendo o destructor Destroy( ) de um objeto a fim de liberar recursos valiosos. Como fazer
isso será discutido no Capítulo 9.

A origem dos objetos


Você talvez esteja se perguntando como todos esses métodos entraram no seu pequeno
objeto. Certamente, não foi você mesmo que os declarou, certo? Correto. Na verdade, os
métodos recém-discutidos são provenientes da classe básica System.Object do .NET. A
classe Tobject do Delphi é um alias para System.Object. No .NET, todos os objetos sempre
são descendentes de TObject independentemente de serem declarados como tais. Portan-
to, a declaração

type
TFoo = class
end;

é equivalente à declaração

type
TFoo = class(TObject)
end;

Campos
A adição de campos a uma classe é realizada com uma sintaxe muito semelhante à da de-
claração de variáveis em um bloco var. Por exemplo, o código a seguir adiciona um Inte-
ger, uma string e um Double à classe Tfoo:

type
TFoo = class(TObject)
I: Integer;
S: string;
D: Double;
end;

A linguagem Delphi também suporta campos estáticos – isto é, campos cujos dados
são compartilhados entre todas as instâncias de uma dada classe. Isso é realizado adicio-
nando um ou mais blocos class var a uma declaração de classe. Para exemplificar, o códi-
go a seguir adiciona três campos estáticos à classe Tfoo:
Utilizando objetos no Delphi 97

type
TFoo = class(TObject)
I: Integer;
S: string;
D: Double;
class var
I_Static: Integer;
S_Static: string;
D_Static: Double;
end;

Observe que também é valido (embora sintaticamente desnecessário) ter um bloco


var em uma definição de classe que define os campos normais.
O bloco class var é idêntico, em função, à palavra-chave static em C#. Observe que
um bloco class var ou var é terminado por qualquer um dos elementos a seguir:
— Outro bloco class var ou var

— Uma declaração de propriedade

— Qualquer declaração de método

— Um especificador de visibilidade

Métodos
Métodos são procedures e funções que pertencem a um dado objeto: eles fornecem o
comportamento para um objeto em vez de apenas dados. Dois métodos importantes dos
objetos que você cria são os métodos construtor e destrutor, recém-discutidos. Você tam-
bém pode criar métodos personalizados em seus objetos para realizar diversas tarefas.
Criar um método é um processo de dois passos. Primeiro, você precisa declarar o mé-
todo na declaração do tipo de objeto e então definir o método no código. O código a se-
guir demonstra o processo de declaração e definição de um método:

type
TBoogieNights = class
Dance: Boolean;
procedure DoTheHustle;
end;

procedure TBoogieNights.DoTheHustle;
begin
Dance := True;
end;

Observe que, ao definir o corpo do método, o nome completamente qualificado – que


consiste nos nomes de classe e método – deve ser utilizado. É importante também observar
que o campo Dance do objeto pode ser acessado diretamente de dentro do método.
98 Capítulo 5 A Linguagem Delphi

Tipos de métodos
Os métodos de um tipo de classe podem ser declarados como normal, static, virtual, class
static, class virtual, dynamic ou message. Pense no exemplo de objeto a seguir:

TFoo = class
procedure IAmNormal;
class procedure IAmAClassMethod;
class procedure IAmAStatic; static;
procedure IAmAVirtual; virtual;
class procedure IAmAVirtualClassMethod; virtual;
procedure IAmADynamic; dynamic;
procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;

Métodos regulares
IAmNormal( ) é um método regular. Esse é o tipo de método padrão e funciona de maneira
semelhante a uma procedure regular ou chamada de função. O compilador sabe o ende-
reço desses métodos; assim, quando você chama um método estático, ele é capaz de vin-
cular estaticamente essas informações no executável. Os métodos estáticos funcionam
mais rapidamente; entretanto, eles não têm a capacidade de serem sobrescritos para for-
necer polimorfismo.

Métodos de classe
IAmAClassMethod( ) é um tipo de método estático especial, específico ao Delphi. Os méto-
dos de classe podem ser chamados sem uma instância e a implementação dos métodos
de classe é compartilhada entre todas as instâncias da classe dada. Entretanto, os méto-
dos de classe têm um parâmetro Self especial implícito e oculto, passado pelo compila-
dor, que permite chamar métodos de classe polimórficos (virtuais). Os métodos de classe
podem ser simples ou virtuais. Elementos que não sejam de classe ou estáticos não po-
dem ser acessados de dentro de um método de classe.

Métodos estáticos
IAMStatic( ) é verdadeiramente um método estático compatível com o .NET. Como ocor-
re com campos estáticos, a implementação dos métodos estáticos é compartilhada entre
todas as instâncias da classe dada. Assim, os elementos não-estáticos podem não ser aces-
sados de dentro de um método estático. Nenhum parâmetro Self é passado para os méto-
dos estáticos, o que significa que os métodos não-estáticos podem não ser chamados a
partir de métodos estáticos.

Métodos virtuais
IamAVirtual( ) é um método virtual. Os métodos virtuais são chamados da mesma manei-
ra que os métodos estáticos, mas visto que os métodos virtuais podem ser sobrescritos, o
compilador não sabe o endereço de uma função virtual particular quando você a chama
no seu código. O compilador JIT do .NET, portanto, constrói uma Virtual Method Table
(VMT) que fornece um meio de pesquisar os endereços de função em tempo de execu-
Utilizando objetos no Delphi 99

ção. Todas as chamadas a métodos virtuais são despachadas em tempo de execução pela
VMT. A VMT de um objeto contém todos os métodos virtuais de seu ancestral, bem
como aqueles que ele declara.

Métodos dinâmicos
IAmADynamic( ) é um método dinâmico. O compilador .NET mapeia métodos dinâmicos
para métodos virtuais, diferentemente do compilador Win32, que oferece um mecanis-
mo de despacho de método dinâmico separado.

Métodos de mensagem
IAmAMessage( ) é um método de tratamento de mensagem. A diretiva da mensagem cria
um método que pode responder a mensagens dinamicamente despachadas. O valor após
a palavra-chave message dita a qual mensagem o método responderá. Na VCL, os méto-
dos de mensagem são utilizados para criar uma resposta automática para mensagens do
Windows e, geralmente, você não chama esses métodos diretamente.

Sobrescrevendo métodos
Sobrescrever um método é a implementação da linguagem Delphi do conceito OOP de
polimorfismo. Esse processo permite alterar o comportamento de um método entre des-
cendentes. Os métodos do Delphi podem ser sobrescritos somente se eles forem primeiro
declarados como virtual, dynamic ou message. Para sobrescrever um método virtual ou dyna-
mic, simplesmente utilize a diretiva override em vez de virtual ou dynamic no seu tipo de
objeto descendente. Para sobrescrever um método de mensagem, a diretiva de mensa-
gem deve ser repetida e o mesmo ID de mensagem deve ser utilizado na classe ancestral.
Por exemplo, você poderia sobrescrever os métodos IAmAVirtual( ), IAmADynamic( ) e
IAmAMessage( ) da seguinte forma:

TFooChild = class(TFoo)
procedure IAmAVirtual; override;
procedure IAmADynamic; override;
procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;

A diretiva override substitui a entrada do método original na VMT pelo novo méto-
do. Se tivesse redeclarado IAmAVirtual e IAmADynamic com a palavra-chave virtual ou dynamic,
em vez de override, você teria criado novos métodos em vez de sobrescrever os métodos
ancestrais. Isso normalmente resultará em um alerta do compilador, a menos que você
suprima o alerta adicionando a diretiva reintroduce (descrita a seguir) à declaração do mé-
todo. Além disso, se tentar sobrescrever um método normal em um tipo descendente, o
método estático na nova classe ocultará o método da classe descendente dos usuários .

Sobrecarga de método
Como ocorre com procedures e funções regulares, os métodos podem ser sobrecarrega-
dos de modo que uma classe possa conter múltiplos métodos do mesmo nome com dife-
rentes listas de parâmetro. Métodos sobrecarregados devem ser marcados com a diretiva
100 Capítulo 5 A Linguagem Delphi

overload, embora seja opcional o uso da diretiva na primeira instância de um nome de


método em uma hierarquia de classe. O exemplo de código a seguir mostra uma classe
que contém três métodos sobrecarregados:

type
TSomeClass = class
procedure AMethod(I: Integer); overload;
procedure AMethod(S: string); overload;
procedure AMethod(D: Double); overload;
end;

Reintroduzindo nomes de método


Ocasionalmente, talvez você queira adicionar um método a uma das suas classes a fim de
substituir um método virtual do mesmo nome em um ancestral de sua classe. Nesse caso,
não é recomendável sobrescrever o método ancestral, mas, em vez disso, ocultar e su-
plantar completamente o método da classe básica. Se simplesmente adicionar o método
e compilar, você verá que o compilador produzirá um alerta explicando que o novo mé-
todo oculta um método do mesmo nome em uma classe básica. Para suprimir esse erro,
utilize a diretiva reintroduce na declaração de método na classe descendente. O exemplo
de código a seguir demonstra a utilização adequada da diretiva reintroduce:

type
TSomeBase = class
procedure Cooper; virtual;
end;

TSomeClass = class
procedure Cooper; reintroduce;
end;

Self
Uma variável implícita chamada Self está disponível dentro de todos os métodos de ob-
jeto. Self é uma referência à instância de classe que foi utilizada para chamar o método. É
passada pelo compilador como um parâmetro oculto para todos os métodos. Self é aná-
loga a this em C# e a Me no Visual Basic .NET.

Referências de classe
Embora variáveis de classe normais contenham uma referência a um objeto, as referên-
cias de classe fornecem um meio de armazenar uma referência em um tipo de classe. Uti-
lizando uma referência de classe, você chama uma classe, os métodos estáticos de uma
classe ou cria instâncias da classe. O código a seguir ilustra a sintaxe para declarar uma
nova classe chamada SomeClass e um tipo referência de classe para SomeClass:

type
SomeClass = class
Utilizando objetos no Delphi 101

constructor Create; virtual;


class procedure Foo;
end;

SomeClassRef = class of SomeClass;

Utilizando esses tipos, você poderia chamar o método da classe SomeClass.Foo( ) com
o tipo referência de classe SomeClassRef assim:

var
SCRef: SomeClassRef;
begin
SCRef := SomeClass;
SCRef.Foo;

De maneira semelhante, uma instância de SomeClass pode ser criada a partir da refe-
rência de classe:

var
SCRef: SomeClassRef;
SC: SomeClass;
begin
SCRef := SomeClass;
SC := SCRef.Create;

Observe que criar uma classe via uma referência de classe requer que a classe conte-
nha pelo menos um construtor virtual. Construtores virtuais são um recurso exclusivo
da linguagem Delphi, permitindo que as classes sejam criadas por referências de classe,
em que o tipo de classe específica não é conhecido em tempo de compilação.

Propriedades
Talvez ajude pensar nas propriedades como campos acessores especiais que permitem
modificar dados e executar o código contido dentro de sua classe. Para componentes, as
propriedades são os detalhes que aparecem na janela Object Inspector quando publica-
dos. O exemplo a seguir ilustra um Object simplificado com uma propriedade:

TMyObject = class
private
SomeValue: Integer;
procedure SetSomeValue(AValue: Integer);
public
property Value: Integer read SomeValue write SetSomeValue;
end;

procedure TMyObject.SetSomeValue(AValue: Integer);


begin
102 Capítulo 5 A Linguagem Delphi

if SomeValue < > AValue then


SomeValue := AValue;
end;

TmyObject é um objeto que contém: um campo (um inteiro chamado SomeValue), um


método (uma procedure chamada SetSomeValue) e uma propriedade chamada Value. O
propósito único da procedure SetSomeValue é configurar o valor do campo SomeValue. Na
verdade, a propriedade Value não contém nenhum dado. Value é um acessor para o campo
SomeValue; quando você solicita o número que Value contém, ela lê o valor em SomeValue.
Ao tentar configurar o valor da propriedade Value, esta chama SetSomeValue para modificar
o valor de SomeValue. Isso é útil por algumas razões: primeiro, permite ao desenvolvedor
criar efeitos colaterais naturais de obter ou configurar uma propriedade (como recalcular
uma equação ou repintar um controle). Segundo, permite apresentar aos usuários da
classe uma variável simples sem sobrecarregá-los com os detalhes sobre a implementação
da classe. Por fim, você pode permitir que, para comportamento polimórfico, o usuário
sobrescreva o método acessor nas classes descendentes.
Como ocorre com campos estáticos e métodos, a linguagem Delphi também suporta
propriedades estáticas utilizando a palavra-chave class. O código a seguir mostra uma
classe com uma propriedade estática que acessa um campo estático:

TMyClass = class(TObject)
class var FValue: Integer;
class procedure SetValue(Value: Integer); static;
class property Value: Integer read FValue write SetValue;
end;

Observe que as propriedades estáticas podem empregar apenas campos estáticos e


métodos como getters e setters.

Eventos
A linguagem Delphi suporta dois tipos diferentes de eventos: singleton e multicast.
Os eventos singleton estão presentes na linguagem Delphi desde o início. Eles são
declarados como uma propriedade cujo tipo é de procedure com os acessores read e write.
Os eventos singleton podem ter zero ou um ouvinte do evento (handler de evento). O
operador de atribuição é utilizado para acoplar um ouvinte ao evento e nil é atribuído
para remover o ouvinte do evento. A Listagem 5.3 fornece uma demonstração da decla-
ração e utilização de um evento singleton.

LISTAGEM 5.3 Demonstração do evento singleton


1: program singleevent;
2:
3: {$APPTYPE CONSOLE}
4:
5: type
6: TMyEvent = procedure (Sender: TObject; Msg: string) of object;
Utilizando objetos no Delphi 103

LISTAGEM 5.3 Continuação


7:
8: TClassWithEvent = class
9: private
10: FAnEvent: TMyEvent;
11: public
12: procedure FireEvent;
13: property AnEvent: TMyEvent read FAnEvent write FAnEvent;
14: end;
15:
16: TListener = class
17: procedure EventHandler(Sender: TObject; Msg: string);
18: end;
19:
20: { TClassWithEvent }
21:
22: procedure TClassWithEvent.FireEvent;
23: begin
24: if Assigned(FAnEvent) then
25: FAnEvent(Self, '*singleton event*');
26: end;
27:
28: { TListener }
29:
30: procedure TListener.EventHandler(Sender: TObject; Msg: string);
31: begin
32: WriteLn('Event was fired. Message is: ', Msg);
33: end;
34:
35: var
36: L: TListener;
37: CWE: TClassWithEvent;
38: begin
39: L := TListener.Create; // cria objetos
40: CWE := TClassWithEvent.Create;
41: CWE.AnEvent := L.EventHandler; // atribui o handler de evento
42: CWE.FireEvent; // faz com que o evento seja disparado
43: CWE.AnEvent := nil; // desconecta o handler de evento
44: ReadLn;n
45: end.

A saída de programa mostrada na Listagem 5.3 é

Event was fired. Message is: *singleton event*

Eventos multicast foram adicionados à linguagem para suportar a capacidade de


múltiplos ouvintes do .NET para um dado evento. Um evento multicast é uma proprie-
dade cujo tipo é de procedure e requer acessores add e remove. Os eventos multicast podem
104 Capítulo 5 A Linguagem Delphi

ter qualquer número de ouvintes. As procedures Include( ) e Exclude( ) são utilizadas para
adicionar e remover ouvintes de um evento multicast.
A Listagem 5.4 fornece um exemplo da declaração e utilização de um evento multicast.

LISTAGEM 5.4 Demonstração do evento multicast


1: program multievent;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses
6: SysUtils;
7:
8: type
9: TMyEvent = procedure (Sender: TObject; Msg: string) of object;
10:
11: TClassWithEvent = class
12: private
13: FAnEvent: TMyEvent;
14: public
15: procedure FireEvent;
16: property AnEvent: TMyEvent add FAnEvent remove FAnEvent;
17: end;
18:
19: TListener = class
20: procedure EventHandler(Sender: TObject; Msg: string);
21: end;
22:
23: { TClassWithEvent }
24:
25: procedure TClassWithEvent.FireEvent;
26: begin
27: if Assigned(FAnEvent) then
28: FAnEvent(Self, '*multicast event*');
29: end;
30:
31: { TListener }
32:
33: procedure TListener.EventHandler(Sender: TObject; Msg: string);
34: begin
35: WriteLn('Event was fired. Message is: ', Msg);
36: end;
37:
38: var
39: L1, L2: TListener;
40: CWE: TClassWithEvent;
41: begin
42: L1 := TListener.Create; // cria objetos
43: L2 := TListener.Create;
Utilizando objetos no Delphi 105

LISTAGEM 5.4 Continuação


44: CWE := TClassWithEvent.Create;
45: Include(CWE.AnEvent, L1.EventHandler); // atribui o handler de evento
46: Include(CWE.AnEvent, L2.EventHandler); // atribui o handler de evento
47: CWE.FireEvent; // faz com que o evento seja disparado
48: Exclude(CWE.AnEvent, L1.EventHandler); // desconecta o handler de evento
49: Exclude(CWE.AnEvent, L2.EventHandler); // desconecta o handler de evento
50: ReadLn;
51: end

A saída de programa mostrada na Listagem 5.4 é

Event was fired. Message is: *multicast event*


Event was fired. Message is: *multicast event*

Observe que tentativas de utilizar Include( ) para adicionar o mesmo método mais
de uma vez à lista de ouvintes resultarão em múltiplas chamadas ao método.
Para manter a compatibilidade com outras linguagens .NET CLR, o compilador
Delphi implementa uma semântica de multicast até mesmo para eventos singleton, crian-
do acessores add e remove para o evento singleton. Nessa implementação, o método
add( ) resultará em uma sobrescrição do valor existente.

Especificadores de visibilidade
A linguagem Delphi oferece maior controle sobre o comportamento de seus objetos per-
mitindo que você declare campos e métodos com diretivas como private, strict private,
protected, strict protected, public e published. A sintaxe para utilizar essas diretivas é:

TSomeObject = class
private
APrivateVariable: Integer;
AnotherPrivateVariable: Boolean;
strict private
procedure AStrictPrivateMethod;
protected
procedure AProtectedProcedure;
function ProtectMe: Byte;
strict protected
procedure AStrictProtectedMethod;
public
constructor APublicContructor;
destructor Destroy; override;
// um destrutor público
published
property AProperty: Integer
read APrivateVariable write APrivateVariable;
end;
106 Capítulo 5 A Linguagem Delphi

Você pode posicionar o número de campos ou métodos que quiser sob cada diretiva.
O estilo dita que o recuo do especificador deve ser o mesmo recuo que utiliza no nome da
classe. Os significados destas diretivas são:
— private – Estas partes de seu objeto estão acessíveis somente ao código na mesma
unit da implementação de seu objeto. Utilize essa diretiva para ocultar dos usuá-
rios os detalhes sobre a implementação e impedir que eles modifiquem direta-
mente membros sigilosos de seu objeto.
— strict private – Os membros estão acessíveis dentro da classe de declaração e não
dentro da mesma unit. Utilizado mais para encapsulamento de dados estritos do
que private.
— protected – Os membros protected de seu objeto podem ser acessados por des-
cendentes dele. Essa capacidade permite ocultar dos usuários os detalhes sobre
a implementação e, ao mesmo tempo, fornecer a máxima flexibilidade para os
descendentes de seu objeto.
— strict protected – Os membros são acessíveis somente dentro da classe de declara-
ção e ancestrais e não dentro das units de declaração. Utilizado mais para encap-
sulamento de dados estritos do que protected.
— public – Estes campos e métodos estão acessíveis em qualquer lugar no seu progra-
ma. Construtores e destrutores de objeto sempre devem ser public.
— published – Idêntico a public do ponto de vista da visibilidade. Published tem o be-
nefício extra de adicionar o atributo [Browsable(true)] a propriedades contidas
dentro dele, o que faz com que sejam exibidas no Object Inspector quando utili-
zadas no designer de formulário. Os atributos serão discutidos mais adiante neste
capítulo.

NOTA
O significado de published representa, para a linguagem Delphi, um distanciamento relativamen-
te substancial em relação à implementação do Win32. No Win32, a Runtime Type Information
(RTTI) era gerada para propriedades publicadas. O equivalente .NET da RTTI é Reflection; entre-
tanto, Reflection é possível em todos os elementos de classe, independentemente do especifica-
dor de visibilidade.

Aqui, portanto, é o código da classe TMyObject que foi introduzido anteriormente,


com diretivas adicionais para melhorar a integridade do objeto:

TMyObject = class
private
SomeValue: Integer;
procedure SetSomeValue(AValue: Integer);
published
property Value: Integer read SomeValue write SetSomeValue;
end;
Utilizando objetos no Delphi 107

procedure TMyObject.SetSomeValue(AValue: Integer);


begin
if SomeValue < > AValue then
SomeValue := AValue;
end;

Agora, os usuários de seu objeto não serão capazes de modificar o valor de SomeValue
diretamente e terão de passar pela interface fornecida pela propriedade Value para modifi-
car os dados do objeto.

Classes “amigáveis”
A linguagem C++ apresenta um conceito de classes amigáveis (isto é, classes que têm per-
missão de acessar os dados e funções privadas em outras classes). Isso é realizado em C++
com a palavra-chave friend. As linguagens do .NET, como Delphi e C#, apresentam um
conceito semelhante, embora a implementação seja diferente. Membros não-strict, pri-
vate e protected de uma classe são visíveis e acessíveis por outras classes e pelo código de-
clarado dentro do mesmo namespace da unit.

Class Helpers
Class helpers (auxiliares de classes) fornecem um meio de estender uma classe sem modi-
ficar a classe real. Em vez disso é criada, uma nova classe, a helper, e seus métodos são efe-
tivamente incorporados à classe original. Isso permite que usuários da classe original
chamem métodos da class helper como se eles fossem métodos das classes originais.
O exemplo de código a seguir cria uma classe simples e uma classe auxiliar a fim de
demonstrar uma chamada a um método na class helper:

program Helpers;

{$APPTYPE CONSOLE}

type
TFoo = class
procedure AProc;
end;

TFooHelper = class helper for TFoo


procedure AHelperProc;
end;

{ TFoo }

procedure TFoo.AProc;
begin
WriteLn('TFoo.AProc');
108 Capítulo 5 A Linguagem Delphi

end;

{ TFooHelper }

procedure TFooHelper.AHelperProc;
begin
WriteLn('TFooHelper.AHelperProc');
AProc;
end;

var
Foo: TFoo;
begin
Foo := TFoo.Create;
Foo.AHelperProc;
end.

ATENÇÃO
Class helpers são um recurso interessante, mas não um recurso que geralmente possa servir a um
bom projeto de software. Esse recurso está na linguagem principalmente porque a Borland preci-
sou ocultar as diferenças entre as classes .NET padrão e classes semelhantes no Win32 Delphi. De-
vem ser raras as ocasiões em que um bom projeto necessite utilizar class helpers.

Tipos aninhados
A linguagem Delphi permite que uma cláusula type apareça em uma declaração de classe,
aninhando eficazmente tipos dentro de uma classe. Esses tipos aninhados são referencia-
dos com uma sintaxe NestedType.OuterType, como mostrado no exemplo de código a seguir:

type
OutClass = class
procedure SomeProc;

type
InClass = class
procedure SomeOtherProc;
end;

end;

var
IC: OutClass.InClass;
Utilizando objetos no Delphi 109

Sobrecarga de operadores
A linguagem Delphi suporta a sobrecarga de operadores para classes e registros. A sintaxe
para sobrecarregar um operador é tão simples e direta quanto declarar um método de
classe com um nome e assinatura específica. Uma lista completa de operadores sobrecar-
regáveis pode ser encontrada na ajuda on-line do Delphi sob o tópico Operator Overloads;
no entanto, o exemplo de código a seguir demonstra como você poderia sobrecarregar os
operadores + e – de uma classe:

OverloadsOps = class
private
FField: Integer;
public
class operator Add(a, b: OverloadsOps): OverloadsOps;
class operator Subtract(a, b: OverloadsOps): OverloadsOps;
end;

class operator OverloadsOps.Add(a, b: OverloadsOps): OverloadsOps;


begin
Result := OverloadsOps.Create;
Result.FField := a.FField + b.FField;
end;

class operator OverloadsOps.Subtract(a, b: OverloadsOps): OverloadsOps;


begin
Result := OverloadsOps.Create;
Result.FField := a.FField - b.FField;
end;

Observe que os operadores sobrecarregados são declarados como class operator e re-
cebem a classe de declaração como parâmetros. Uma vez que os operadores + e – são bi-
nários, eles também retornam a classe de declaração.
Uma vez declarados, os operadores podem ser utilizados de maneira semelhante a
esta:

var
O1, O2, O3: OverloadsOps;
begin
O1 := OverloadsOps.Create;
O2 := OverloadsOps.Create;
O3 := O1 + O2;
end;

Atributos
Um dos recursos mais animadores da plataforma .NET é a realização de desenvolvimento
baseado em atributo, presente há vários anos no projeto de diferentes linguagens de pro-
110 Capítulo 5 A Linguagem Delphi

gramação. Os atributos fornecem um meio de associar metadados a elementos da lingua-


gem, como classes, propriedades, métodos, variáveis e assim por diante, que fornecem
aos seus consumidores informações adicionais sobre esses elementos.
Os atributos são declarados com a notação de colchetes antes de o elemento ser au-
mentado. Por exemplo, o código a seguir demonstra o uso do atributo DllImport, que si-
naliza para o .NET que o método deve ser importado da DLL especificada:

[DllImport('user32.dll')]
function MessageBeep(uType : LongWord) : Boolean; external;

Os atributos são utilizados livremente no .NET. Por exemplo, o atributo Browsable em


uma propriedade determina se ela deve ser exibida no Object Inspector:

[Browsable(True)]
property Foo: string read FFoo write FFoo;

O sistema de atributos do .NET (.NET’s attribute system) é bem extensível, visto que
os atributos são implementados como classes. Isso permite extensão ilimitada do sistema
de atributos, pois você pode criar seus próprios atributos a partir do zero ou herar de clas-
ses de atributos existentes e utilizá-los em outras classes.

Interfaces
A linguagem Delphi contém suporte nativo a interfaces, o que, em termos simples,
define um conjunto de funções e procedures que pode ser utilizado para interagir
com um objeto. A definição de uma dada interface é conhecida tanto pelo implemen-
tador como pelo cliente da interface – que atua como um tipo de contrato sobre a
forma como uma interface será definida e utilizada. Uma classe pode implementar
múltiplas interfaces, fornecendo múltiplas “faces” conhecidas pelas quais um cliente
pode controlar um objeto.
Como seu nome indica, uma interface só define uma interface por meio da qual ob-
jetos e clientes se comunicam. É o trabalho de uma classe que suporta que uma interface
implemente cada uma das funções e procedures da interface.

NOTA
Diferentemente do Win32 Delphi, as interfaces do .NET não descendem implicitamente de
IInterface ou IUnknown. Assim, elas não mais implementam QueryInterface( ), _AddRef ou _Re-
lease( ). Agora, a coerção é utilizada para identidade de tipo e a contagem de referência é implí-
cita na plataforma .NET.

Definindo interfaces
A sintaxe para definir uma interface é muito semelhante à utilzada para definição de
uma classe. A principal diferença é que uma interface pode ser opcionalmente associada
a um GUID (Globally Unique Identifier), que é único à interface. O código a seguir define
uma nova interface chamada IFoo, que implementa um método chamado F1( ):
Utilizando objetos no Delphi 111

type
IFoo = interface
function F1: Integer;
end;

Observe que os GUIDs não são exigidos para definições de interface .NET, mas eles
são exigidos no Win32. Portanto, o uso de um GUID só é recomendável se você precisar
manter uma base de código para múltiplas plataformas ou se quiser empregar o .NET
COM Interop para interoperação entre .NET e COM.

DICA
A IDE do Delphi produzirá novos GUIDs para suas interfaces quando você utilizar a combinação de
teclas Ctrl+Shift+G.

O código a seguir define uma nova interface, IBar, que descende de Ifoo:

type
IBar = interface(IFoo)
function F2: Integer;
end;

Implementando interfaces
O código a seguir demonstra como implementar IFoo e IBar em uma classe chamada TFoo-
Bar:

type
TFooBar = class(TObject, IFoo, IBar)
function F1: Integer;
function F2: Integer;
end;

function TFooBar.F1: Integer;


begin
Result := 0;
end;

function TFooBar.F2: Integer;


begin
Result := 0;
end;

Observe que múltiplas interfaces podem ser listadas após a classe ancestral na primei-
ra linha da declaração de classe a fim de implementar múltiplas interfaces. A vinculação
de uma função de interface a uma função particular na classe acontece quando o compi-
lador corresponde uma assinatura de método na interface com uma assinatura corres-
pondente na classe. Um erro de compilação ocorrerá se uma classe declarar que ela im-
112 Capítulo 5 A Linguagem Delphi

plementa uma interface, porém a classe não consegue implementar um ou mais méto-
dos dessa interface.

MÉTODOS DE INTERFACE FACILITADOS


Convenhamos, as interfaces são excelentes, mas toda a digitação necessária para implementar
métodos de interface em uma classe pode ser entediante! Eis um truque na IDE para implemen-
tar todos os métodos de interface com apenas alguns pressionamentos de tecla e cliques no
mouse:
1. Adicione as interfaces que você quer implementar na declaração de classe.
2. Posicione o cursor em algum lugar na classe e pressione a combinação de teclas Ctrl+Barra de
espaço para invocar o code completion (conclusão de código). Os métodos ainda não imple-
mentados são exibidos em vermelho na janela do code completion.
3. Selecione na lista todos os métodos com a cor vermelha, mantenha a tecla Shift pressionada e
utilize as teclas de seta do teclado ou o mouse.
4. Pressione a tecla Enter e os métodos de interface serão adicionados automaticamente à defini-
ção de classe.
5. Pressione a combinação de teclas Ctrl+Shift+C para completar a parte da implementação dos
novos métodos.
6. Agora, tudo o que resta a fazer é preencher a parte da implementação de cada método!

Se uma classe implementar múltiplas interfaces que têm métodos com a mesma assi-
natura, você deverá utilizar um alias para os métodos com os mesmos nomes, como mos-
tra o breve exemplo a seguir:

type
IFoo = interface
function F1: Integer;
end;

IBar = interface
function F1: Integer;
end;

TFooBar = class(TObject, IFoo, IBar)


// métodos com alias
function IFoo.F1 = FooF1;
function IBar.F1 = BarF1;
// métodos de interface
function FooF1: Integer;
function BarF1: Integer;
end;
Utilizando objetos no Delphi 113

function TFooBar.FooF1: Integer;


begin
Result := 0;
end;

function TFooBar.BarF1: Integer;


begin
Result := 0;
end;

NOTA
A diretiva implements no Win32 Delphi não está disponível na versão atual do compilador .NET
Delphi.

Utilizando interfaces
Algumas regras importantes de linguagem são aplicadas ao utilizar variáveis dos tipos de
interface em suas aplicações. Como ocorre com outros tipos .NET, as interfaces são ge-
renciadas pelo tempo de vida. O garbage collection irá liberar um objeto quando todas as
referências ao objeto e suas interfaces implementadas tiverem sido liberadas ou saírem
de escopo. Antes da utilização, um tipo de interface sempre é inicializado para nil. Confi-
gurar uma interface manualmente como nil libera a referência ao seu objeto de imple-
mentação.
Uma outra regra única com relação a variáveis de interface é que uma interface é
uma atribuição compatível com os objetos que a implementam. Entretanto, essa compa-
tibilidade é de uma via: você pode atribuir uma referência de objeto a uma referência de
interface, mas não vice-versa. Por exemplo, o código a seguir é válido com a classe TFooBar
definida anteriormente:

procedure Test(FB: TFooBar)


var

F: IFoo;
begin

F := FB; // suportado porque FB suporta IFoo


.
.
.

Se FB não suportasse IFoo, o código ainda compilaria, mas nil seria atribuído à refe-
rência de interface. Qualquer tentativa subseqüente de utilizar essa referência resultaria
em NullReferencedException sendo levantada em tempo de execução.
Por fim, o operador de coerção de tipo as pode ser utilizado para consultar uma dada
variável de interface em uma outra interface no mesmo objeto. Isso é ilustrado aqui:
114 Capítulo 5 A Linguagem Delphi

var
FB: TFooBar;
F: IFoo;
B: IBar;
begin
FB := TFooBar.Create;
F := FB; // suportado porque FB suporta IFoo
B := F as IBar; // coerção para Ibar
.
.
.

Se a interface solicitada não for suportada, a expressão retornará nil.

Tratamento estruturado de exceções


O tratamento estruturado de exceções (Structured Exception Handling – SEH) é um mé-
todo centralizador e normalizador de tratamento de erros, fornecendo tanto tratamento
de erros não-invasivo dentro do código-fonte como a capacidade de tratar elegantemen-
te quase todos os tipos de condições de erros. O SEH da linguagem Delphi é mapeado
para aquilo fornecido no CLR pelo .NET.
As exceções são, no nível mais básico, meramente classes que contêm informações
sobre a localização e a natureza de um erro particular. Isso torna a implementação e o uso
de exceções nas suas aplicações tão fácil quanto utilizar uma outra classe qualquer.
O .NET fornece exceções predefinidas para várias condições de erros de programa,
como erros de falta de memória, divisão por zero, estouro e underflow numérico e de er-
ros de E/S de arquivo. A Borland fornece um número ainda maior de classes de exceção
dentro da RTL e VCL do Delphi. E, naturalmente, não há nada que o impeça de definir
suas próprias classes de exceção em suas aplicações da maneira que achar melhor.
A Listagem 5.5 demonstra como utilizar o tratamento de exceções durante E/S de ar-
quivo.

LISTAGEM 5.5 A E/S de arquivo com tratamento de exceções


1: program FileIO;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses System.IO;
6:
7: var
8: F: TextFile;
9: S: string;
10: begin
Tratamento estruturado de exceções 115

LISTAGEM 5.5 Continuação


11: AssignFile(F, 'FOO.TXT');
12: try
13: Reset(F);
14: try
15: ReadLn(F, S);
16: WriteLn(S);
17: finally
18: CloseFile(F);
19: end;
20: except
21: on System.IO.IOException do
22: WriteLn('Error Accessing File!');
23: end;
24: ReadLn;
25: end

Na Listagem 5.5, o bloco interno try..finally é utilizado para assegurar que o arqui-
vo será fechado independentemente de surgirem quaisquer exceções. O que esse bloco
significa é “Programa, tente executar as instruções entre o try e o finally. Se você termi-
ná-las ou encontrar uma exceção, em qualquer caso, execute as instruções entre o finally
e o end. Se uma exceção ocorrer, prossiga para o próximo bloco de tratamento de exce-
ções”. Isso significa que o arquivo será fechado e o erro poderá ser adequadamente trata-
do independentemente do erro que ocorre.

NOTA
As instruções depois de finally em um bloco try..finally executam independentemente da
ocorrência de uma exceção. Certifique-se de que o código no seu bloco finally não supõe que
ocorreu uma exceção. Além disso, como a instrução finally não pára a migração de uma exce-
ção, o fluxo da execução de seu programa prosseguirá para o próximo handler de exceção.

O bloco try..except externo é utilizado para tratar as exceções à medida que elas
ocorrem no programa. Depois que o arquivo é fechado no bloco finally, o bloco except
imprime uma mensagem no console informando ao usuário que ocorreu um erro de E/S.
Um das principais vantagens que o tratamento de exceções fornece, em relação aos
métodos das escolas mais antigas de tratamento de erro de retorno de valor de verifica-
ção de função, é a capacidade de separar distintamente o código de detecção de erros do
código de correção de erros. Esse é um bom aspecto principalmente porque torna seu có-
digo mais fácil de ler e manter, permitindo que você se concentre em um aspecto distin-
to do código em um determinado momento.
O fato de que você não pode interromper nenhuma exceção específica utilizando o
bloco try..finally é importante. Quando você utiliza um bloco try..finally em seu códi-
go, significa que não se importa com as exceções que poderiam ocorrer. Você só quer rea-
lizar algumas tarefas quando essas exceções ocorrem a fim de sair elegantemente de uma
situação difícil. O bloco finally é um lugar ideal para liberar quaisquer recursos que você
alocou (como arquivos ou recursos do Windows) porque ele sempre funcionará no caso
116 Capítulo 5 A Linguagem Delphi

de um erro. Em muitas situações, porém, você precisará de algum tipo de tratamento de


erros que seja capaz de responder de maneira diferente com base no tipo de erro que
ocorre. Você pode interromper exceções específicas utilizando um bloco try..except, que
é novamente mostrado na Listagem 5.6.

LISTAGEM 5.6 Um bloco try..except de tratamento de exceções


1: program HandleIt;
2:
3: {$APPTYPE CONSOLE}
4:
5: var
6: D1, D2, D3: Double;
7: begin
8: try
9: Write('Enter a decimal number: ');
10: ReadLn(D1);
11: Write('Enter another decimal number: ');
12: ReadLn(D2);
13: Writeln('I will now divide the first number by the second...');
14: D3 := D1 / D2;
15: Writeln('The answer is: ', D3:5:2);
16: except
17: on System.OverflowException do
18: Writeln('Overflow in performing division!');
19: on System.DivideByZeroException do
20: Writeln('You cannot divide by zero!');
21: on Borland.Delphi.System.EInvalidInput do
22: Writeln('That is not a valid number!');
23: end;
24: end

Embora seja possível interromper exceções específicas com o bloco try..except, você
também pode capturar outras exceções adicionando a cláusula else que captura tudo
para essa construção. A sintaxe da construção try..except..else é:

try
Instruções
except
On ESomeException do Something;
else
{ do some default exception handling }
end;
Tratamento estruturado de exceções 117

ATENÇÃO
Ao utilizar a construção try..except..else, você deve estar ciente de que a parte else irá capturar
todas as exceções – mesmo aquelas que você talvez não espere, como falta de memória ou outras
exceções de biblioteca em tempo de execução. Tenha cuidado ao utilizar a cláusula else e a utilize
moderadamente. Você sempre deve levantar a exceção novamente ao interrompê-la com hand-
lers de exceção inadequados. Isso é explicado na seção “Regerando uma exceção”.

Você pode alcançar o mesmo efeito de uma construção try..except..else sem especi-
ficar a classe de exceção em um bloco try..except, como mostrado neste exemplo:

try
Instruções
except
TrataExceção // quase a mesma coisa que uma instrução else
end;

Classes de exceção
As exceções são meramente instâncias especiais dos objetos. Esses objetos são instancia-
dos quando uma exceção ocorre e são destruídos quando ela é tratada. O objeto de exce-
ção básico é a classe System.Exception do .NET.
Um dos elementos mais importantes do objeto Exception é a propriedade Message,
uma string que fornece informações ou explicações adicionais sobre a exceção. As infor-
mações fornecidas por Message dependem do tipo de exceção que é levantado.

ATENÇÃO
Se definir seu próprio objeto de exceção, certifique-se de derivá-lo de um objeto de exceção co-
nhecido como Exception ou um de seus descendentes. Isso ocorre para que handlers de exceção
genéricos possam interromper sua exceção.

Ao tratar um tipo de exceção específica em um bloco except, esse handler também irá
capturar todas as exceções que são descendentes da exceção especificada. Por exemplo,
System.ArithmeticException é o objeto ancestral para uma variedade de exceções relaciona-
das à matemática, como DivideByZeroException, NotFiniteNumberException e OverflowException.
Você pode capturar qualquer uma dessas exceções configurando um handler para a clas-
se ArithmeticException básica, como mostrado aqui:

try
Instruções
except
on EMathError do // irá capturar EMathError ou qualquer descendente

TrataExceção
end;
118 Capítulo 5 A Linguagem Delphi

Quaisquer exceções que você não tratar explicitamente no seu programa irão, conse-
qüentemente, permanecer na pilha até serem tratadas. Em uma aplicação .NET Winform
ou Webform, um handler padrão de exceção realiza algum trabalho para exibir o erro
para o usuário. Nas aplicações VCL, o handler padrão exibirá uma caixa de diálogo de
mensagem informando o usuário de que uma exceção ocorreu.
Ao tratar uma exceção, às vezes é necessário acessar a instância do objeto de exceção
a fim de recuperar informações adicionais sobre a exceção, como aquelas fornecidas pela
sua propriedade Message. Há duas maneiras de fazer isso: o método preferível é utilizar um
identificador opcional com a construção on SomeException. Você também pode utilizar a
função ExceptObject( ), mas essa técnica não é recomendável e tornou-se obsoleta.
Você pode inserir um identificador opcional na parte on ESomeException de um bloco
except e fazer com que o identificador mapeie para uma instância da exceção atualmente
levantada. A sintaxe para fazer isso é prefacear o tipo de exceção com um identificador e
dois-pontos, como a seguir:

try
Algo
except
on E:ESomeException do
ShowMessage(E.Message);
end;

O identificador (nesse caso, E) recebe uma referência para a exceção atualmente le-
vantada. Esse identificador é sempre do mesmo tipo da exceção que ele prefacia.
A sintaxe para levantar uma exceção é semelhante à sintaxe para criar uma instância
de objeto. Para levantar uma exceção definida pelo usuário chamada EBadStuff, por
exemplo, você utilizaria esta sintaxe:

raise EBadStuff.Create('Some bad stuff happened.');

Fluxo de execução
Depois que uma exceção é levantada, o fluxo de execução de seu programa propa-
ga-se para o próximo handler de exceção até que a instância de exceção seja por fim
tratada e destruída. Esse processo é determinado pela pilha de chamadas e, portanto,
funciona por todo o programa (não apenas dentro de uma procedure ou unit). A Lis-
tagem 5.7 é uma unit VCL que ilustra o fluxo de execução de um programa quando
uma exceção é levantada. Essa listagem é a unit principal de uma aplicação Delphi
que consiste em um formulário com um botão. Quando o botão é clicado, o método
Button1Click( ) chama Proc1( ), que chama Proc2( ) que, por sua vez, chama Proc3( ).
Uma exceção é levantada em Proc3( ) e você pode atestar a propagação do fluxo de
execução por cada bloco try..finally até que a exceção seja finalmente tratada dentro
do Button1Click( ).
Tratamento estruturado de exceções 119

DICA
Ao executar esse programa na IDE do Delphi, será possível verificar o fluxo de execução mais ade-
quadamente se você desativar o tratamento de exceções do depurador integrado desmarcando
Tools, Options, Debugger Options, Borland .NET Debugger, Language Exceptions, Stop on Lan-
guage Exceptions.

LISTAGEM 5.7 Unit principal para o projeto de propagação de exceção


1: unit Main;
2:
3: interface
4:
5: uses
6: Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
7: Dialogs;
8:
9: type
10: TForm1 = class(TForm)
11: Button1: TButton;
12: procedure Button1Click(Sender: TObject);
13: end;
14:
15: var
16: Form1: TForm1;
17:
18: implementation
19:
20: {$R *.nfm}
21:
22: type
23: EBadStuff = class(Exception);
24:
25: procedure Proc3;
26: begin
27: try
28: raise EBadStuff.Create('Up the stack we go!');
29: finally
30: ShowMessage('Exception raised. Proc3 sees the exception');
31: end;
32: end;
33:
34: procedure Proc2;
35: begin
36: try
37: Proc3;
38: finally
39: ShowMessage('Proc2 sees the exception');
40: end;
41: end;
120 Capítulo 5 A Linguagem Delphi

LISTAGEM 5.7 Continuação


42:
43: procedure Proc1;
44: begin
45: try
46: Proc2;
47: finally
48: ShowMessage('Proc1 sees the exception');
49: end;
50: end;
51:
52: procedure TForm1.Button1Click(Sender: TObject);
53: const
54: ExceptMsg = 'Exception handled in calling procedure. The message is "%s"';
55: begin
56: ShowMessage('This method calls Proc1 which calls Proc2 which calls Proc3');
57: try
58: Proc1;
59: except
60: on E:EBadStuff do
61: ShowMessage(Format(ExceptMsg, [E.Message]));
62: end;
63: end;
64:
65: end

Regerando uma exceção


Se precisar realizar um tratamento de exceções especial para uma instrução dentro de um
bloco try..except existente e, mesmo assim, for necessário permitir que a exceção flua no
handler padrão do bloco externo, você poderá utilizar uma técnica chamada regerar a
exceção. A Listagem 5.8 demonstra um exemplo dessa técnica de regerar uma exceção.

LISTAGEM 5.8 Regerando uma exceção


1: try // esse é o bloco externo
2: { instruções }
3: { instruções }
4: ( instruções }
5: try // esse é o bloco interno especial
6: { alguma instrução que pode exigir tratamento especial }
7: except
8: on ESomeException do
9: begin
10: { tratamento especial para instrução de bloco interno }
11: raise; // regera a exceção para o bloco externo
12: end;
13: end;
14: except
15:// bloco externo sempre realizará o tratamento padrão
16: on ESomeException do Something;
17: end;
PARTE III: O
CAPÍTULO 6 DESENVOLVIMENTO
COM A BIBLIOTECA DE
CLASSES DO .NET
Assemblies – FRAMEWORK
6 Assemblies – Bibliotecas e
Bibliotecas e packages packages
7 Programação GDI+ –
por Steve Teixeira Desenhando no .NET
8 Mono – Um projeto .NET
para múltiplas plataformas
O s assemblies foram introduzidos no Capítulo 2
9 Gerenciamento de memória
como módulos discretos de distribuição de bibliotecas e
e garbage collection
aplicações .NET. Fisicamente, os assemblies são
arquivos no formato PE que contêm código .NET IL 10 Coleções
concebidos para execução. Logicamente, os assemblies 11 Trabalhando com as classes
definem limites para escopo, tipo, versão e segurança String e StringBuilder
no .NET Framework. 12 Operações de arquivo e de
Os assemblies podem ser módulos executáveis streaming
(arquivos .exe) ou bibliotecas compartilhadas (arquivos 13 Desenvolvendo controles
.dll). Qualquer programa, biblioteca ou package que WinForms personalizados
você cria com o Delphi é um assembly.
14 Threading no Delphi for
.NET
Assemblies centrais 15 API Reflection
O melhor local para começar uma discussão sobre os
assemblies é provavelmente com aqueles que fornecem 16 Interoperabilidade – COM
Interop e o Platform
os serviços básicos para suas aplicações. Todas as
Invocation Service
aplicações .NET utilizam o assembly mscorlib.dll. Entre
outras coisas, esse assembly abriga todos os tipos .NET NESTE CAPÍTULO
básicos, bem como a classe base Exception,
— Assemblies centrais
atributos-chave e alguns outros itens importantes.
— Visualizando conteúdo e
O assembly básico da Borland é Borland.Delphi.dll,
dependências de assemblies
que abriga o namespace da unit Borland.Delphi.System.
— Lembrar do GAC
A maioria dos tipos básicos do Delphi, descritos no
— Construindo assemblies
capítulo anterior, é implementada nesse assembly.
— Utilização de assemblies no
Delphi
Visualizando conteúdo e — Utilização de assemblies do
Delphi em C#
dependências de assemblies — Instalando packages na IDE
O .NET SDK, livremente disponível na Microsoft, contém do Delphi
uma ferramenta chamada ILDASM (Intermediate — Atribuição de nomes fortes a
Language Disassembler) que permite examinar dentro assemblies
de um assembly para visualizar seu conteúdo e suas — Carregando assemblies
dependências. Um assembly pode ser aberto dinamicamente
122 Capítulo 6 Assemblies – Bibliotecas e packages

de dentro do ILDASM passando seu nome para a linha de comando ao carregar o


ILDASM ou selecionar File, Open no menu principal. A Figura 6.1 mostra a janela princi-
pal do ILDASM que exibe uma aplicação Windows Forms muito simples.
Como mostrado na Figura 6.1, cada assembly contém um manifesto, uma coleção
de metadados que detalha as dependências sobre outros assemblies e vários atributos do
assembly e módulo. Dar um clique duplo no item de manifesto na visualização da árvore
do ILDASM cria uma nova janela que fornece uma visualização do conteúdo do manifes-
to. Essa janela é mostrada na Figura 6.2.
Como mostrado na Figura 6.2, a aplicação WinForm contém dependências para os
assemblies mscorlib, System, System.Windows.Forms e System.Drawing. A versão e as entradas pu-

FIGURA 6.1 Inspecionando uma aplicação WinForm simples com o ILDASM.

FIGURA 6.2 Visualizando o conteúdo do manifesto de um assembly.


Lembrar do GAC 123

blickeytoken encontradas em cada um desses assemblies é a combinação secreta que per-


mite acabar com o “inferno da DLL” no mundo do .NET; as dependências dos assemblies
são vinculadas não apenas pelo nome de arquivo, como no mundo do Win32, mas tam-
bém por número de versão e chave criptográfica. Isso é discutido detalhadamente como
parte da atribuição de nomes fortes (strong naming) mais adiante neste capítulo.

NOTA
Aplicações construídas com o Delphi sempre exigirão um ou mais assemblies da Microsoft e da
Borland. Esses assemblies dependentes devem ser implantados em quaisquer máquinas em que
você pretende utilizar seu assembly. Os assemblies da Microsoft devem ser implantados como
parte do pacote .NET Framework redistributable (visite http://www.microsoft.com/net), enquan-
to os assemblies da Borland podem ser implantados de maneira mais granular. Veja o arquivo de-
ploy.txt no seu diretório Delphi ou no CD de seu produto para obter detalhes sobre a distribuição
dos packages da Borland.

Lembrar do GAC
Quando um assembly é dependente de outro, o CLR gerencia o carregamento dos assem-
blies dependentes no application domain (domínio da aplicação). O CLR espera encon-
trar os assemblies listados em um tipo de registro chamado Global Assembly Cache
(GAC) ou utilizando as regras do Win32 para localizar uma biblioteca no disco (exami-
nando no diretório corrente, no diretório do windows, diretórios de sistema do Win-
dows, bem como na path). Portanto, ao implantar uma aplicação que requer assemblies
personalizados, você pode posicionar o assembly em um desses locais, ou em qualquer
diretório antigo, e registrá-los no GAC. O registro no GAC é o método preferido de distri-
buição quando várias diferentes aplicações exigem os assemblies.
O GAC pode ser manipulado via a ferramenta .NET Framework Configuration. Você
pode chamar essa ferramenta exibindo o painel de controle do Windows, carregando a
applet Administrative Tools e então chamando a ferramenta Microsoft .NET Framework
Configuration. Essa aplicação é mostrada na Figura 6.3.
É muito provável que as aplicações do mundo real se beneficiem da capacidade de
um utilitário de instalação de simultaneamente instalar um assembly e colocá-lo no
GAC, em vez de exigir que o GAC seja editado diretamente.

Construindo assemblies
O restante deste capítulo se concentra na construção e utilização de assemblies .dll. Em
Object Pascal, assemblies da biblioteca .NET podem ser desenvolvidos utilizando as pala-
vras-chave package ou library.
A razão pela qual o Delphi mantém dois métodos de criação de assembly tem mais
a ver com o legado do que com a viabilidade. As versões Win32 do Delphi utilizavam
library para criar Win32 Dynamic Link Libraries e package para criar Borland Packages,
módulos do tipo DLL utilizáveis somente no Delphi e C++Builder. No .NET, esses as-
pectos se resumem aos assemblies – apenas com pequenas diferenças sintáticas sepa-
rando os dois.
124 Capítulo 6 Assemblies – Bibliotecas e packages

FIGURA 6.3 Gerenciando o Global Assembly Cache (GAC).

NOTA
A sintaxe package é a preferida para criar e manter assemblies, pois a semântica do package é bem
parecida com a dos assemblies .NET. A sintaxe library é principalmente um veículo para retro-
compatibilidade.

Por que utilizar assemblies?


Há várias razões pelas quais talvez você queira utilizar assemblies, incluindo
— Distribuição menor

— Particionamento de aplicação

— Distribuição de componentes

Distribuição menor
É uma situação bastante comum o fato de as múltiplas aplicações compartilharem al-
guns tipos ou outras bibliotecas. Nesses casos, pode ser eficiente posicionar esse código
compartilhado nos assemblies a serem utilizados pelas várias aplicações. Por exemplo,
pense no fato de que vários programas estão disponíveis na Internet como aplicações
completas, demos para downloads ou atualizações para aplicações existentes. Pense no
benefício de oferecer aos usuários a opção de download de versões menores da aplicação
quando partes dela talvez já existam em seus sistemas, por exemplo, quando elas têm
uma instalação anterior.
Construindo assemblies 125

Particionamento de aplicação
Ao particionar suas aplicações com assemblies você não apenas divide os módulos fisica-
mente com base em uma organização lógica, mas também permite que seus usuários ob-
tenham atualizações somente para as partes da aplicação de que necessitem.

Distribuição de componentes
Provavelmente uma das razões mais comuns de utilizar os assemblies é a distribuição de
componentes independentes. Se você for um fornecedor de componentes, deve saber
como criar assemblies, visto que certos elementos em design-time (tempo de projeto) –
como editores de propriedade e componentes, assistentes e especialistas – são fornecidos
pelos assemblies.

Utilização de packages para construir assemblies


Se souber criar units em Object Pascal e compilar uma aplicação, você já conhece tudo o
que precisa para criar assemblies utilizando packages. Utilizar a IDE Wizard para criar um
novo package e adicionar uma nova unit a esse package gera um arquivo semelhante ao
mostrado na Listagem 6.1. Esse arquivo é mostrado clicando com o botão direito do
mouse no nó do package no Project Manager e selecionando View Source no menu local.

LISTAGEM 6.1 Um arquivo de exemplo package


1: package Package2;
2:
3:
4: {$ALIGN 0}
5: {$ASSERTIONS ON}
6: {$BOOLEVAL OFF}
7: {$DEBUGINFO ON}
8: {$EXTENDEDSYNTAX ON}
9: {$IMPORTEDDATA ON}
10: {$IOCHECKS ON}
11: {$LOCALSYMBOLS ON}
12: {$LONGSTRINGS ON}
13: {$OPENSTRINGS ON}
14: {$OPTIMIZATION ON}
15: {$OVERFLOWCHECKS OFF}
16: {$RANGECHECKS OFF}
17: {$REFERENCEINFO ON}
18: {$SAFEDIVIDE OFF}
19: {$STACKFRAMES OFF}
20: {$TYPEDADDRESS OFF}
21: {$VARSTRINGCHECKS ON}
22: {$WRITEABLECONST OFF}
23: {$MINENUMSIZE 1}
24: {$IMAGEBASE $400000}
126 Capítulo 6 Assemblies – Bibliotecas e packages

LISTAGEM 6.1 Continuação


25: {$IMPLICITBUILD OFF}
26:
27: requires
28: borland.delphi;
29:
30: [assembly: AssemblyDescription('')]
31: [assembly: AssemblyConfiguration('')]
32: [assembly: AssemblyCompany('')]
33: [assembly: AssemblyProduct('')]
34: [assembly: AssemblyCopyright('')]
35: [assembly: AssemblyTrademark('')]
36: [assembly: AssemblyCulture('')]
37:
38:
39: // O compilador Delphi controla o AssemblyTitleAttribute via o
40: // ExeDescription. Você pode configurar isso na IDE via o Project Options.
41: // Configurar manualmente o atributo AssemblyTitle abaixo sobrescreverá a
42: // configuração da IDE.
43: // [assembly: AssemblyTitle ('')]
44:
45:
46: //
47: // As informações sobre a versão de um assembly consistem nestas quatro
➥valores:
48: //
49: // Número de versão principal
50: // Número de versão secundário
51: // Número do Build
52: // Revisão
53: //
54: // Você pode especificar todos os valores ou padronizar os números da Revisão e do Build
55: // utilizando o '*' como mostrado a seguir:
56:
57: [assembly: AssemblyVersion('1.0.*')]
58:
59: //
60: // Para assinar seu assembly você deve especificar uma chave a ser utilizada. Consulte
61: // a documentação do Microsoft .NET Framework para informações adicionais sobre
62: // assinatura de assembly.
63: //
64: // Utilize os atributos abaixo para controlar a chave utilizada na assinatura.
65: //
66: // Notas:
67: // (*) Se nenhuma chave por especificada, o assembly permanece sem assinatura.
68: // (*) KeyName refere-se a uma chave instalada no Crypto
69: // Service Provider (CSP) na sua máquina. KeyFile refere-se a um arquivo
70: // que contém uma chave.
71: // (*) Se o KeyFile e os valores de KeyName forem especificados,
Construindo assemblies 127

LISTAGEM 6.1 Continuação


72: // ocorrerá o processamento a seguir:
73: // (1) Se for possível encontrar o KeyName no CSP, essa chave será utilizada.
74: // (2) Se existir o KeyName e não existir o KeyFile, a
75: // chave no KeyFile será instalada e utilizada no CSP.
76: // (*) Para criar um KeyFile, você pode utilizar o utilitário sn.exe
77: // (nome forte). Ao especificar o KeyFile, a localização do KeyFile
78: // deve ser relativa ao diretório de saída de projeto. Por exemplo,
79: // se seu KeyFile estivesse localizado no diretório de projeto, você
80: // especificaria o atributo AssemblyKeyFile como
81: // [assembly: AssemblyKeyFile ('mykey.snk')], desde que seu diretório de saída
82: // seja o diretório do projeto (o padrão).
83: // (*) Delay Signing é uma opção avançada - consulte a documentação da Microsoft .NET
84: // Framework para informações adicionais sobre ela.
85: //
86: [assembly: AssemblyDelaySign(false)]
87: [assembly: AssemblyKeyFile('')]
88: [assembly: AssemblyKeyName('')]
89: end.

u Localize o código no CD: \Code\Chapter 06.

Examinando esse arquivo de cima para baixo, você pode dividi-lo desta maneira:
— A diretiva package dita que esse módulo é um projeto de um assembly package (li-
nha 1).
— A cláusula requires lista os assemblies exigidos por esse package (linhas 27–28).

— A cláusula contains lista as units que serão compiladas nesse assembly (não mos-
tradas nessa listagem).
— Vários atributos assembly permitem ao usuário inserir informações sobre esse as-
sembly as quais, em última instância, serão posicionadas no manifesto (linhas
30–36).
— Os atributos AssemblyTitle e AssemblyVersion assembly (linhas 43-57, respectivamen-
te) permitem ao usuário controlar o título e o número de versão desse assembly.
— Por fim, há alguns outros atributos assembly que fornecem controle sobre como o
assembly é assinado com um nome forte. Isso é discutido em mais detalhes a se-
guir (linhas 86–88).

NOTA
Independentemente dos assemblies contidos na cláusula requires do package, todos os packages
requerem implicitamente o assembly mscorlib.dll.
128 Capítulo 6 Assemblies – Bibliotecas e packages

A Tabela 6.1 lista e descreve os tipos de arquivos específicos a packages com base nas
extensões de arquivos.

TABELA 6.1 Arquivos de package


Extensão do arquivo Tipo do arquivo Descrição
.dpk Arquivo-fonte do package Esse arquivo é criado ao invocar o Package
Editor. Você pode pensar nele como talvez
pensaria no arquivo .dpr para um projeto
em Delphi.
.pas Arquivo-fonte da unit Um package consiste em uma ou mais
units Object Pascal contendo o código
compartilhado.
.bdsproj Arquivo de definições O arquivo utilizado pela IDE para
do projeto armazenar as definições do projeto.
.cfg Arquivo de definição O arquivo de definição gerado para
do projeto utilização pelo compilador de linha de
comando.
.dcpil Arquivo de símbolo do Essa é a versão do package compilada em
package compilado em IL IL que contém as informações de símbolo
do package e suas units.
.dcuil Unit compilada Uma versão compilada em IL de uma unit
contida em um package. Um arquivo
.dcuil criado para cada unit contida no
package.
.dll Assembly Esse é o arquivo de assembly final que
contém o código utilizável por qualquer
linguagem de programação do .NET.

As Listagens 6.2 e 6.3 mostram os arquivos .dpk e .pas que compõem um assembly do
tipo package. Esse package será utilizado a seguir por outras aplicações.

LISTAGEM 6.2 D8DG.TestPkg: Um projeto package de teste


1: package D8DG.TestPkg;
2:
3:
4: {$ALIGN 0}
5: {$ASSERTIONS ON}
6: {$BOOLEVAL OFF}
7: {$DEBUGINFO ON}
8: {$EXTENDEDSYNTAX ON}
9: {$IMPORTEDDATA ON}
10: {$IOCHECKS ON}
11: {$LOCALSYMBOLS ON}
12: {$LONGSTRINGS ON}
13: {$OPENSTRINGS ON}
14: {$OPTIMIZATION ON}
Construindo assemblies 129

LISTAGEM 6.2 Continuação


15: {$OVERFLOWCHECKS OFF}
16: {$RANGECHECKS OFF}
17: {$REFERENCEINFO ON}
18: {$SAFEDIVIDE OFF}
19: {$STACKFRAMES OFF}
20: {$TYPEDADDRESS OFF}
21: {$VARSTRINGCHECKS ON}
22: {$WRITEABLECONST OFF}
23: {$MINENUMSIZE 1}
24: {$IMAGEBASE $400000}
25: {$IMPLICITBUILD OFF}
26:
27: requires
28: Borland.Delphi;
29:
30: contains
31: D8DG.PkgUnit in 'D8DG.PkgUnit.pas';
32:
33: [assembly: AssemblyDescription('')]
34: [assembly: AssemblyConfiguration('')]
35: [assembly: AssemblyCompany('')]
36: [assembly: AssemblyProduct('')]
37: [assembly: AssemblyCopyright('')]
38: [assembly: AssemblyTrademark('')]
39: [assembly: AssemblyCulture('')]
40:
41: [assembly: AssemblyVersion('1.0.*')]
42:
43: [assembly: AssemblyDelaySign(false)]
44: [assembly: AssemblyKeyFile('')]
45: [assembly: AssemblyKeyName('')]
46: end

u Localize o código no CD: \Code\Chapter 06.

LISTAGEM 6.3 D8DG.PkgUnit: Uma unit dentro do package


1: unit D8DG.PkgUnit;
2:
3: interface
4:
5: type
6: TBlandClass = class
7: private
8: FTheString: string;
9: public
10: function TheMethod: Integer;
130 Capítulo 6 Assemblies – Bibliotecas e packages

LISTAGEM 6.3 Continuação


11: property TheString: string read FTheString write FTheString;
12: end;
13:
14: procedure IAmProcedural;
15:
16: implementation
17:
18: procedure IAmProcedural;
19: begin
20: // não faz nada
21: end;
22:
23: { TBlandClass }
24:
25: function TBlandClass.TheMethod: Integer;
26: begin
27: Result := 3;
28: end;
29:
30: end.

u Localize o código no CD: \Code\Chapter 06.

Observe que a unit contém uma classe (linha 6) e uma procedure independente (li-
nha 14) que não é membro de uma classe.

DICA
Os namespaces são simplesmente criados com uma notação de pontos no nome de uma unit.

Utilização de bibliotecas para construir assemblies


O processo de construção de assemblies como bibliotecas é muito semelhante ao dos
packages. Há poucas diferenças; como um ponto de partida, o novo assistente de biblio-
teca é utilizado em vez do novo assistente de package e a sintaxe é um pouco diferente.
As Listagens 6.4 e 6.5 mostram um arquivo de biblioteca muito semelhante ao package já
criado.

LISTAGEM 6.4 D8DG.TestLib.dpr: um projeto de biblioteca de teste


1: library D8DG.TestLib;
2:
3: {%DelphiDotNetAssemblyCompiler 'c:\program files\common files\
➥borland shared\bds\shared assemblies\2.0\Borland.Vcl.dll'}
4:
5: uses
6: System.Reflection,
Construindo assemblies 131

LISTAGEM 6.4 Continuação


7: D8DG.LibU in 'D8DG.LibU.pas';
8:
9: [assembly: AssemblyTitle('')]
10: [assembly: AssemblyDescription('')]
11: [assembly: AssemblyConfiguration('')]
12: [assembly: AssemblyCompany('')]
13: [assembly: AssemblyProduct('')]
14: [assembly: AssemblyCopyright('')]
15: [assembly: AssemblyTrademark('')]
16: [assembly: AssemblyCulture('')]
17:
18: //
19: // As informações da versão de um assembly consistem nos seguintes
20: // quatro valores:
21: //
22: // Número de versão principal
23: // Número de versão secundário
24: // Número do build
25: // Revisão
26: //
27: // Você pode especificar todos os valores ou padronizar os números de Revisão e
28: // Build utilizando o '*' como mostrado a seguir:
29:
30: [assembly: AssemblyVersion('1.0.*')]
31:
32: //
33: // Para assinar seu assembly você deve especificar uma chave a ser utilizada.
34: // Consulte a documentação do Microsoft .NET Framework para informações adicionais
35: // sobre assinatura de assembly.
36: //
37: // Utilize os atributos abaixo para controlar a chave utilizada na assinatura.
38: //
39: // Notas:
40: // (*) Se nenhuma chave tiver sido especificada, o assembly permanece sem assinatura.
41: // (*) KeyName refere-se a uma chave instalada no
42: // Crypto Service Provider (CSP) na sua máquina. KeyFile
43: // refere-se a um arquivo que contém uma chave.
44: // (*) Se os valores de KeyFile e KeyName forem especificados,
45: // ocorrerá o processamento a seguir:
46: // (1) Se for possível encontrar o KeyName no CSP, essa chave será
47: // utilizada.
48: // (2) Se não existir o KeyName e existir o KeyFile,
49: // a chave no KeyFile será instalada e utilizada
50: // no CSP.
51: // (*) Para criar um KeyFile, você pode utilizar o utilitário sn.exe (nome
52: // forte). Ao especificar o KeyFile, a localização do
53: // KeyFile deve ser relativa ao diretório de saída
54: // de projeto. Por exemplo, se seu KeyFile estivesse localizado no
132 Capítulo 6 Assemblies – Bibliotecas e packages

LISTAGEM 6.4 Continuação


55: // diretório de projeto, você especificaria o atributo AssemblyKeyFile
56: // como [assembly: AssemblyKeyFile ('mykey.snk')],
57: // desde que seu diretório de saída seja o diretório de projeto
58: // (o padrão).
59: // (*) Delay Signing é uma opção avançada - consulte a documentação da Microsoft .NET
60: // Framework para informações adicionais sobre ela.
61: //
62: [assembly: AssemblyDelaySign(false)]
63: [assembly: AssemblyKeyFile('')]
64: [assembly: AssemblyKeyName('')]
65:
66: type
67: TLibClass = class
68: procedure AProc;
69: end;
70:
71: procedure LibProc;
72: begin
73: // nada
74: end;
75:
76: function LibFunc: Integer;
77: begin
78: Result := 3;
79: end;
80:
81: { TSomeClass }
82:
83: procedure TLibClass.AProc;
84: begin
85: // Nada
86: end;
87:
88: begin
89: end

u Localize o código no CD: \Code\Chapter 06.

LISTAGEM 6.5 D8DG.LibU.pas: Uma unit de teste para a biblioteca


1: unit D8DG.LibU;
2:
3: interface
4:
5: type
6: TSomeClass = class
7: procedure AProc;
Construindo assemblies 133

LISTAGEM 6.5 Continuação


8: end;
9:
10: procedure foobar;
11: procedure barfoo;
12: procedure BlahBlah;
13:
14: implementation
15:
16: { TSomeClass }
17:
18: procedure TSomeClass.AProc;
19: begin
20: // Nada
21: end;
22:
23: procedure BlahBlah;
24: begin
25: // Nada
26: end;
27:
28: procedure foobar;
29: begin
30: // nada
31: end;
32:
33: procedure barfoo;
34: begin
35: // nada
36: end;
37:
38: end.

u Localize o código no CD: \Code\Chapter 06.

Como ocorre com o assembly package, esse assembly de biblioteca contém o código
orientado a objetos e o procedural. As principais diferenças entre assemblies de bibliote-
ca e package assemblies incluem
— Arquivos de projeto de biblioteca utilizam a diretiva %DelphiDotNetAssemblyCompiler
(como mostrado na linha 3 da Listagem 6.4) para permitir que o compilador e a
IDE monitorem os assemblies exigidos por esse assembly, enquanto os arquivos
de package utilizam a cláusula requires.
— Uma biblioteca pode conter código no arquivo .dpr, incluindo código na seção
begin..end que será executado quando a biblioteca for carregada pela primeira vez.
Isso é mostrado na Listagem 6.4 a partir da linha 66.
134 Capítulo 6 Assemblies – Bibliotecas e packages

— Uma biblioteca pode exportar rotinas simples para aplicações Win32 – essa é uma
maneira poderosa de tirar vantagem do .NET Framework em aplicações Win32
existentes.
— Uma biblioteca é inteligentemente vinculada pelo compilador Delphi. Isso signi-
fica que o código para qualquer classe direta ou indiretamente utilizada não será
vinculado ao binário compilado. Em um assembly package, todos os códigos con-
tidos nas units serão vinculados ao package .

NOTA
A diretiva exports em uma biblioteca é utilizada pelo compilador .NET para exportar rotinas
para aplicações Win32 nativas. As bibliotecas que exportam essas rotinas devem permitir código
inseguro utilizando a diretiva {$UNSAFECODE ON}. Todos os símbolos no arquivo de biblioteca ou
em seções da interface da unit são automaticamente exportados no assembly para utilização pe-
las aplicações .NET.

Utilização de assemblies no Delphi


Utilizar assemblies semelhantes àqueles que acabamos de mostrar é um processo sim-
ples e direto. O primeiro passo é adicionar uma referência ao assembly no projeto se-
lecionando Project, New Reference no menu principal ou clicar com o botão direito
do mouse no Project Manager e selecionar Add Reference. Depois de a referência ser
adicionada, os namespaces apropriados podem ser adicionados à cláusula uses. A Lis-
tagem 6.6 mostra um exemplo de unit de projeto que utiliza os assemblies package de
biblioteca criados anteriormente. O código desses assemblies é referenciado nas li-
nhas 13 e 14 da listagem.

LISTAGEM 6.6 Utilização de assemblies no Delphi


1: program TestApp;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler 'D8DG.TestPkg.dll'}
6: {%DelphiDotNetAssemblyCompiler 'D8DG.TestLib.dll'}
7:
8: uses
9: D8DG.PkgUnit,
10: D8DG.LibU;
11:
12: begin
13: D8DG.LibU.Unit.BlahBlah;
14: D8DG.PkgUnit.TBlandClass.Create;
15: end.

u Localize o código no CD: \Code\Chapter 06.


Utilização de assemblies do Delphi em C# 135

Utilização de assemblies do Delphi em C#


O processo de utilização de assemblies escritos em Delphi a partir de qualquer outra lin-
guagem do .NET é notavelmente semelhante ao do Delphi; simplesmente adicione as re-
ferências ao projeto e acesse os namespaces diretamente. A Listagem 6.7 ilustra como
nossos exemplos de assembly poderiam ser chamados do C#.

LISTAGEM 6.7 Utilizando um assembly do Delphi a partir do C#


1: using System;
2:
3: namespace DelAsmUser
4: {
5: /// <resumo>
6: /// Descrição de resumo para Class.
7: /// </resumo>
8: class Class
9: {
10: /// <resumo>
11: /// O ponto de entrada principal da aplicação.
12: /// </resumo>
13: [STAThread]
14: static void Main(string[ ] args)
15: {
16: new D8DG.LibU.TSomeClass( );
17: D8DG.PkgUnit.Unit.IAmProcedural( );
18: }
19: }
20: }

A Listagem 6.7 pode ser compilada utilizando o compilador C# da Microsoft com a


seguinte linha de comando:

csc DelAsmUser.cs /r:D8DG.TestLib.dll;D8DG.TestPkg.dll

NOTA
Observe que os elementos procedurais (por exemplo, não-objetos) podem ser acessados como
métodos de classe estáticos de uma classe chamada Unit produzida pelo compilador.

Instalando packages na IDE do Delphi


Se tiver um componente VCL que quer instalar na IDE do Delphi, é só selecionar Com-
ponent, Install VCL Component. Isso permitirá adicionar a unit do componente a um
assembly package, novo ou existente, e instalar esse assembly na IDE, tornando-o assim
disponível na paleta de componentes.
136 Capítulo 6 Assemblies – Bibliotecas e packages

Atribuição de nomes fortes a assemblies


A atribuição de nomes fortes a um assembly é um processo pelo qual uma chave pública
(public key) criptografada é adicionada ao assembly. Na verdade, um nome forte não
oferece nenhuma garantia sobre a confiabilidade do assembly, mas, em vez disso, é um
meio de ajudar a verificar a unicidade do nome, impedindo spoofing de nome e forne-
cendo aos chamadores algum tipo de identidade quando uma referência é resolvida.

NOTA
Qualquer assembly distribuído ao público deve ter um nome forte.

O processo de atribuição de nomes fortes envolve gerar e utilizar um par de chaves


nos atributos do assembly.
Para criar um par de chaves, utilize o utilitário sn.exe do Microsoft .NET SDK com o
parâmetro –k:

sn –k newkeypair.snk

Em seguida, passe esse nome de arquivo para o atributo AssemblyKeyFile no arquivo


de package:

[assembly: AssemblyKeyFile('newkeypair.snk')]

Depois de atribuir um nome forte, você verá a chave pública no manifesto do as-
sembly como mostra a Figura 6.4.

FIGURA 6.4 Visualizando a chave pública no manifesto do assembly.


Carregando assemblies dinamicamente 137

Carregando assemblies dinamicamente


Um dos recursos mais poderosos dos assemblies é que eles podem ser carregados não
apenas estaticamente como uma referência a partir de outro assembly, mas também di-
namicamente em tempo de execução. Isso é realizado utilizando a reflection do .NET
para carregar o arquivo do disco, localizar e criar objetos e invocar métodos. A Listagem
6.8 mostra como isso ocorre.

LISTAGEM 6.8 Carregando um package dinamicamente


1: program DynAsm;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\
➥v1.1.4322\System.dll'}
6:
7: uses
8: System.Reflection;
9:
10: var
11: a: Assembly;
12: typ: System.Type;
13: meth: MethodInfo;
14: o: System.Object;
15: begin
16: // carrega o assembly com base no nome de arquivo
17: a := Assembly.LoadFrom('D8DG.TestPkg.dll');
18: // utiliza a reflexão para obter um tipo de objeto no assembly
19: typ := a.GetType('D8DG.PkgUnit.TBlandClass');
20: // remove o método do objeto
21: meth := typ.GetMethod('TheMethod');
22: // cria uma instância do objeto
23: o := Activator.CreateInstance(typ);
24: // invoca o método no objeto
25: WriteLn(meth.Invoke(o, nil));
26: ReadLn;
27: end.

u Localize o código no CD: \Code\Chapter 06.

Como a Listagem 6.8 mostra, o processo é bem simples e direto não apenas para car-
regar um assembly dinamicamente, como mostrado na linha 17, mas também para obter
tipos e métodos do assembly, como demonstrado nas linhas 19 e 21. As linhas 23 e 25
mostram que não é muito mais complexo o processo para criar instâncias e invocar mé-
todos usando os tipos obtidos com reflection no assembly.
138 Capítulo 6 Assemblies – Bibliotecas e packages

ATENÇÃO
Packages carregados dinamicamente e reflection são recursos muito poderosos, mas devem ser
empregados criteriosamente. Uma vez que a maioria do trabalho é feita em tempo de execução
utilizando strings, não há a oportunidade de o compilador localizar bugs antes da execução. Por
causa disso, há maior probabilidade de seu código exibir erros ou levantar exceções em tempo de
execução.
Para evitar esses problemas e ainda se beneficiar de assemblies carregados dinamicamente, utilize
as interfaces ou classes base abstratas definidas em um assembly comum, utilizadas tanto por uma
aplicação como pelo assembly carregado dinamicamente. Dessa maneira, você torna seu código
seguro, com tipagem forte e, ao mesmo tempo, aprimora consideravelmente o desempenho se
comparado à reflection e às chamadas Invoke.
NESTE CAPÍTULO
CAPÍTULO 7 — Conceitos fundamentais

— Desenhando linhas
Programação GDI+ – — Desenhando curvas

Desenhando no .NET — Desenhando formas

— GraphicsPaths e Regions

— Trabalhando com imagens

A programação gráfica é muito divertida. Temos de — Revisitando os sistemas de


coordenadas
reconhecer, foi difícil permanecer sério ao escrever este
capítulo. Há muitas capacidades na biblioteca GDI+ e, — Exemplo de animação
neste capítulo, apresentaremos algumas delas. Veremos
como trabalhar com o desenho de linhas e formas. Este
capítulo demonstra o uso das classes Brush e Pen.
Veremos como criar efeitos especiais como cores de
gradiente e pontas de linha. Discutiremos como
desenhar curvas e trabalhar com as classes GraphicsPaths,
Regions e transformações. Examinaremos como
manipular imagens e, no final, o capítulo termina com
uma demo de animação.

Conceitos fundamentais
Antes de nos aprofundarmos nos exemplos, vamos rever
alguns conceitos fundamentais sobre a programação GDI+.

Os namespaces da GDI+
As classes relacionadas à GDI+ são definidas nos names-
paces a seguir:
— System.Drawing – Fornece classes gráficas básicas
como Bitmap, Font, Brushes, Graphics, Icon, Image,
Pen, Region etc.
— System.Drawing.Drawing2D – Fornece classes gráficas
bidimensionais e vetoriais avançadas como
LinearGradientBrush e Matrix.
— System.Drawing.Imaging – Fornece classes com
funcionalidade de geração de imagem avançadas.
— System.Drawing.Printing – Fornece classes que
lidam com impressão.
— System.Drawing.Text – Contém classes com suporte
de tipografia.
140 Capítulo 7 Programação GDI+ – Desenhando no .NET

Este capítulo abrange apenas as classes contidas nos dois primeiros namespaces lista-
dos.

A classe Graphics
A classe Graphics será utilizada intensamente por todo o capítulo e por todo o seu desen-
volvimento na GDI+. Essa classe encapsula a superfície de desenho e contém todos os
métodos necessários para realizar várias operações de renderização.

Siatema de coordenadas do Windows


O sistema de coordenadas do Windows é simples. A superfície é basicamente uma grade
composta por pixels. A origem dessa grade é o pixel superior esquerdo. Portanto, a coor-
denada desse pixel utilizando o eixo x, y seria [x=0, y=0]. A Figura 7.1 ilustra esse concei-
to em uma grade 8x8. Ela é composta de uma linha que se estende da coordenada [0, 0] à
coordenada [7, 7].

FIGURA 7.1 Linha que se estende de [0, 0] até [7, 7].

Essa explicação é uma das maneiras de pensarmos sobre um sistema de coordenadas a.


Na verdade, a GDI+ conta com três diferentes sistemas de coordenadas. Estes são os siste-
mas de coordenadas world, page e device. Ao desenhar na tela, em geral você especifica uma
coordenada x, y dos pontos na sua superfície de desenho. Esses pontos são representados
em coordenadas globais (world coordinates). Basicamente, isso é um espaço de desenho
abstrato. Essas coordenadas devem então ser convertidas em coordenadas de página (page
coordinates). Normalmente, as coordenadas de página representam algum tipo de superfí-
cie de desenho, por exemplo, um formulário (form). Por fim, as coordenadas de página de-
vem ser convertidas em coordenadas de páginas (device coordinates) que representam o
dispositivo real em que a renderização ocorre (monitor, impressora e outros). Mais adiante
neste capítulo, apresentaremos um exemplo que ilustra esse conceito.
Uma demonstração simples irá esclarecer esse processo. Imagine uma folha de papel
quadriculado na sua mesa. Estabelecer o fato de que a caixa na posição esquerda superior
[0, 0] torna esse papel quadriculado semelhante ao dispositivo ao referenciar as coorde-
nadas de dispositivo. A posição [x=0, y = 0] no seu monitor de 1024x768 também está no
lado esquerdo superior do monitor. A coordenada x aumenta e você se move para a direi-
ta ao longo do papel como ocorre com a coordenada y quando você se move para baixo.
Agora, suponha que você fosse utilizar um estêncil, do mesmo tamanho do papel, mas
com um recorte menor em algum lugar na metade. A Figura 7.2 ilustra isso – a área cinza
é coberta pelo estêncil. A área branca é a área exposta.
Conceitos fundamentais 141

A área exposta (branca) no papel representa a página no sistema de coordenadas de


página. Isso seria equivalente a qualquer superfície de desenho na sua tela como a área
cliente (client area) de um formulário. Quando desenhamos na área cliente de um for-
mulário, normalmente nos referimos ao seu canto superior como [0, 0]. Agora, estas coor-
denadas são na verdade coordenadas globais, mas, por enquanto, pense nelas como
coordenadas de página para essa ilustração. Fica claro, examinando a Figura 7.2, que a
posição [0, 0] nas coordenadas de página não é a posição [0, 0] nas coordenadas de dispo-
sitivo. De fato, provavelmente é algum outro valor como mostrado na Figura 7.2. As
coordenadas de página só correspondem às coordenadas de dispositivo quando o canto
superior esquerdo da página está exatamente sobre o canto superior esquerdo da coorde-
nada de dispositivo. A conversão entre coordenadas de página e de dispositivo é algo tra-
tado entre a GDI+ e o driver de dispositivo. Você não terá de lidar com isso.
Por outro lado, você talvez precise lidar com a conversão entre coordenadas de pági-
na e globais. É uma simples conveniência o fato de a origem das coordenadas globais e de
página estarem na mesma área e utilizarem a mesma unidade de medida, o pixel. Ao emi-
tir um comando como

MyGraphics.DrawLine(10, 10, 10, 100);

Coordenadas de dispositivo
[x = 0, y = 0]

Coordenadas de dispositivo
[x = 100, y = 215]

Coordenadas de dispositivo
[x = 0, y = 0]

FIGURA 7.2 Coordenadas de dispositivo e de página.


142 Capítulo 7 Programação GDI+ – Desenhando no .NET

Você diz ao computador: “Desenhe uma linha na coordenada global do ponto de pi-
xel 10, 10 até o ponto de pixel 10, 100”. O computador determinará a coordenada de pá-
gina para o primeiro ponto, nesse caso 10, 10 nas coordenadas de página, e irá desenhar
uma linha para o segundo ponto depois de converter todos os pontos na linha em coor-
denadas de página. Mas e se não houver uma correspondência exata entre as coordena-
das de página e as coordenadas globais? Bem, esse é um tópico discutido mais adiante
neste capítulo ao trabalharmos com transformações. Veremos como mudamos coorde-
nadas globais no plano de página e como podemos utilizar diferentes unidades de medi-
da ao informar a GDI+ o que desenhar. Assim, sem mais cerimônias, pegue seu pincel;
vamos começar a pintar.

Desenhando linhas
A capacidade de desenhar linhas é fundamental no desenvolvimento de aplicações gráfi-
cas. A GDI + fornece duas classes fundamentais para renderização de linhas. São as classes
Pen e Brush.

As classes Pen e Brush


Você utiliza as classes Pen para desenhar linhas, formas e curvas. A classe Pen pode desenhar
linhas de diferentes larguras. Você também pode aplicar pontas a linhas (estilos de pontos
finais para linhas) e as linhas podem ser unidas para criar formas mais complexas.
As classes Brush são utilizadas para preencher uma superfície de desenho. A área da
superfície pode ser preenchida com cores, modelos, imagens e texturas.
Esta seção abrange várias técnicas para desenhar linhas. No exemplo, há uma função
auxiliar que discutiremos antes de ilustrar os vários exemplos de desenho. A primeira é
uma rotina auxiliar utilizada para limpar nossa superfície de desenho. Isso é mostrado na
Listagem 7.1.

LISTAGEM 7.1 Rotina auxiliar ClearCanvas( )


1: procedure TWinForm.ClearCanvas;
2: var
3: MyGraphics: Graphics;
4: begin
5: MyGraphics := Graphics.FromHwnd(Panel1.Handle);
6: MyGraphics.FillRectangle(Brushes.White, Panel1.ClientRectangle);
7: MyPen.DashStyle := DashStyle.Solid;
8: MyPen.Color := Color.Black;
9: MyPen.StartCap := LineCap.Flat;
10: MyPen.EndCap := LineCap.Flat;
11: MyPen.Width := 10;
12: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.


Desenhando linhas 143

Essa rotina simplesmente inicializa um controle Panel em uma classe Graphics (linha
5). Esse Panel servirá como a superfície de desenho do formulário. Além disso, ela utiliza
uma classe Brush padrão para preencher a superfície do Panel com um fundo branco. Isso
tem o efeito de apagar a superfície. Ela faz isso invocando o método MyGraphics.FillRectan-
gle( ) (linha 6), que recebe um parâmetro Brush e Rectangle. Os controles têm uma pro-
priedade ClientRectangle que se refere à área cliente. Por fim, a rotina configura várias pro-
priedades de um objeto Pen, MyPen, que utilizaremos por toda a aplicação de exemplo.

Desenhando linhas
Ao desenhar linhas, é possível que estas tenham vários atributos como cor, largura de li-
nha e assim por diante. Além disso, utilizando diferentes pincéis, você pode variar a tex-
tura e as cores da linha. A Listagem 7.2 ilustra algumas dessas técnicas.

LISTAGEM 7.2 Técnicas para desenhar linhas


1: procedure TWinForm.PaintLines;
2: var
3: wdth: Integer;
4: yPos: integer;
5: lgBrush: LinearGradientBrush;
6: txBrush: TextureBrush;
7: img: Image;
8: begin
9: MyPen.Color := Color.Red;
10: yPos := 10;
11:
12: // desenha linhas de diferentes tamanhos.
13: for wdth := 1 to 5 do
14: begin
15: MyPen.Width := wdth;
16: MyGraphics.DrawLine(MyPen, 10, yPos, Panel1.Width-10, yPos);
17: inc(yPos, 20);
18: end;
19:
20: // desenha uma linha utilizando um pincel diferente
21: MyPen.Width := 20;
22: lgBrush := LinearGradientBrush.Create(Point.Create(0, yPos),
23: Point.Create(Panel1.Width-10, 10), Color.Black, Color.Yellow);
24: MyPen.Brush := lgBrush;
25: MyGraphics.DrawLine(MyPen, 10, yPos, Panel1.Width-10, yPos);
26:
27:
28: // desenha uma linha utilizando um modelo
29: yPos := yPos + 40;
30: img := Image.FromFile('Stucco.bmp');
31: txBrush := TextureBrush.Create(img);
32: MyPen.Brush := txBrush;
33: MyGraphics.DrawLine(MyPen, 10, yPos, Panel1.Width-10, yPos);
34: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.


144 Capítulo 7 Programação GDI+ – Desenhando no .NET

As linhas 13–18 ilustram como desenhar linhas de larguras diferentes. Isso é feito
simplesmente alterando a largura da classe Pen utilizada para desenhar a linha. O mé-
todo Graphics.DrawLine( ) é na verdade utilizado para renderizar a linha na superfície
de desenho. O método mostrado aqui é uma das quatro versões sobrecarregadas de
DrawLine( ). Nesse exemplo, DrawLine( ) recebe um objeto Pen e os pares de coordenadas
y e x que representam as pontas iniciais e finais da linha. As cinco linhas superiores
(cor vermelha quando você executa o programa) na Figura 7.3 mostram a saída desse
loop.

FIGURA 7.3 Saída do método PaintLines( ).

As linhas 21–25 ilustram como desenhar linhas utilizando um pincel diferente. Nes-
se caso, utilizaremos um LinearGradientBrush. Esse pincel é capaz de renderizar um interva-
lo de cores por toda a superfície de desenho. Há maneiras mais complexas de utilizar esse
tipo de pincel. Mais adiante, demonstraremos algumas. Outros pincéis que podemos uti-
lizar são mostrados na Tabela 7.1.

TABELA 7.1 Tipos de pincel


Tipo de pincel Descrição
HatchBrush Desenha utilizando um estilo de hachura, cores de primeiro plano e
cores de fundo.
LinearGradientBrush Desenha utilizando dois gradientes de cores ou gradientes multicores
personalizados.
PathGradientBrush Desenha uma GraphicsPath utilizando cores gradientes.
SolidBrush Desenha utilizando uma única cor.
TextureBrush Desenha utilizando uma imagem como a textura da classe Brush.
Desenhando linhas 145

As linhas 29–33 ilustram a utilização da classe TextureBrush para desenhar uma linha.
Aqui, um bitmap (mapa de bits) é utilizado para especificar a textura da classe Brush. A
saída desse método é mostrada na Figura 7.3.

Pontas de linhas
Além disso, você pode aplicar pontas iniciais e finais a linhas para adorná-las ou fornecer
às linhas unidas uma aparência mais suave em seus pontos de conexão. Para alcançar
isso, atribuímos um dos tipos enumerados LineCap às propriedades StartCap e EndCap das
classes Pens. As possibilidades são Flat, Square, SquareAnchor, Round, RoundAnchor, AnchorMask,
ArrorAnchor, Custom, DiamondAnchor, NoAnchor e Triangle. A Listagem 7.3 mostra o método que
demonstra enumerações LineCap.

LISTAGEM 7.3 Aplicando LineCap


1: procedure TWinForm.PaintLineCaps;
2: var
3: endPoint: Point;
4: begin
5: MyPen.Color := Color.Black;
6: MyPen.Width := 15;
7:
8: if rbtnFlat.Checked then MyPen.EndCap := LineCap.Flat
9: else if rbtnSq.Checked then MyPen.EndCap := LineCap.Square
10: else if rbtnSqAnc.Checked then MyPen.EndCap := LineCap.SquareAnchor
11: else if rbtnRound.Checked then MyPen.EndCap := LineCap.Round
12: else if rbtnRoundAnc.Checked then MyPen.EndCap := LineCap.RoundAnchor;
13:
14: endPoint := DrawAngle(MyGraphics, MyPen, Point.Create(10,
15: Panel1.Height-10), 200, 45.0);
16: endPoint := DrawAngle(MyGraphics, MyPen, endPoint, 200, 315.0);
17: endPoint := DrawAngle(MyGraphics, MyPen, endPoint, 200, 45.0);
18: endPoint := DrawAngle(MyGraphics, MyPen, endPoint, 200, 315.0);
19: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

As linhas 8–12 determinam a LineCap a ser aplicada com base na seleção do usuário
na caixa de diálogo. Em seguida, quatro linhas são desenhadas em ângulos alternados
para demonstrar como as pontas finais influenciam as linhas unidas.
Considere a Figura 7.4, que mostra a conexão de linhas utilizando uma classe Pen
com Flat LineCaps.
Agora examine a Figura 7.5. Nesse exemplo, atribuímos a propriedade MyPen.EndCap à
propriedade LineCap.Round e podemos ver como ocorre a conexão de linhas a fim de lhes
dar uma aparência mais suave.
Na Listagem 7.3, utilizamos um método DrawAngle( ). Esse é um método auxiliar que
utilizamos para desenhar um ângulo de acordo com uma ponta inicial, um comprimen-
to de linha e o grau desejado. Esse método é mostrado na Listagem 7.4.
146 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.4 Unindo linhas com Flat LineCaps.

FIGURA 7.5 Unindo linhas com Round LineCaps.

LISTAGEM 7.4 Aplicando LineCaps


1: function TWinForm.DrawAngle(aGraphics: Graphics; aPen: Pen;
2: aFromPt: Point; aLength: Integer; aTheta: Double): Point;
3: var
4: ToX, ToY: Integer;
5: begin
6: // converte graus em radianos
7: aTheta := -aTheta*Math.PI/180;
Desenhando linhas 147

LISTAGEM 7.4 Continuação


8:
9: // calcula pontos ao longo de um círculo no raio R
10: ToX := aFromPt.X + Convert.ToInt32(aLength * Math.Cos(aTheta));
11: ToY := aFromPt.Y + Convert.ToInt32(aLength * Math.Sin(aTheta));
12:
13: Result := Point.Create(ToX, ToY);
14: aGraphics.DrawLine(aPen, aFromPt, Result);
15: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Unindo linhas – a classe GraphicsPath


A classe GraphicsPath é utilizada para combinar formas gráficas que incluem linhas. Entra-
remos nos detalhes sobre a GraphicsPath mais adiante. Por enquanto, simplesmente supo-
nha que as linhas criadas no exemplo a seguir são parte de uma coleção combinada. A
Listagem 7.5 mostra isso.

LISTAGEM 7.5 Unindo linhas com GraphicsPath


1: procedure TWinForm.PaintLineJoins;
2: var
3: MyPath: GraphicsPath;
4: begin
5: MyPath := GraphicsPath.Create;
6: MyPen.Color := Color.Black;
7: MyPen.Width := 10;
8:
9: MyPath.AddLine(10, 10, 300, 10);
10: MyPath.AddLine(300, 10, 10, 200);
11: MyPath.AddLine(10, 200, 300, 200);
12:
13: if rbBevel.Checked then MyPen.LineJoin := LineJoin.Bevel
14: else if rbMiter.Checked then MyPen.LineJoin := LineJoin.Miter
15: else if rbRound.Checked then MyPen.LineJoin := LineJoin.Round
16: else if rbMiterClipped.Checked then MyPen.LineJoin :=
17: LineJoin.MiterClipped;
18:
19: MyGraphics.DrawPath(MyPen, MyPath);
20: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Nesse exemplo, utilizamos a classe GraphicsPath para definir três linhas (linhas 9–11).
Essas linhas, por pertencerem à GraphicsPath, são parte de uma única unidade. Entretanto,
semelhantes à demo LineCaps anterior, elas são desenhadas com o LineCap.Flat como pa-
148 Capítulo 7 Programação GDI+ – Desenhando no .NET

drão. Você pode especificar um atributo LineJoin para a classe Pen. Esse atributo pode ser
Bevel, Miter e Round. A Figura 7.6 ilustra o efeito da utilização de LineJoin.Miter.

FIGURA 7.6 Utilizando LineJoin.Miter ao unir linhas.

Desenhando curvas
O .NET suporta duas formas de curvas: spline cardinal e spline de Bezier. Em geral, as curvas
são representadas por uma linha que passa pelos pontos na superfície de desenho em uma di-
reção particular. A tensão da curva é baseada na extensão dos pontos fora da linha da curva.

O spline cardinal
O spline cardinal é definido por uma linha que passa diretamente pelo array de pontos.
Um valor de tensão determina a curvatura da linha. Se a tensão for mais baixa, a curvatu-
ra será mais reta. A Listagem 7.6 mostra o código para desenhar um spline cardinal.

LISTAGEM 7.6 Desenhando o spline cardinal


1: procedure TWinForm.paintCardinal;
2: var
3: MyPointsAry: array[0..2] of Point;
4: begin
5: MyPen.Color := Color.Blue;
6: MyPen.Width := 8;
7:
8: MyPointsAry[0] := Point.Create(10, 200);
9: MyPointsAry[1] := Point.Create(150, 75);
10: MyPointsAry[2] := Point.Create(500, 200);
11: MyGraphics.DrawCurve(MyPen, MyPointsAry, TrackBar1.Value / 10)
12: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.


Desenhando curvas 149

Esse exemplo é relativamente simples. O método declara um array de três pontos (li-
nha 3), preenche-o com pontos (linhas 8–10) e chama o método Graphics.DrawCurve( ) (linha
11). O parâmetro final do método mostrado aqui recebe o valor da tensão. Esse valor é
determinado pelo valor em um controle TrackBar no formulário. A Figura 7.7 mostra o
impacto do valor de tensão crescente.

FIGURA 7.7 Tensão crescente em uma spline cardinal.

O spline de Bezier
O spline de Bezier é particularmente interessante e apresenta múltiplos usos, incluindo
animação CAD/CAM, e impressão postScript, só para citar alguns. Demonstraremos uma
curva simples, duas recurvas e um loop que utiliza curvas de Bezier.
A Listagem 7.7 mostra o código para as várias formas de curvas de Bezier.

LISTAGEM 7.7 Exemplo da curva de Bezier


1: procedure TWinForm.paintBezier;
2: var
3: MyPointsAry: array[0..4] of Point;
4: begin
5: if rbtnBSC.Checked then // Curva simples
6: begin
7: MyPointsAry[0] := Point.Create(10, 200);
8: MyPointsAry[1] := Point.Create(100,100);
9: MyPointsAry[2] := Point.Create(250, 100);
10: MyPointsAry[3] := Point.Create(500, 200);
11: end
12: else if rbtnBSR1.Checked then // Recurva 1
13: begin
150 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.7 Continuação


14: MyPointsAry[0] := Point.Create(10, 200);
15: MyPointsAry[1] := Point.Create(500, 10);
16: MyPointsAry[2] := Point.Create(10, 180);
17: MyPointsAry[3] := Point.Create(500, 200);
18: end
19: else if rbtnBSR2.Checked then // Recurva 2
20: begin
21: MyPointsAry[0] := Point.Create(10, 150);
22: MyPointsAry[1] := Point.Create(250, 10);
23: MyPointsAry[2] := Point.Create(200, 265);
24: MyPointsAry[3] := Point.Create(450, 100);
25: end
26: else if rbtnBSL.Checked then // Loop
27: begin
28: MyPointsAry[0] := Point.Create(10, 10);
29: MyPointsAry[1] := Point.Create(500, 265);
30: MyPointsAry[2] := Point.Create(10, 200);
31: MyPointsAry[3] := Point.Create(80, 15);
32: end;
33:
34: MyPen.Color := Color.Blue;
35: MyPen.Width := 8;
36:
37: // Desenha a curva
38: MyGraphics.DrawBezier(MyPen, MyPointsAry[0], MyPointsAry[1],
39: MyPointsAry[2], MyPointsAry[3]);
40:
41: // Desenha linhas para mostrar o início..fim e os pontos de controle.
42: MyPen.Width := 4;
43: MyPen.Color := Color.Red;
44: MyPen.DashStyle := DashStyle.DashDotDot;
45: MyPen.StartCap := LineCap.RoundAnchor;
46: MyPen.EndCap := LineCap.ArrowAnchor;
47: MyGraphics.DrawLine(MyPen, MyPointsAry[0], MyPointsAry[1]);
48:
49: MyPen.Color := Color.Green;
50: MyPen.StartCap := LineCap.RoundAnchor;
51: MyPen.EndCap := LineCap.ArrowAnchor;
52: MyGraphics.DrawLine(MyPen, MyPointsAry[3], MyPointsAry[2]);
53: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

As linhas 5–32 configuram os vários pontos de array da curva. O primeiro elemento


é o ponto de origem da curva. O último elemento é o ponto de destino da curva. O se-
gundo e terceiro pontos são pontos de controle para os pontos de origem e de destino,
respectivamente. Na linha 38, o objeto Graphics renderiza a curva chamando o método
Desenhando curvas 151

DrawBezier( ). À medida que a curva se move ao longo da linha, ela é puxada pelos pontos
de controle, que influenciam sua forma. A Figura 7.8 ilustra a curva de Bezier simples.

FIGURA 7.8 Uma curva de Bezier simples.

Desenhei duas linhas adicionais para ilustrar o relacionamento entre os pontos de


controle e os pontos de origem/destino. Quando uma curva de Bezier muda de direção
como o resultado de pelo menos um dos pontos, ela é conhecida como uma recurva e,
portanto, essa é a razão pela qual o arco da recurva é assim chamado. As Figuras 7.9 e 7.10
ilustram dois tipos de recurva de Bezier. A Figura 7.11 ilustra como podemos formar um
loop com essa curva.

FIGURA 7.9 Recurva de Bezier 1.


152 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.10 Recurva de Bezier 2.

FIGURA 7.11 Formando um laço.

Desenhando formas
O desenho gráfico não seria desenho gráfico se não fosse possível desenhar formas. A
GDI + suporta as formas Rectangle, Ellipse, Polygon e Pie e também oferece a capacidade de
desenhar outras formas que não se encaixam nessas formas comuns.

Desenhando retângulos
Desenhar retângulos é simples. Utilize a classe Rectangle para definir os limites do retân-
gulo e então desenhe-o com uma classe Pen e Brush que escolher. A Listagem 7.8 mostra o
código para desenhar um retângulo.
Desenhando formas 153

LISTAGEM 7.8 Desenhando um retângulo


1: procedure DrawRectangle(aBrush: Brush);
2: var
3: MyRect: Rectangle;
4: begin
5: MyPen.Width := 5;
6: MyRect := Rectangle.Create(10, 10, 500, 250);
7: MyGraphics.DrawRectangle(MyPen, MyRect);
8: if Assigned(aBrush) then
9: MyGraphics.FillRectangle(aBrush, MyRect);
10: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Você observará que a linha 9 preenche opcionalmente o retângulo com um pincel


especificado. Se fossemos passar um SolidBrush ou uma das outras variações da classe
Brush, seu retângulo seria preenchido com o pincel que definimos como ilustrado na Fi-
gura 7.12, que mostra um retângulo preenchido com hachura.

FIGURA 7.12 Um retângulo preenchido com hachura.

Desenhando elipses
Desenhar elipses é quase idêntico a desenhar um retângulo. De fato, você utiliza a classe
Rectangle para definir os limites das elipses. A Listagem 7.9 ilustra isso.
154 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.9 Desenhando elipses


1: procedure DrawEllipse(aBrush: Brush);
2: var
3: MyRect: Rectangle;
4: begin
5: MyPen.Width := 5;
6: MyRect := Rectangle.Create(10, 10, 500, 250);
7: MyGraphics.DrawEllipse(MyPen, MyRect);
8: if CheckBox1.Checked then
9: MyGraphics.FillEllipse(aBrush, MyRect);
10: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Você observará que a única diferença entre desenhar uma elipse e um retângulo é a
chamada para DrawEllipse( ) /FillEllipse.

Desenhando polígonos
Um polígono é um plano fechado limitado por pelo menos três lados. Para desenhar um
polígono, fornecemos um array de pontos para os métodos DrawPolygon( ) /FillPolygon( )
da classe Graphics. A Listagem 7.10 mostra essa técnica.

LISTAGEM 7.10 Desenhando um polígono


1: procedure DrawPolygon(aBrush: Brush);
2: var
3: MyPointAry: array[0..6] of Point;
4: begin
5: MyPen.Width := 5;
6: MyPointAry[0] := Point.Create(200, 50);
7: MyPointAry[1] := Point.Create(250, 100);
8: MyPointAry[2] := Point.Create(250, 150);
9: MyPointAry[3] := Point.Create(200, 200);
10: MyPointAry[4] := Point.Create(150, 150);
11: MyPointAry[5] := Point.Create(150, 100);
12: MyPointAry[6] := Point.Create(200, 50);
13: MyGraphics.DrawPolygon(MyPen, MyPointAry);
14: if CheckBox1.Checked then
15: MyGraphics.FillPolygon(aBrush, MyPointAry);
16: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Desenhando gráficos
Com a GDI +, também podemos desenhar e comer uma pizza. Como uma elipse é limita-
da por um retângulo e um gráfico de pizza é simplesmente uma forma de cunha em uma
Desenhando formas 155

elipse (mais freqüentemente um círculo), podemos obter a fatia da nossa pizza a partir do
mesmo retângulo.
É verdade; podemos. A Listagem 7.11 mostra isso.

LISTAGEM 7.11 Desenhando uma pizza (gráfico de setores circulares)


1: procedure DrawPie(aBrush: Brush);
2: var
3: MyRect: Rectangle;
4: begin
5: MyPen.Width := 5;
6: MyRect := Rectangle.Create(10, 10, 250, 250);
7: // direita superior
8: MyGraphics.DrawPie(MyPen, MyRect, 0, -90);
9: // direita inferior
10: MyGraphics.DrawPie(MyPen, MyRect, -270, -90);
11: // parte esquerda inferior
12: MyGraphics.DrawPie(MyPen, MyRect, -180, -90);
13: // parte esquerda superior
14: MyGraphics.DrawPie(MyPen, MyRect, -90, -90);
15:
16: if CheckBox1.Checked then
17: MyGraphics.FillPie(aBrush, MyRect, 0, -90);
18: end;

u Localize o código no CD: \Code\Chapter 07\Ex01.

Esse exemplo desenha um gráfico de pizza com quatro segmentos. A linha 17 mostra
como você pode, utilizando um pincel, preencher sua torta com um conteúdo de sua
preferência. A Figura 7.13 mostra um segmento da torta preenchido.

FIGURA 7.13 Desenhando uma pizza.


156 Capítulo 7 Programação GDI+ – Desenhando no .NET

Mais sobre o LinearGradientBrush


A linha de código que cria o LinearGradientBrush é

B := LinearGradientBrush.Create(Panel1.ClientRectangle,
Color.Blue, Color.Crimson, TrackBar2.Value);

Você observará que o último parâmetro é obtido a partir do valor de um controle


TrackBar. Esse parâmetro é um ângulo de direção, representado em graus, no sentido ho-
rário do eixo s. A Figura 7.14 mostra esse efeito, embora estaticamente. Você terá de exe-
cutar a aplicação para obter o efeito completo.

FIGURA 7.14 Desenhando com um LinearGradientBrush.

GraphicsPaths e Regions
GraphicsPaths e Regions são duas classes distintas, porém semelhantes.
GraphicsPaths representa uma lista de figuras independentes. Uma GraphicsPath pode,
por exemplo, conter diferentes desenhos de linhas e formas. Ao trabalhar com Graphics-
Path, todas as figuras são desenhadas de uma vez.
Uma classe Region é uma área fechada que representa uma forma (shape) em que
você pode realizar várias operações de desenho. Regions pode, como ocorre com Graphics-
Path, conter elementos desconectados.

Desenhando com a classe GraphicsPath


Anteriormente, afirmamos que a classe GraphicsPath é utilizada para combinar formas gráfi-
cas incluindo linhas. O exemplo a seguir ilustra como uma GraphicsPath pode ser composta
de linhas e formas que talvez nem mesmo estejam unidas. O que é conveniente a respeito
da classe graphics path (caminho gráfico) é o fato de ser possível definir o que queremos
desenhar e então realizar o desenho de uma só vez. A Listagem 7.12 mostra esse código.
GraphicsPaths e Regions 157

LISTAGEM 7.12 Desenhando um caminho gráfico


1: procedure TWinForm.btnCreatePath_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: gp: GraphicsPath;
5: MyPen: Pen;
6: begin
7: ClearCanvas;
8: MyPen := Pen.Create(Color.Black, 2);
9: gp := GraphicsPath.Create;
10: gp.AddLine(10, 200, 50, 300);
11: gp.AddLine(50, 300, 500, 250);
12: gp.AddLine(500, 250, 260, 80);
13:
14: gp.StartFigure;
15: gp.AddLine(400, 10, 400, 60);
16: gp.AddLine(400, 60, 450, 60);
17: gp.AddLine(450, 60, 450, 10);
18:
19: gp.StartFigure;
20: gp.AddBezier(10, 50, 10, 55, 300, 70, 300, 250);
21: if cbPathClosed.Checked then
22: gp.CloseAllFigures;
23: if cbPathFilled.Checked then
24: MyGraphics.FillPath(Brushes.Bisque, gp);
25: MyGraphics.DrawPath(MyPen, gp);
26: end;

u Localize o código no CD: \Code\Chapter 07\Ex02.

Já vimos como adicionar linhas a GraphicsPath. Observe a linha 14. Utilizando o mé-
todo StartFigure( ) da classe GraphicsPath, podemos criar subcaminhos desconectados,
mas ainda contidos na GraphicsPath. Nesse exemplo, criamos duas figuras compostas de
linhas e uma curva de Bezier. Quando DrawPath( ) é chamado na linha 25, todas as figuras
são desenhadas. A Figura 7.15 mostra essa saída ao preencher as áreas.
Talvez você tenha observado que o método FillPath( ) funcionou até mesmo pelas
figuras dentro de GraphicsPath que não estavam fechadas. FillPath( ) sabe como determi-
nar o caminho para fechar cada figura e assume isso ao preencher.

Desenhando com a classe Region


Ao criar classes Region você define uma área em que realizará as operações de desenho.
A Listagem 7.13 mostra o código que ilustra como utilizar a classe Region.
158 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.15 Desenhando os elementos de um caminho gráfico.

LISTAGEM 7.13 Utilizando a classe Region


1: procedure TWinForm.btnCreateRegion_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: r: Rectangle;
5: r2: Rectangle;
6: MyPen: Pen;
7: rg: System.Drawing.Region;
8: begin
9: ClearCanvas;
10: MyPen := Pen.Create(Color.Black, 2);
11: r := Rectangle.Create(20, 40, 400, 50);
12: r2 := Rectangle.Create(100, 10, 50, 200);
13: rg := System.Drawing.Region.Create(r);
14:
15: case ListBox1.SelectedIndex of
16: 1: rg.Intersect(r2);
17: 2: rg.Union(r2);
18: 3: rg.&Xor(r2);
19: 4: rg.Complement(r2);
20: 5: rg.Exclude(r2);
21: end; // case
22:
23: MyGraphics.FillRegion(Brushes.Indigo, rg);
24: MyGraphics.DrawRectangle(Pens.Black, r);
25: MyGraphics.DrawRectangle(Pens.Black, r2);
26: end;

u Localize o código no CD: \Code\Chapter 07\Ex02.


GraphicsPaths e Regions 159

Nesse exemplo, são criadas duas classes Rectangle que se cruzam. A primeira é utiliza-
da para criar a região (linha 13). A segunda é combinada com a classe Region chamando
uma das operações que realizam as combinações de uma Region com outro artefato como
uma classe Rectangle, uma outra Region ou uma classe GraphicsPath. As operações realizam
as funções que seus nomes implicam. A Figura 7.16 é um exemplo de uma Region em que
foi feito um Xor sobre outra classe Rectangle.
A Figura 7.17 mostra as mesmas classes Region e Rectangle combinadas, mas intersec-
cionadas. Isso é o inverso de uma operação Xor.

FIGURA 7.16 Region originada de um Xor em uma Rectangle.

FIGURA 7.17 Region interseccionada com uma classe Rectangle.


160 Capítulo 7 Programação GDI+ – Desenhando no .NET

Recortando regiões
Vimos como as classes Region podem ser utilizadas para definir uma área em que o dese-
nho será realizado. Também vimos como são certas operações em uma região quando
uma Region é desenhada com uma outra, sobrepondo uma forma. Classes Region também
podem ser utilizadas para proibir desenho fora, ou dentro dela mesma. A Listagem 7.14
ilustra isso.

LISTAGEM 7.14 Recortando uma região


1: procedure TWinForm.btnClipRegion_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: gp: GraphicsPath;
5: r: Rectangle;
6: begin
7: ClearCanvas;
8: gp := GraphicsPath.Create;
9: r := Rectangle.Create(20, 40, 400, 200);
10: gp.AddEllipse(r);
11: MyGraphics.DrawPath(Pens.Black, gp);
12: MyGraphics.SetClip(gp, CombineMode.Replace);
13: MyGraphics.DrawString('Delphi 8',
14: System.Drawing.Font.Create('Comic Sans MS',124),
15: Brushes.Red, Point.Create(20, 20), StringFormat.GenericTypographic);
16: end;

u Localize o código no CD: \Code\Chapter 07\Ex02.

Esse exemplo desenha uma elipse na superfície com uma classe GraphicsPath. Ele en-
tão chama o método Graphics.SetClip( ) (linha 12) para estabelecer uma região de re-
corte. O segundo parâmetro, um tipo enumerado CombineMode, determina o tipo de recor-
te a ser feito. O tipo enumerado CombineMode tem o mesmo impacto na região das operações
de combinação ao interseccionar classes Region. A Figura 7.18 mostra a saída dessa função.
Veremos que o desenho da string não foi além dos limites definidos pela região de re-
corte.
Para ilustrar o poder das regiões de recorte, examine a Listagem 7.15.

LISTAGEM 7.15 Recortando uma string


1: procedure TWinForm.btnClipString_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: gp: GraphicsPath;
5: begin
6: ClearCanvas;
7: gp := GraphicsPath.Create;
8: gp.AddString('Delphi 8', FontFamily.Create('Comic Sans MS'),
Trabalhando com imagens 161

LISTAGEM 7.15 Continuação


9: Integer(FontStyle.Bold or FontStyle.Italic), 144,
10: Point.Create(10, 20), StringFormat.GenericTypographic);
11:
12: MyGraphics.DrawPath(Pens.Black, gp);
13: if cbxExclude.Checked then
14: MyGraphics.SetClip(gp, CombineMode.Exclude)
15: else
16: MyGraphics.SetClip(gp);
17: MyGraphics.DrawImage(Bitmap.Create('USFlag.gif'),
18: Panel1.ClientRectangle);
19: end;

u Localize o código no CD: \Code\Chapter 07\Ex02.

Esse exemplo adiciona uma string à GraphicsPath (linhas 8–10). Depois de desenhar a
GraphicsPath, o exemplo configura uma região de recorte no conteúdo da GraphicsPath uti-
lizando o CombineMode determinado pela seleção de um controle CheckBox. Se o controle
CheckBox estiver desmarcado, a saída será a mostrada na Figura 7.19. Caso contrário, a re-
gião de recorte é invertida e a saída aparece como mostrado na Figura 7.20.

Trabalhando com imagens


Nenhuma biblioteca gráfica seria completa sem as ferramentas para trabalhar com as
imagens. A GDI + fornece várias capacidades para manipulação de imagens. A seção a se-
guir ilustra algumas delas.

FIGURA 7.18 Exibindo uma Region de recorte.


162 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.19 Saída de uma Region de string.

FIGURA 7.20 A saída de uma Region de string – excluída.

As classes de imagem
Há duas classes com as quais você trabalhará ao manipular imagens: Bitmap e MetaFile.
Essas classes descendem da classe Image, a classe abstrata que fornece a funcionalidade co-
mum a ambas.
A classe Bitmap é utilizada para manipular imagens baseadas em rasterização, que são
imagens que consistem em dados de pixels.
Trabalhando com imagens 163

A classe Metafile contém um registro das operações de renderização gráfica que serão
reproduzidas quando ela é desenhada.
Os exemplos a seguir ilustram alguns tipos de manipulações Bitmap que você pode fa-
zer com a GDI+. Naturalmente, neste espaço, só é possível discutir algumas capacidades
da GDI+. Esperamos que isso o instigue a explorar essa tecnologia ainda mais.

Carregando e criando bitmaps


Os métodos para carregar e criar bitmaps (mapas de bits) são intensamente sobrecarrega-
dos, portanto não iremos abordar todas as várias maneiras de criá-los. Em vez disso, dis-
cutiremos os métodos utilizados nos exemplos restantes neste capítulo.
Criamos uma única aplicação que demonstra algumas capacidades da manipulação
de imagens. Essa aplicação contém um único método para carregar imagens a partir de
um arquivo. Isso é mostrado na Listagem 7.16.

LISTAGEM 7.16 Carregando imagens para uma aplicação de exemplo


1: procedure TWinForm1.LoadBitmap(aBm: String);
2: begin
3: MyBitmap:= Bitmap.Create(aBm);
4: MyBitmap.SetResolution(120, 120);
5: MyGraphics := Graphics.FromImage(MyBitmap);
6: MyGraphics.DrawImage(MyBitmap, Rectangle.Create(0, 0, MyBitmap.Width,
7: MyBitmap.Height));
8: WriteStats;
9: Invalidate;
10: end;

u Localize o código no CD: \Code\Chapter 07\Ex03.

Esse procedimento recebe um parâmetro que contém o caminho da imagem que ele
deve carregar. A imagem é carregada utilizando o construtor Bitmap.Create( ), que aceita
um nome de arquivo como um parâmetro.
MyBitmap e MyGraphics são campos Bitmap e Graphics privados utilizados nas várias roti-
nas nessa aplicação de exemplo.
Por fim, MyGraphics desenha a imagem na tela como visto na Figura 7.21. O método
WriteStats( ) é simplesmente um método que grava as informações sobre a imagem,
como mostrado na ListBox na Figura 7.21.

Alterando a resolução de uma imagem


Você pode alterar a resolução de uma imagem chamando o método SetResolution( ).
A Listagem 7.17 ilustra a utilização desse método.
164 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.21 A imagem vista depois de ser carregada.

LISTAGEM 7.17 Utilizando o método SetResolution( )


1: procedure TWinForm1.ChangeResolution(g: Graphics);
2: var
3: tmpBm1: Bitmap;
4: tmpBm2: Bitmap;
5: begin
6: tmpBm1 := Bitmap.Create(MyBitmap);
7: tmpBm2 := Bitmap.Create(MyBitmap);
8:
9: tmpBm1.SetResolution(600, 600);
10: tmpBm2.SetResolution(120, 120);
11:
12: g.DrawImage(tmpBm1, 0, 0);
13: g.DrawImage(tmpBm2, 100, 0);
14: WriteStats;
15: end;

u Localize o código no CD: \Code\Chapter 07\Ex03.

A saída do código utilizado na Listagem 7.17 é mostrada na Figura 7.22.


Trabalhando com imagens 165

FIGURA 7.22 A saída da chamada do método SetResolution( ).

Ao alterar a resolução, você observará que a imagem é escalonada para um tamanho


diferente quando é desenhada. A Figura 7.22 mostra isso. Quando aumentamos a resolu-
ção, o desenho da imagem na tela torna-se menor. Quando diminuímos a resolução, o
desenho da imagem torna-se maior. Isso ocorre porque a GDI+ utiliza a resolução da
imagem para fatorar como ela será escalonada quando desenhada. Assim, por exemplo,
uma imagem com 600 pixels de largura com uma resolução de 120 dpi será desenhada de
modo que tenha aproximadamente 5 polegadas de largura. Isto é,

5 polegadas = 600/120

O mesmo é verdade para a altura da imagem. Se quiser que uma imagem seja rende-
rizada de acordo com uma altura/largura específica independentemente de sua resolu-
ção, você deverá chamar o método DrawImage( )apropriado que aceita os limites que você
deseja.

Desenhando uma imagem


Já vimos como renderizar uma imagem em uma superfície. Só para reiterar, utilize o mé-
todo DrawImage( ) da classe Graphics como mostrado aqui:

g.DrawImage(MyBitmap, 0, 0);
166 Capítulo 7 Programação GDI+ – Desenhando no .NET

Esse método renderiza uma imagem nas coordenadas [0,0] na superfície de desenho.
A imagem é desenhada usando sua resolução padrão. Mais uma vez, a GDI+ determinará
como a imagem será escalonada. O DrawImage( ) é intensamente sobrecarregado. Uma das
várias versões de DrawImage( ), que permite especificar o tamanho físico que ele desenha,
recebe tanto uma imagem como uma classe Rectangle que representa os limites em que a
imagem é desenhada. Essa versão pode ser chamada como

g.DrawImage(MyBitmap, Rectangle.Create(0, 0, 100, 100));

O método DrawImage( ) está bem documentado na ajuda on-line distribuída com o


Delphi e em www.msdn.microsoft.com .

Interpolação
A interpolação é o processo pelo qual a GDI+ determina as cores dos pixels de preenchi-
mento a serem utilizadas quando uma imagem é expandida. Considere o fato de que
uma imagem é uma representação bidimensional dos pixels. Cada pixel contém uma cor
específica. Quando uma imagem é desenhada no mesmo dispositivo, porém com tama-
nho maior, ela irá requerer mais pixels. Como a GDI+ determina à cor de preenchimento
que a utilização de pixels varia em complexidade – cuja saída afeta a qualidade da ima-
gem. Um algoritmo complexo interpolará os pixels mais precisamente, resultando em
uma imagem com qualidade mais alta. Um algoritmo menos complexo resultará em uma
imagem com qualidade mais baixa. Por que simplesmente não utilizar o melhor algorit-
mo? Os algoritmos mais complexos levam mais tempo para processar. Se você estiver
criando imagens instantaneamente para exibição em um site da Web, talvez não queira
consumir todo o tempo do processador para criar imagens de alta qualidade. A proprie-
dade da classe Graphics que determina o algoritmo utilizado é InterpolationMode. Os va-
lores que podem ser utilizados da qualidade mais alta para a mais baixa são HighQualityBi-
cubic, Bicubic, HighQualityBilinear, Bilinear e NearestNeighbor. A Listagem 7.18 demonstra a
utilização da qualidade mais baixa e mais alta. A Figura 7.23 mostra sua saída.

LISTAGEM 7.18 Configurando o modo de interpolação


1: procedure TWinForm1.Interpolate(g: Graphics);
2: var
3: tmpBm: Bitmap;
4: begin
5: tmpBm := Bitmap.Create(MyBitmap);
6: tmpBm.SetResolution(45, 45);
7: g.InterpolationMode := InterpolationMode.NearestNeighbor;
8: g.DrawImage(tmpBM, 0, 0);
9: g.InterpolationMode := InterpolationMode.HighQualityBicubic;
10: g.DrawImage(tmpBm, 200, 0);
11: end;

u Localize o código no CD: \Code\Chapter 07\Ex03.


Trabalhando com imagens 167

A linha 6 diminui a resolução, o que resulta em uma imagem sendo desenhada de


modo que tenha um tamanho maior. As linhas 7 e 9 configuram diferentes valores de
InterpolationMode. Na Figura 7.23, a variação de qualidade das duas imagens pode ser facil-
mente percebida.

Desenhando um efeito de espelho


Você já revelou suas fotos e descobriu que elas estavam na direção errada, isto é rotacio-
nadas? Felizmente, com imagens digitais, você mesmo pode rotacioná-las utilizando o
método Bitmap.RotateFlip( ) como mostrado aqui:

tmpbm.RotateFlip(RotateFlipType.RotateNoneFlipX);
g.DrawImage(tmpBm, 290, 0);

u Localize o código no CD: \Code\Chapter 07\Ex03.

O método RotateFlip( ) realiza as várias operações de rotação/giro nas imagens com


base em um dos valores do tipo enumerado RotateFlipType. Os possíveis valores são os
mostrados na Tabela 7.2.

FIGURA 7.23 Imagens desenhadas com dois diferentes modos de interpolação.


168 Capítulo 7 Programação GDI+ – Desenhando no .NET

TABELA 7.2 Valores do tipo enumerado RotateFlipType


Valor Descrição
Rotate180FlipNone Rotaciona 180 graus, nenhum giro.
Rotate180FlipX Rotaciona 180 graus, gira horizontalmente.
Rotate180FlipXY Rotaciona 180 graus, gira horizontal e verticalmente.
Rotate180FlipY Rotaciona 180 graus, gira verticalmente.
Rotate270FlipNone Rotaciona 270 graus, nenhum giro.
Rotate270FlipX Rotaciona 270 graus, gira horizontalmente.
Rotate270FlipXY Rotaciona 270 graus, gira horizontal e verticalmente.
Rotate270FlipY Rotaciona 270 graus, gira verticalmente.
Rotate90FlipNone Rotaciona 90 graus, nenhum giro.
Rotate90FlipX Rotaciona 90 graus, gira horizontalmente.
Rotate90FlipXY Rotaciona 90 graus, gira horizontal e verticalmente.
Rotate90FlipY Rotaciona 90 graus, gira verticalmente.
RotateNoneFlipNone Não rotaciona, não gira.
RotateNoneFlipX Não rotaciona, gira horizontalmente.
RotateNoneFlipXY Não rotaciona, gira horizontal e verticalmente.
RotateNoneFlipY Não rotaciona, gira verticalmente.

A Figura 7.24 mostra a saída resultante dessa operação que, como pode ver, realiza
um efeito do tipo espelho ao girar.
Você também pode rotacionar um bitmap com o método RotateTransform( ), que
aceita os graus da rotação desejada como um parâmetro. O código para realizar essa ope-
ração é mostrado aqui:

g.RotateTransform(20);
g.DrawImage(MyBitmap, 250, -50);

A saída desse código é mostrada na Figura 7.25.

Utilização dos métodos de transformação


Na GDI+, há vários métodos que você pode utilizar para aplicar transformações a ima-
gens. As transformações que você pode utilizar entram nas classificações de rotação, es-
calonamento, corte e translação. As transformações por rotação envolvem rotacionar as
imagens sobre certo ponto. Para realizar as transformações por rotação, utilize o método
Graphics.RotateTransform( ). As transformações por escalonamento envolvem redimen-
sionar a imagem e requerem o método Graphics.ScaleTransform( ). As transformações por
translação envolvem mudar a imagem para um outro ponto. Utilize o método Grap-
hics.TranslateTransform( ) para realizar a transformação por translação.
Os métodos supracitados são fornecidos pela GDI+ a fim de tornar os métodos de
transformação mais comuns facilmente acessíveis. Algoritmos de transformação envol-
vem o uso de multiplicação de matriz a fim de alcançar os resultados desejados. Você
pode continuar a fazer isso com o método MultiplyTransform( ) que torna possíveis trans-
Trabalhando com imagens 169

formações por cortes. Esse método recebe uma classe Matrix, que encapsula a matriz 3x3
em que os cálculos necessários são aplicados para alcançar a transformação. Aqui, não
apresentaremos uma lição sobre a multiplicação de matrizes, mas demonstraremos o uso
de MultiplyTransform( ) para inclinar uma imagem, dando-lhe um efeito de movimento.

FIGURA 7.24 Girando um bitmap.

FIGURA 7.25 Rotacionando um bitmap em 20 graus.


170 Capítulo 7 Programação GDI+ – Desenhando no .NET

Primeiro, examinaremos uma transformação por escala. Essa é uma transformação


simples. O código a seguir ilustra isso:

g.ScaleTransform(1.5, 0.3);
g.DrawImage(MyBitmap, 0, 1000);

Aqui, configuramos um fator de escala de 1,5 multiplicado pela largura das imagens
e 0,3 multiplicado pela altura das imagens. A saída resultante é mostrada na Figura 7.26.

FIGURA 7.26 Resultado de ScaleTransformation( ).

Demonstrarei o uso dos métodos TranslateTransform( ) e RotateTransform( ) simulta-


neamente. A Listagem 7.19 contém o código que utiliza esses dois métodos.

LISTAGEM 7.19 Demo da transformação por rotação e por translação


1: procedure TWinForm1.RotateTransform(g: Graphics);
2: var
3: angle: Double;
4: begin
5: angle := 0.0;
6: while angle < 360.0 do
7: begin
8: g.ResetTransform;
9: g.TranslateTransform(275, 250);
10: g.RotateTransform(angle);
11: g.DrawString('Right round baby right round',
12: System.Drawing.Font.Create('Times New Roman', 14),
Trabalhando com imagens 171

LISTAGEM 7.19 Continuação


13: Brushes.Black, 25, 0);
14: angle := angle + 45.0
15: end;
16: end;

u Localize o código no CD: \Code\Chapter 07\Ex03.

Esse exemplo, embora não funcione especificamente com uma imagem, demonstra
o uso dos métodos. Ele desenha a mesma string, no formulário, em um ângulo que é in-
crementado em 45% para cada desenho. O método TranslateTransform( ) é utilizado para
posicionar as chamadas a imagens gráficas subseqüentes na coordenada x, y especificada
(linha 9). Em seguida, o método RotateTransform( ) é chamado para configurar o ângulo
de rotação (linha 10). Por fim, a string é desenhada. A saída desse exemplo é mostrada na
Figura 7.27.

FIGURA 7.27 Saída da transformação por rotação e por translação.

O método MultiplyTransform( ) pode ser utilizado para inclinar uma imagem. Os va-
lores atribuídos ao objeto Matrix, passado para o método MultiplyTranform( ), determina-
rão a forma resultante. A Listagem 7.20 mostra como utilizar esse método.

LISTAGEM 7.20 Utilizando o método MultiplyTransform( )


1: procedure TWinForm1.MultiplyTransform(g: Graphics);
2: var
3: mx: Matrix;
172 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.20 Continuação


4: begin
5: DrawNormal(g);
6: mx := Matrix.Create(1, 0, 0, 1, 0, 0);
7: mx.Shear(-0.65, 0);
8: g.MultiplyTransform(mx);
9: g.DrawImage(MyBitmap, 200, 200);
10: mx := Matrix.Create(1, 0, 1.5, 1, 0, 1);
11: g.MultiplyTransform(mx);
12: g.DrawImage(MyBitmap, 250, 50);
13: end;

u Localize o código no CD: \Code\Chapter 07\Ex03.

Esse exemplo desenha a imagem normalmente e então cria duas versões da imagem
inclinada. Os valores de Matrix utilizados na linha 6 resultam na impressão de que a ima-
gem que aparece se move para frente. Os valores inseridos na linha 10 apresentam uma
aparência de quebra. A Figura 7.28 mostra isso.

FIGURA 7.28 Saída de MultiplyTransform( ).

Criando uma miniatura


Ocasionalmente, você irá querer gerar uma miniatura para uma imagem. Algumas ima-
gens apresentam a capacidade de incluir miniaturas. O método Bitmap.GetThumbnailIma-
ge( ) retorna a miniatura contida em uma imagem. Se não houver uma miniatura, ela
será criada e retornada. Esse código é invocado como mostrado a seguir:
Revisitando os sistemas de coordenadas 173

procedure TWinForm1.Thumb(g: Graphics);


var
tn: Image;
tmpBm: Bitmap;
begin
tn := MyBitmap.GetThumbnailImage(75, 75, nil, nil);
tmpBm := Bitmap.Create(tn);
g.DrawImage(tmpBm, 10, 10);
end;

Revisitando os sistemas de coordenadas


A Listagem 7.19 fornece um exemplo breve de como lidar com o método Graphics.Trans-
lateTransform( ). Internamente, o método TranslateTransform( ) utiliza uma classe Matrix
para representar a matriz de transformação da coordenada global utilizada ao calcular a
posição, o ângulo e outros aspectos em que o desenho ocorre. Um exemplo simples da
mudança em que ponto [0, 0] nas coordenadas globais aparece nas coordenadas de pági-
na é mostrado na Listagem 7.21.

LISTAGEM 7.21 Mudando a origem da coordenada global com o TranslateTransform( )


1: procedure TWinForm.DrawBasicTransform(g: Graphics);
2: var
3: MyPen: Pen;
4: begin
5: MyPen := Pen.Create(Color.Black, 3);
6: g.DrawLine(MyPen, 50, 50, 300, 50);
7: g.DrawLine(MyPen, 50, 50, 50, 300);
8:
9: g.TranslateTransform(100, 100);
10: g.DrawLine(MyPen, 50, 50, 300, 50);
11: g.DrawLine(MyPen, 50, 50, 50, 300);
12: end;

u Localize o código no CD: \Code\Chapter 07\Ex04.

As linhas 6–7 desenham uma linha horizontal e uma vertical. A linha 9 utiliza o
método TranslateTransform( ) para efetivamente mudar onde a GDI+ vê a origem da coor-
denada global. Quando o método DrawLine( ) é chamado novamente, com os mesmos
valores de parâmetro, você verá na Figura 7.29 que eles são desenhados em um local
diferente nas coordenadas de página.
Você também pode alterar a unidade de medida utilizada nas coordenadas globais.
Por exemplo, digamos que você queira instruir a GDI+ a “desenhar uma linha iniciando
uma polegada a partir do topo da página e uma polegada à esquerda da página, com duas
polegadas de comprimento”.
174 Capítulo 7 Programação GDI+ – Desenhando no .NET

Bem, você pode fazer isso. Pense no código

var
MyPen: Pen;
begin
MyPen := Pen.Create(Color.Black, (1/g.DpiX)*3);
g.PageUnit := GraphicsUnit.Inch;
g.DrawLine(myPen, 1, 1, 1, 3);
end;

FIGURA 7.29 Resultado da mudança de origem com o TranslateTransform( ).

A propriedade Graphics.PageUnit contém um valor do tipo enumerado GraphicsUnit.


Esse valor determina como as coordenadas de página são interpretadas pela GDI+. Por-
tanto, com GraphicsUnit.Inch, o ponto [x=1, y = 2] é interpretado como uma polegada à es-
querda e duas polegadas a partir do topo da origem da página. Com GraphicsUnit.Pixel, os
mesmos valores são interpretados como 1 pixel à esquerda e 2 pixels a partir do topo da
página. Ambos, naturalmente, teriam dois resultados completamente diferentes. De
fato, provavelmente você não verá uma linha com comprimento de 2 pixels desenhada
com a opção Pixel. A Figura 7.30 mostra a saída do código anterior. Ela deve ser uma li-
nha de aproximadamente uma polegada à esquerda e no topo da página, estendendo-se
para baixo cerca de duas polegadas.

Exemplo de animação
Na demo de animação desta seção o fundo, o céu de uma noite clara, é invadido por um
disco voador que aparece e desaparece, provavelmente procurando raptar alguém. O
programa é realmente bem simples – de fato, muito mais simples do que a versão Win32,
que utilizou uma série de BitBlt( ) e operações mascaradas para alcançar transparência.
Exemplo de animação 175

FIGURA 7.30 Utilizando PageUnit para mudar a unidade de medida para polegadas.

NOTA
A versão Win32 da aplicação ainda está presente e compilada sob VCL.NET. Você a encontrará no
seguinte diretório do CD:
\Code\Chapter 07\AnimateVCL

Primeiro, vamos examinar a classe que contém o sprite, o disco voador. Ela é mostra-
da na Listagem 7.22.

LISTAGEM 7.22 Declaração do sprite


1: type
2:
3: TSpriteDirection = (sdLeft, sdRight, sdUp, sdDown);
4: TSpriteDirections = set of TSpriteDirection;
5:
6: TSprite = class
7: private
8: public
9: Width: integer;
10: Height: integer;
11: Left: integer;
12: Top: integer;
13: Saucer: Bitmap;
14: Direction: TSpriteDirections;
15: public
16: constructor Create;
176 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.22 Continuação


17: end;
18: ...
19:
20: constructor TSprite.Create;
21: begin
22: inherited;
23: Saucer := Bitmap.Create('saucer2.bmp');
24: Saucer.SetResolution(72, 72);
25: Left := 0;
26: Top := 0;
27:
28: Height := Saucer.Height;
29: Width := Saucer.Width;
30: Direction := [sdRight, sdDown];
31: end;

u Localize o código no CD: \Code\Chapter 07\Ex05.

Nessa listagem, incluímos apenas o código relevante para essa discussão e faremos o
mesmo com as listagens restantes. A classe Tsprite encapsula as propriedades de que pre-
ciso para o disco voador. Especificamente, precisamos da posição, do tamanho e tam-
bém da direção do movimento. A direção é mantida por um conjunto de tipos enumera-
dos TspriteDirection. O sprite é simplesmente um bitmap armazenado em disco. Ele é car-
regado e definimos uma resolução de 72, utilizada em todos os bitmaps nessa demo, para
manter a coerência (linha 24). A direção do sprite é então inicializada na linha 30. O for-
mulário principal dessa aplicação manterá uma instância desse sprite. A Listagem 7.23
mostra a declaração do formulário principal.

LISTAGEM 7.23 Declaração do formulário principal


1: TWinForm = class(System.Windows.Forms.Form)
2: strict private
3: procedure Timer1_Tick(sender: System.Object; e: System.EventArgs);
4: procedure Step1_Click1(sender: System.Object; e: System.EventArgs);
5: procedure Step2_Click(sender: System.Object; e: System.EventArgs);
6: procedure ClearSky_Click(sender: System.Object; e: System.EventArgs);
7: procedure Animate_Click(sender: System.Object; e: System.EventArgs);
8: strict protected
9: procedure MoveSaucer;
10: procedure ClearSky;
11: procedure Step1_Erase(aDemo: Boolean);
12: procedure Step2_DrawSaucerTrans(aDemo: Boolean);
13: procedure Step3_DrawSaucerPos(aDemo: Boolean);
14: private
15: // Desenho é feito para FskyImage
16: FSkyImage: Bitmap;
17: // FClearSkyImage retém o céu claro
Exemplo de animação 177

LISTAGEM 7.23 Continuação


18: FClearSkyImage: Bitmap;
19: // FskyGraphic são referências a FskyImages
20: FSkyGraphic: Graphics;
21: // CanvasGraphic refere-se ao formulário
22: CanvasGraphic: Graphics;
23:
24: FSaucer: TSprite;
25: FAnimate: Boolean;
26: FStep: Integer;
27: public
28: constructor Create;
29: end;

u Localize o código no CD: \Code\Chapter 07\Ex05.

As linhas 3–7 são handlers de evento para os vários botões no formulário. As linhas
9–13 são as várias procedures chamadas pelo handler de evento Timer1_Tick( ) para pro-
duzir a animação. FSkyImage é um Bitmap no qual todo o desenho será realizado. Esse Bitmap
é desenhado em memória, o que permite manipular o Bitmap sem causar uma atualização
da exibição. Essa manipulação de Bitmap na memória é o que permite realizar as opera-
ções de desenho eficientemente. Desenhar diretamente no formulário resultaria em um
movimento mais lento e oscilante. FClearSkyImage é uma cópia limpa do bitmap do céu.
Ele é utilizado para apagar o céu sujo, FSkyImage. FSkyGraphics é o objeto Graphics que será
utilizado para realizar as operações de desenho em FSkyImage. CanvasGraphics é o objeto
Graphics que se refere ao formulário. FSaucer é uma instância da classe TSprite contendo o
bitmap do disco voador. FAnimate é utilizado para determinar se a animação deve ser reali-
zada e FStep contém o número de pixels para mover o disco voador por operação de mo-
vimento. A Listagem 7.24 mostra a implementação do formulário principal.

LISTAGEM 7.24 Implementação da demo de animação


1: implementation
2:
3: procedure TWinForm.Step1_Click1(sender: System.Object;
4: e: System.EventArgs);
5: begin
6: Step1_Erase(True);
7: end;
8:
9: procedure TWinForm.Step2_Click(sender: System.Object;
10: e: System.EventArgs);
11: begin
12: MoveSaucer;
13: Step2_DrawSaucerTrans(True);
14: end;
15:
178 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.24 Continuação


16: procedure TWinForm.ClearSky_Click(sender: System.Object;
17: e: System.EventArgs);
18: begin
19: ClearSky;
20: end;
21:
22: procedure TWinForm.Animate_Click(sender: System.Object;
23: e: System.EventArgs);
24: begin
25: FAnimate := not FAnimate;
26: if FAnimate then
27: Button4.Text := 'Stop'
28: else
29: Button4.Text := 'Animate';
30: end;
31:
32: procedure TWinForm.Timer1_Tick(sender: System.Object;
33: e: System.EventArgs);
34: begin
35: if FAnimate then
36: begin
37: Step1_Erase(false);
38: MoveSaucer;
39: Step2_DrawSaucerTrans(false);
40: Step3_DrawSaucerPos(false);
41: end;
42: end;
43:
44: constructor TWinForm.Create;
45: begin
46: inherited Create;
47: InitializeComponent;
48:
49: FAnimate := False;
50:
51: FSkyImage := Bitmap.Create('sky2.bmp');
52: FSkyImage.SetResolution(72, 72);
53: FSkyGraphic := Graphics.FromImage(FSkyImage);
54:
55: FStep := 2;
56: FClearSkyImage := FSkyImage.Clone(Rectangle.Create(0, 0,
57: FSkyImage.Width, FSkyImage.Height), PixelFormat.Format32bppArgb);
58: FClearSkyImage.SetResolution(72, 72);
59:
60: FSaucer := TSprite.Create;
61: CanvasGraphic := Graphics.FromHwnd(Handle);
62: end;
63:
Exemplo de animação 179

LISTAGEM 7.24 Continuação


64: procedure TWinForm.MoveSaucer;
65: begin
66: // Salva os valores antigos
67: if (sdLeft in FSaucer.Direction) then
68: if FSaucer.Left > 0 then
69: FSaucer.Left := FSaucer.Left - FStep
70: else
71: FSaucer.Direction := FSaucer.Direction - [sdLeft] + [sdRight];
72:
73: if (sdDown in FSaucer.Direction) then
74: if FSaucer.Top+FSaucer.Height < FSkyImage.Height then
75: FSaucer.Top := FSaucer.Top + FStep
76: else
77: FSaucer.Direction := FSaucer.Direction - [sdDown] + [sdUp];
78:
79: if (sdUp in FSaucer.Direction) then
80: if FSaucer.Top > 0 then
81: FSaucer.Top := FSaucer.Top - FStep
82: else
83: FSaucer.Direction := FSaucer.Direction - [sdUp] + [sdDown];
84:
85: if (sdRight in FSaucer.Direction) then
86: if FSaucer.Left+FSaucer.Width < FSkyImage.Width then
87: FSaucer.Left := FSaucer.Left + FStep
88: else
89: FSaucer.Direction := FSaucer.Direction - [sdRight] + [sdLeft];
90: end;
91:
92: procedure TWinForm.ClearSky;
93: begin
94: FSkyGraphic.DrawImage(FClearSkyImage, 0, 0);
95: CanvasGraphic.DrawImageUnScaled(FSkyImage, 0, 0);
96: end;
97:
98: procedure TWinForm.Step1_Erase(aDemo: Boolean);
99: begin
100: // Copia uma parte do céu armazenado para a imagem na memória
101: FSkyGraphic.DrawImage(FClearSkyImage,
102: Rectangle.Create(FSaucer.Left-2, FSaucer.Top-2, FSaucer.Width+2,
103: FSaucer.Height+2), FSaucer.Left-2, FSaucer.Top-2, FSaucer.Width+2,
104: FSaucer.Height+2, GraphicsUnit.Pixel);
105:
106: if aDemo then
107: CanvasGraphic.DrawImageUnscaled(FSkyImage, 0, 0);
108: end;
109:
110: procedure TWinForm.Step2_DrawSaucerTrans(aDemo: Boolean);
111: var
180 Capítulo 7 Programação GDI+ – Desenhando no .NET

LISTAGEM 7.24 Continuação


112: imAttr: ImageAttributes;
113: begin
114: ImAttr := ImageAttributes.Create;
115: ImAttr.SetColorKey(Color.Black, Color.Black, ColorAdjustType.Default);
116: FSkyGraphic.DrawImage(FSaucer.Saucer, Rectangle.Create(FSaucer.Left,
117: FSaucer.Top, FSaucer.Width, FSaucer.Height), 0, 0,
118: FSaucer.Width, FSaucer.Height, GraphicsUnit.Pixel, imAttr);
119: if aDemo then
120: CanvasGraphic.DrawImageUnScaled(FSkyImage, 0, 0);
121: end;
122:
123: procedure TWinForm.Step3_DrawSaucerPos(aDemo: Boolean);
124: begin
125: CanvasGraphic.DrawImageUnscaled(FSkyImage, 0, 0);
126: end;
127:
128: end.

u Localize o código no CD: \Code\Chapter 07\Ex05.

Os métodos Step1_Click( ) e Step2_Click( ) (linhas 3–14) são utilizados para fazer uma
inspeção passo a passo nas chamadas de procedure que o handler de evento Timer1_Timer( )
faz. A variável Boolean é passada para as procedures nos métodos Step1_Erase( ) e Step1_DrawSa-
ucerTrans( )informando aos métodos que desenhem o resultado da operação no formulário
depois de fazer isso. Isso permite ilustrar a operação passo a passo. Ao criar a animação, o de-
senho na tela não é feito devido ao desempenho e à oscilação mencionados anteriormente.
O handler de evento para o botão de animação Animate_Click( ) (linhas 22–30) permite ao ti-
mer fazer seu trabalho e invocar as várias procedures que produzem a animação.
Vamos supor que o ponto de partida da animação é o mostrado na Figura 7.31.
A primeira ação que ocorre ao produzir a animação é Step1_Erase( ). Esse método (li-
nhas 98–108) copia um retângulo da imagem limpa que ocupa o mesmo espaço do disco
voador para a imagem suja (FSkyImage). Isso faz com que o disco voador seja apagado da
imagem. A Figura 7.32 mostra os resultados desse passo.
Em seguida, Timer1_Timer( ) chama MoveSaucer( ). Esse método avalia a direção e a po-
sição do disco voador em relação à superfície de desenho. Ele então determina se é neces-
sário alterar a direção do disco voador. Ele é escrito de modo que o disco voador rebata
nos limites do céu.
O Step2_DrawSaucerTrans( ) chama o método Graphics.DrawImage( ), mas dessa vez pas-
sa um objeto ImageAttribute como o último parâmetro para DrawImage( ). Com essa classe,
definida no namespace System.Drawing.Imaging, você pode especificar operações comple-
xas de renderização de imagens como ajustes de cores, escala de cinza, ajustes de gama e
assim por diante. Nesse exemplo, seu método SetColorKey é chamado, o que configura o
intervalo de cores da transparência. A cor que cerca o disco voador no mapa de bit real é
preta. Portanto, configurar isso como o intervalo de cores chave torna transparente qual-
quer coisa em preto. O disco voador com esses atributos é então desenhado no céu agora
limpo. Como o método MoveSaucer( ) foi chamado, o disco voador é realocado.
Exemplo de animação 181

FIGURA 7.31 Ponto de partida da animação.

FIGURA 7.32 O disco voador foi apagado.

Pressionando apenas o botão Draw Saucer, tanto o método MoveSaucer( ) como o


DrawSaucerTrans( ) são chamados. Em outras palavras, a operação para apagar é pulada. A Fi-
gura 7.33 mostra como seria isso. Embora não sejam os resultados desejados, mostra o
movimento do disco voador.
182 Capítulo 7 Programação GDI+ – Desenhando no .NET

FIGURA 7.33 Disco voador se movendo quando não foi apagado.

Por fim, Step3_DrawSaucerPos( ), que é chamado, desenha o bitmap do fundo, FSkyIma-


ge, na superfície dos formulários sobrescrevendo o original. Para cada movimento do dis-
co voador, uma operação write é gravada na superfície do formulário.
A título de ilustração, esse exemplo utiliza a abordagem da animação simplificada
utilizando um timer. Na prática, você utilizaria um modelo com threads.
NESTE CAPÍTULO
CAPÍTULO 8 — Recursos do Mono

— História do Mono
Mono – um projeto — Por que o Mono?

.NET para múltiplas — Guia do Mono

— Instalação/Configuração
plataformas — Criando seu primeiro
programa no Mono
por Steven Beebe — Executando assemblies
gerados no Delphi sob o
Mono (no Linux)
O Mono é uma implementação de código-fonte aberto — O ASP.NET para múltiplas
(open source) do .NET Framework. O Projeto Mono
plataformas
fornece um mecanismo para entender a alta produtividade
do desenvolvimento .NET em um ambiente de múltiplas — O ADO.NET com o Mono
plataformas. Com objetivos semelhantes aos do Java, o — O Mono e o Apache
Mono eleva a aposta no jogo do “escrever uma vez,
executar em qualquer lugar”. Este capítulo introduz o — O Mono e o
Mono, seus recursos, sua história e objetivos. Ele mostra System.Windows.Forms
como configurar o Mono de modo que você possa testá-lo.
Este capítulo também ilustra como é possível utilizar o
Delphi for .NET tendo como alvo múltiplas plataformas
utilizando o Mono. Por fim, veremos como aprender mais
sobre o Mono e como utilizá-lo.

Recursos do Mono
A implementação do Mono inclui
— Um ambiente CLI (Common Language
Infrastructure) que fornece compilação JIT, class
loading (carregamento de classe) e garbage
collection (coleta de lixo).
— Uma biblioteca de classes que funciona com
qualquer linguagem que tenha como alvo o CLR
— Um compilador para a linguagem C#

— Uma implementação das bibliotecas de classes


.NET
— Uma implementação da infra-estrutura .NET
Remoting
— Um compilador Visual Basic .NET
184 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

— Um compilador Jscript

— Recursos específicos do Linux/Unix: bibliotecas de classes específicas ao GNOME,


Mono.Posix, GTK#

OS OBJETIVOS DO PROJETO MONO


As FAQs do Projeto Mono afirmam o objetivo do projeto como
“O Projeto Mono é uma iniciativa de desenvolvimento em código-fonte aberto, patrocinada pela
Ximian, que trabalha no desenvolvimento de uma versão Unix de código-fonte aberto da platafor-
ma de desenvolvimento Microsoft .NET. Seu objetivo é permitir aos desenvolvedores Unix cons-
truir e implantar aplicações .NET para diversas plataformas. O projeto implementará várias tecno-
logias desenvolvidas pela Microsoft que já foram submetidas ao ECMA para padronização.”
Essa citação é encontrada na URL a seguir:
(www.go-mono.com/faq.html#basics)

História do Mono
O Projeto Mono foi iniciado por Miguel de Icaza, fundador, em 2001, do Projeto
GNOME de código-fonte aberto. Miguel trabalhou na Ximian Incorporated, produtora
de um desktop GNOME voltado a negócios (Ximian Desktop) e editores da Evolution,
um popular cliente/PIM de correio eletrônico de código-fonte aberto. Miguel foi possi-
velmente o primeiro na comunidade de código-fonte aberto a reconhecer o potencial da
plataforma .NET para orientar a produtividade de desenvolvimento. O Projeto GNOME
tem uma longa história voltada à independência de linguagem. O atrativo que Miguel
viu no .NET foi a elegância de sua solução técnica para independência de linguagem,
algo em que o Projeto GNOME havia se concentrado desde o início. Depois de uma in-
vestigação mais profunda, ele se convenceu da poderosa produtividade da plataforma.
Em fevereiro de 2002, Miguel explicou seu pensamento em uma postagem em um news-
group popular intitulada “The Long Reply”:

“O .NET Framework realmente se resume à produtividade: mesmo se a Microsoft impulsionar es-


sas tecnologias para criar Web Services, o benefício mais importante destas é o aumento de produ-
tividade do programador.”

Trecho de Miguel de Icaza em “Mono and Gnome. The Long Reply.”

NOTA
A postagem anterior pode ser localizada realizando uma pesquisa no Google sobre “Miguel de Ica-
za” + “The Long Reply.”

Miguel e outros viram o .NET Framework como uma solução de desenvolvimento


mais produtiva do que a arquitetura atual do GNOME. O Mono foi a maneira de levar es-
ses benefícios à comunidade de código-fonte aberto. À medida que o projeto cresceu, os
objetivos se expandiram para reconhecer o potencial de desenvolvimento e distribuição
de múltiplas plataformas.
Por que o Mono? 185

Por que o Mono?


Como desenvolvedor em Windows, por que você se interessaria pelo Mono? Principal-
mente por causa da flexibilidade que ele fornece. Se você fornecer soluções a uma única
empresa ou desenvolver soluções a serem utilizadas por várias empresas, o Mono forne-
cerá as opções adicionais para soluções de distribuição. O Mono executa em uma varie-
dade de hardwares e plataformas de sistemas operacionais. As plataformas do Mono in-
cluem Windows, Linux, Solaris, FreeBSD, HP-UX e Mac OS X.
O Mono separa a decisão quanto à plataforma de desenvolvimento da decisão quan-
to à plataforma de produção. A plataforma de desenvolvimento pode ser selecionada e
otimizada para produtividade do desenvolvimento. A plataforma de distribuição pode
ser selecionada a fim de otimizar o custo operacional, a estabilidade e a segurança. As res-
postas ótimas irão variar por organização e talvez por projeto. O Mono oferece flexibili-
dade adicional, que resulta em melhores respostas, a um conjunto mais amplo de organi-
zações do que o .NET no Windows por si só.
O Mono também fornece conectividade adicional de bancos de dados, expandindo
as opções fornecidas pelo ADO.NET. O Mono fornece bibliotecas para diversas platafor-
mas para acesso a dados, tendo por alvo
— Postgress
— MySQL

— IBM DB2

— Sybase
— Oracle

— Microsoft SQL Server

— Firebird
— OLE DB

— ODBC
— GDA da GNOME

A maneira correta de pensar sobre o Mono não é como uma substituição do .NET, mas
como uma extensão do .NET que fornece flexibilidade adicional de distribuição. Como
provedor de soluções internas, você pode separar a decisão quanto à plataforma de desen-
volvimento da decisão quanto à plataforma operacional. Como provedor de soluções para
o mercado, o Mono permite que você tenha por alvo uma base de clientes expandida, per-
mitindo que suas soluções possam ser executadas em diversas plataformas.

Guia do Mono
No momento em que escrevia este capítulo, o guia publicado do Mono abrangia as distri-
buições até 2005. Em geral, as distribuições do Mono são lançadas para acompanhar a
evolução das funcionalidades no .NET. Os pacotes de distribuição do Mono incluem
uma construção estável com os recursos adequados para produção e uma construção ins-
tável com um snapshot (instantâneo) dos recursos em desenvolvimento.
186 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Embora esses planos olhem para o futuro e estejam sujeitos a revisões durante sua
evolução, o Mono, hoje, oferece capacidades reais (vários meses antes do plano inicial de
entrega do Mono 1.0). O compilador Mono C# é bem completo e utilizado atualmente
para construir o Mono. A implementação Mono ASP.NET é suficientemente funcional
para executar a demo IbuySpy.NET sem modificações.

Objetivos do Mono 1.0


O Mono 1.0 disponibilizará amplos componentes de funcionalidade da .NET Framework
1.1 API e fornecerá um pacote separado a fim de oferecer compatibilidade com a .NET 1.0
API. O Mono 1.0 não disponibilizará uma implementação da .NET 1.1 API completa,
mas implementará boa parte do framework para distribuir aplicações sobre o Mono.
A versão 1.0 do Mono está prevista para o segundo trimestre de 2004 e, no momento
em que escrevíamos este livro, havia planos de incluir
— Compilador do C#

— Máquina virtual, com JIT e pré-compilador


— Assembler, disassembler de IL

— Bibliotecas básicas: mscorlib, System, System.XML

— System.Data e providers de bancos de dados Mono


— System.Web: plataforma para aplicações Web e módulo de integração Apache

— System.Web.Services: suporte de servidor e cliente


— System.Drawing

— System.DirectoryServices

— Suporte JIT: arquiteturas x86 e PPC (interpretador disponível para outras arquite-
turas)
— Integração Java por meio da IKVM, uma JVM que executa sobre o Mono
(www.ikvm.net)
— Interface incorporada para runtime

Visite www.go-mono.com/mono-1.0.html para acessar uma listagem de assemblies, lingua-


gens e máquinas virtuais que serão disponibilizados no Mono 1.0.

Objetivos do Mono 1.2


Há planos de que a versão 1.2 do Mono adicione recursos e melhorias à versão Whidbey
do .NET Framework da Microsoft. Espera-se que o Mono 1.2 inclua
— API básica para o .NET Framework 1.2

— Melhorias no ASP.NET 2.0

— Melhorias no Remoting da Whidbey


— Melhorias na XML da Whidbey

— Suporte de compilador para Visual Basic.NET e Jscript


Instalação/Configuração 187

— System.Windows.Forms com a .NET 1.0 API. A construção instável incluirá as


APIs do 1.1 e 1.2.
O Mono 1.2 está previsto para o final de 2004.

Objetivos do Mono 1.4


Para 2005, a equipe do Mono planeja a distribuição da Versão 1.4 – pensando em mudar
os recursos da construção instável para a estável e continuar a monitorar o desenvolvi-
mento da .NET 1.2 API.

Instalação/Configuração
Para nossos propósitos, somente iremos instalar o ambiente runtime do Mono. Se estiver
interessado em entender o Mono mais profundamente, recomendo que você instale o
código-fonte e construa o Mono com seu próprio compilador C#, o mcs.
O ambiente runtime do Mono é mais rapidamente configurado no Linux por meio
de uma distribuição RPM. Os pacotes para as várias versões do Red Hat, Fedora, Mandra-
ke e SuSe estão disponíveis por meio do utilitário de instalação Red Carpet da Novell/Xi-
mian. Há também um Windows Installer se quiser experimentar o Mono no Microsoft
Windows. Se quiser executar o Mono com o Apache, instale o Apache antes de prosse-
guir com a instalação do Mono. A configuração do Apache será abordada mais adiante
neste capítulo. O Mono suporta o Apache 1.3 e o Apache 2.0.
O processo de instalação do Mono é descrito a seguir, utilizando o Red Carpet.

Instalação do Mono – utilizando o Red Carpet


A Ximian oferece uma versão paga e uma gratuita do serviço Red Carpet . O serviço pago
oferece conexões de largura de banda mais altas; entretanto, para instalar e manter uma
instalação do Mono, o serviço gratuito é adequado. A instalação do Red Carpet é feita no
site Web www.ximian.com.
1. Visite http://www.ximian.com/products/redcarpet/download.html.

2. Selecione sua distribuição.

3. Faça o download dos pacotes mostrados na Tabela 8.1 em um local adequado,


como ~/downloads.

TABELA 8.1 Pacotes do Red Carpet


Pacote Descrição
rcd-{version}.{arch}.rpm o daemon do Red Carpet
red-carpet-{version}.{arch}.rpm a GUI do Red Carpet (não disponível em todas as distribuições)
rug-{version}.{arch}.rpm o programa de atualização de linha de comando do Red
Carpet
188 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

4. Abra uma janela terminal e navegue para ~/downloads (ou para o diretório que você
selecionou para o rpms do Red Carpet).
5. Emita um comando su ao usuário root, fornecendo a senha root se solicitada.

6. Emita o comando a seguir no prompt de comando:

$ rpm -Uvh red-carpet-{version}.{arch}.rpm rcd-{version}.{arch}.rpm rug-


{version}.{arch}.rpm

7. Inicie o daemon do Red Carpet.

$ rcd

8. Teste a instalação emitindo um ping no daemon do Red Carpet com o comando a


seguir:

$ rug ping

Você deve ver uma saída semelhante a esta:

Daemon identified itself as:


Red Carpet Daemon 2.0.1
Copyright (C) 2000-2003 Ximian Inc.

9. Carregue a Red Carpet GUI. Para uma distribuição Red Hat 9.0, a instalação do rpm
irá gerar uma entrada de menu para o Red Carpet em System Tools, Red Carpet do
menu principal. Será solicitada a senha root.

Se você não encontrar uma entrada no menu, insira o seguinte no prompt de co-
mando (como root) :

$ red-carpet&

Se sua distribuição não veio com uma Red Carpet GUI (mostrada na Figura 8.1), os
passos a seguir podem ser completados utilizando rug na linha de comando. A se-
guir, uma lista de comandos que pode ser utilizada para completar os passos res-
tantes:

$ rug – help

10. Selecione o ícone Channels na barra de ferramentas.

11. Selecione Mono Channel.

12. Selecione a guia Available Software.

13. Selecione os pacotes mostrados na Tabela 8.2.


Instalação/Configuração 189

FIGURA 8.1 Red Carpet GUI.

TABELA 8.2 Pacotes do Mono


Pacote Descrição
mono O runtime Mono CIL.
mono-wine Versão corrigida (patched) do Wine que suporta System.Windows.Forms.
monodoc Navegador de documentação do Mono.
xsp Pequeno servidor Web para hospedar as classes System.Web.
*-debuginfo Informações de depuração para cada um dos pacotes supracitados.
mod-mono Módulo Apache que interfaceia com o Mono para executar páginas ASP.NET.
Esse pacote é necessário se você pretende utilizar o Mono com o Apache.

À medida que seleciona os pacotes, é possível vê-los adicionados ao painel Pen-


ding Actions.

14. Selecione Run Now na barra de ferramentas. Uma caixa de diálogo de resolução de
dependência será brevemente exibida.

15. Os resultados da resolução de dependência serão exibidos para sua revisão. Se


estiver satisfeito, selecione Continue.

16. A caixa de diálogo Processing Transaction será exibida à medida que ocorre o
download, a verificação e a instalação dos pacotes. A caixa de diálogo exibirá Tran-
saction Finished depois que o processamento estiver completo. Neste momento,
feche o Red Carpet.

17. Verifique a instalação do Mono no prompt de comando:

$ mono -V

Você deve ver uma saída semelhante a


190 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Mono JIT compiler version 0.29, (C) 2002, 2003 Ximian, Inc.

18. Verifique o compilador Mono C# utilizando o seguinte no prompt de comando:

$ mcs – about

Isso deve gerar uma saída semelhante a

The Mono C# compiler is (C) 2001, 2002, 2003 Ximian, Inc.


The compiler source code is released under the terms of the GNU GPL
For more information on Mono, visit the project Web site
http://www.go-mono.com
The compiler was written by Miguel de Icaza, Ravi Pratap and Martin Baulig

Criando seu primeiro programa no Mono


Esta seção mostra como criar um pequeno programa em C# que fará um teste mais com-
pleto do ambiente do Mono. Abra um editor na máquina Linux. Para este exemplo, utili-
zaremos gedit; entretanto, qualquer editor servirá. (Se preferir, digite command_line_appli-
cations, run vi, ou emacs no prompt de comando). Insira o código a seguir:

using System;
class Hello
{
public static void Main(String[ ] args) {
Console.WriteLine("Mono is working...");
for (int i = 0; i < args.Length; i++)
Console.WriteLine("{0}. Hello {1}!", i, args[i]);
}
}

u Localize o código no CD: \Code\Chapter 08\Ex01.

Salve o arquivo como hello.cs e abra uma janela de terminal. Navegue pela localiza-
ção de hello.cs e execute

$ mcs hello.cs

Se tudo correr bem, você verá Compilation Succeeded ecoada no terminal. Se hou-
ver erros, faça as alterações conforme necessário com base nas mensagens do compilador
na janela do terminal. Uma compilação bem-sucedida irá gerar um assembly .NET que
pode ser executado pelo ambiente runtime do .NET. O Mono utiliza a mesma convenção
de extensão .exe utilizada na plataforma Windows, embora as extensões de arquivos não
tenham a mesma importância no Linux como têm no Windows. Para verificar se o as-
sembly foi produzido, faça uma pesquisa na lista de diretórios (comando ls) e localize
hello.exe na listagem. Em seguida, executaremos o assembly no ambiente runtime do
Mono. No prompt de comando, execute

$ mono hello.exe {nome1} {nome2}


Executando assemblies gerados no Delphi sob o Mono (no Linux) 191

Substitua os dois nomes por nome1 e nome2. A saída deve ser semelhante a isto:

Mono is working...
0. Hello Hannah!
1. Hello Sarah!

NOTA
Você pode remover a extensão .exe do hello.exe e ainda executar Mono hello {nome1} {nome2}. O
Linux não utiliza a extensão de arquivo para determinar o tipo de arquivo. Neste caso, a extensão
.exe é utilizada para portabilidade.

Parabéns! Você acaba de escrever e executar um programa em C# no Linux. Para


uma demonstração simples do potencial conjunto do .NET e do Mono copie hello.exe
para uma máquina Windows com o ambiente runtime do .NET instalado. Execute hel-
lo.exe no prompt de comando do Windows. Os resultados devem corresponder ao se-
guinte (com a possível exceção dos nomes):

C:\>hello diane hannah sarah


Mono is working...
0. Hello diane!
1. Hello hannah!
2. Hello sarah!
C:\>

Executando assemblies gerados


no Delphi sob o Mono (no Linux)
Agora, retornaremos ao Delphi for .NET. Inicie uma nova aplicação console selecionan-
do File, New, Other, Console Application na pasta Projects do Delphi for .NET. Será apre-
sentado um projeto em branco.
Você observará que o Delphi inclui a seguinte cláusula uses por padrão:

Uses
Sysutils;

Embora seja desejável para uma aplicação Win32, isso causará erros em tempo de
execução no Mono. Muitas das classes VCL da Borland contêm referências a classes es-
pecíficas e importações nativas ao Win32 e, portanto, não podem ser executadas no
Mono. Remova a referência a SysUtils e evite o impulso de adicionar outras units VCL
ao exemplo.
A aplicação console consiste nisto: menu baseado em texto, solicitação para entrada
de usuário, validação da entrada de usuário e processamento da entrada de usuário. A
parte central da aplicação é mostrada na Listagem 8.1.
192 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Listagem 8.1 Aplicação console no MonoMenu .NET


1: program MonoMenu;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses
6: MonoFuncs in 'MonoFuncs.pas';
7:
8: var
9: MainMenu: String;
10: UserResponse: Integer;
11: PossibleResponses: array of Char;
12:
13:
14: begin
15:
16: // a parte central da aplicação
17: MainMenu :=
18: 'Welcome to Cross Platform Development - Are you:'+#10#13+#10#13+
19: '1. Curious as to how this is going to work?'+#10#13+
20: '2. A skeptic?'+#10#13+
21: '3. Thinking this rocks?'+#10#13+#10#13+
22: 'Enter a number (1-3):';
23:
24: // identifica respostas válidas
25: SetLength(PossibleResponses, 3);
26: PossibleResponses[0] := '1';
27: PossibleResponses[1] := '2';
28: PossibleResponses[2] := '3';
29:
30: // solicita uma resposta, assegurando que obtemos a resposta que queremos
31: repeat
32: UserResponse := GetInput(MainMenu, PossibleResponses);
33: until UserResponse >= 0;
34:
35: // reconhece a resposta com algum estímulo
36: Case UserResponse+1 of
37: 1: WriteLn('Just watch, you will be impressed');
38: 2: WriteLn('So am I. But this is cool.');
39: 3: WriteLn('It does.');
40: end; // case
41: end.

u Localize o código no CD: \Code\Chapter 08\Ex02.

As linhas 17–22 definem a string que representa o menu principal da aplicação. Nas
linhas 25–28, um array é criado e carregado com respostas válidas. Na linha 32, GetInput é
chamado para tratar o processamento da entrada de usuário que é independente do
Executando assemblies gerados no Delphi sob o Mono (no Linux) 193

exemplo atual específico. Para assegurar que uma resposta válida seja obtida, GetInput é
colocado em um loop do tipo repeat-until.
O método GetInput será criado em uma unit separada. Adicione uma nova unit ao
projeto e crie o GetInput mostrado na Listagem 8.2.

Listagem 8.2 Unit MonoFuncs


1: unit MonoFuncs;
2:
3: interface
4:
5: function GetInput(aConsoleMessage: String;
6: aPossibleResponses: Array of Char):Integer;
7:
8: implementation
9:
10: function GetInput(aConsoleMessage: String;
11: aPossibleResponses: Array of Char):Integer;
12: var
13: Response: String;
14: i: Integer;
15: begin
16: // estabelece um resultado padrão
17: Result := -1;
18:
19: // solicita entrada de usuário
20: WriteLn('');
21: WriteLn(aConsoleMessage);
22: Write(' > ');
23:
24: // lê a entrada
25: ReadLn(Response);
26:
27: // determina se a entrada está no result set esperado
28: for i := 0 to High(aPossibleResponses) do
29: if Response = aPossibleResponses[i] then
30: Result := i;
31: end;
32:
33: end.

u Localize o código no CD: \Code\Chapter 08\Ex02.

Utilizando dois parâmetros, GetInput exibe uma mensagem, lê a entrada de usuário e


retorna um inteiro. Na linha 17, um resultado padrão de -1 é atribuído. As linhas 20–22
exibem o conteúdo do parâmetro aConsoleMessage. Em seguida, a resposta é lida (linha 25) e
então avaliada em relação aos valores no parâmetro do array aPossibleResponses. Se a res-
posta for localizada em aPossibleResponses, um Result de retorno será atribuído (linha 30).
194 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Compile o projeto para produzir o arquivo MonoMenu.exe. Abra uma sessão no console
Windows e execute a aplicação. Você deve ver uma saída como a mostrada na Figura 8.2.

FIGURA 8.2 MonoMenu em ação.

Copie o arquivo MonoMenu.exe para o computador Linux que está executando o Mono.

NOTA
Se tiver de fazer algum trabalho sério entre um computador Windows e um computador Linux,
você precisará instalar o Samba a fim de suportar compartilhamento de arquivos. Recomendo vee-
mentemente o Webmin ou SWAT como as ferramentas de configuração de GUI para simplificar a
configuração do Samba. Uma configuração detalhada do Samba está além do escopo deste livro.
Visite www.samba.org para obter detalhes completos sobre a instalação e configuração do Samba.

Execute o arquivo no Linux com o seguinte comando:


$ mono MonoMenu.exe

Se tudo correr bem, seus resultados devem ser idênticos aos mostrados na Figura 8.3
– com a exceção do prompt de comando e, possivelmente, de sua resposta.
Esse exemplo ilustra o compilador Mono JIT funcionando com o código de lingua-
gem intermediária (IL) .NET produzido pelo Delphi for .NET. Em outras palavras, você
acabou de executar o código Delphi no Linux.
Esquecer de remover ou evitar as classes VCL resultará em erros de compilação ao
executar no Mono. Você verá uma saída semelhante a esta:

[steve@schroeder cs]$ mono MonoMenu.exe

(MonoMenu.exe:311): WARNING **: Failed to load function GetVersionEx


➥from kenel32.dll
(MonoMenu.exe:311): WARNING **: Failed to load function GetVersionEx
➥from kenel32.dll
Unhandled Exception: System.TypeInitializationException: An exception was
➥throw by the type initializer for MonoMenu.Unit – ->
➥System.TypeInitializationExcepton: An exception was thrown by
➥the type initializer for Borland.Vcl.SysUtils.Unt – ->
➥System.MissingMethodException: A missing method exception has occurred.
O ASP.NET para múltiplas plataformas 195

in <0x00042> (wrapper managed-to-native) Borland.Vcl.Windows.Unit:GetVersionEx


Borland.Vcl.Windows._OSVERSIONINFO&)
in <0x0006c> Borland.Vcl.SysUtils.Unit:InitPlatformId ( )
in <0x00053> Borland.Vcl.SysUtils.Unit:Borland.Vcl.SysUtils ( )
in <0x006b8> Borland.Vcl.SysUtils.Unit:.cctor ( )
– - End of inner exception stack trace – -
in (unmanaged) /usr/lib/libmono.so.0(mono_raise_exception+0x20) [0x400acef7]
in (unmanaged) /usr/lib/libmono.so.0(mono_runtime_class_init+0x2c3) [0x400a9860
in (unmanaged) /usr/lib/libmono.so.0 [0x400afde8]
in <0x0001d> System.Runtime.CompilerServices.RuntimeHelpers:
➥RunClassConstructor(System.RuntimeTypeHandle)
in <0x00026> MonoMenu.Unit:.cctor ( )
– - End of inner exception stack trace – -

FIGURA 8.3 MonoMenu executando no Linux.

O ASP.NET para múltiplas plataformas


A aplicação de exemplo para o ASP.NET será uma aplicação simples, de uma página, que
utiliza o code-behind e responde a uma interação simples do usuário. A aplicação solici-
tará que o usuário insira um nome e selecione uma data. Ao clicar no botão no formulá-
rio, as entradas de usuário serão reapresentadas.
Execute o Delphi e crie uma nova aplicação ASP.NET (File, New, ASP.NET Web
Application). A aplicação de exemplo é chamada MonoASP.

DICA
Iremos executar esse exemplo na IIS no Windows; entretanto, a IIS ou Cassini será suficiente para
verificar o funcionamento da aplicação de exemplo antes de implantá-la no Mono.
196 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Selecione um diretório apropriado para a aplicação. Mude a propriedade page layout


de Document para FlowLayout. Isso é feito selecionando Document no Object Inspector
e modificando a propriedade PageLayout. FlowLayout permitirá que o navegador determi-
ne o posicionamento relativo na página. Uma abordagem alternativa é utilizada no
exemplo do Capítulo 26 para manter um controle relativo apropriado sobre o posicio-
namento em todos os mecanismos de exibição em HTML. O formulário Web inicial é
salvo como MainGreeting.aspx.
Na categoria Web Controls da Tool Palette, coloque um Label e um TextBox na primei-
ra linha. Utilizando FlowLayout, você estrutura uma página quase da mesma maneira
como faria ao utilizar um processador de texto. Por exemplo, novas linhas de página são
inseridas utilizando as teclas Enter ou Return. Insira uma nova linha em MainGreeting sele-
cionando o final da primeira linha (um pouco antes de TextBox) e pressione Enter. Para as
três próximas linhas, coloque um Label, Calendar, Button, e Label. Modifique as proprieda-
des de controle para que se assemelhem ao Web Form mostrado na Figura 8.4.

FIGURA 8.4 O layout do formulário MainGreeting.aspx.

Adicione um handler de evento Click a Button e insira o código a seguir:

procedure TWebForm1.Button1_Click(sender: System.Object;


e: System.EventArgs);
begin
lblResponse.Text := lblResponse.Text + ' Hello, '+txtbxName.Text+'!'+
' You have selected ' + Calendar1.SelectedDate.ToString'.';
end;

u Localize o código no CD: \Code\Chapter 08\Ex03.


O ASP.NET para múltiplas plataformas 197

Compile e execute a aplicação no Windows, corrigindo quaisquer erros de compila-


ção. Insira um nome, selecione uma data e clique em Submit. A página deve recarregar
com uma resposta refletindo suas seleções.
Agora executaremos a aplicação no Mono.

Distribuição do ASP.NET para o Mono


O Mono ASP.NET inclui Web Forms e Web services. Ele oferece duas opções para servido-
res Web:
— XSP – um servidor Web de peso leve, concebido principalmente para testes, escri-
to em C#
— Apache – um servidor Web de nível de produção

O XSP é semelhante ao Cassini pelo fato de fornecer hospedagem para o namespace


System.Web, mas não fornece um conjunto de recursos suficiente para servir a um site Web
de produção.

Configuração do XSP
Utilizar o XSP manterá o mínimo de questões de instalação e configuração. O XSP é exe-
cutado com o comando a seguir:
$ mono {path to xsp}/xsp.exe

Parâmetros de runtime do XSP


Você deve conhecer certos parâmetros de runtime que talvez precise configurar. Eles são
discutidos nas seções a seguir.

Diretório-raiz Web
O diretório-raiz Web para o XSP será o diretório em que o XSP for executado. Para especi-
ficar um diretório-raiz Web, adicione o parâmetro –root {path} ao executar o XSP. No
exemplo a seguir, o arquivo xsp.exe está localizado no diretório /usr/bin e o diretório-raiz
Web está em /usr/share/doc/xsp/test/mono:
$ mono /usr/bin/xsp.exe --root /usr/share/doc/xsp/test/mono

A instalação padrão pelo Red Carpet colocará xsp.exe tanto em /usr/bin como em
/usr/share/doc/xsp/test. Aplicações de exemplo são incluídas no diretório de teste.

Diretório-raiz da aplicação
O XSP trata um diretório-raiz virtual e um diretório físico da aplicação. Por padrão, o di-
retório virtual '/' é mapeado para o diretório físico em que o xsp foi executado. O XSP es-
pera que os arquivos .aspx estejam no diretório atual e os arquivos .dll no subdiretório
bin. Para especificar um diretório-raiz da aplicação diferente, utilize o parâmetro --apli-
cations ao executar o xsp, como mostrado no comando a seguir:

$ mono /usr/bin/xsp.exe –applications {virtual path}:{physical path}


198 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

A especificação de múltiplos diretórios-raiz da aplicação é realizada passando uma


lista delimitada por ponto-e-vírgula de pares de caminho virtual/físico como o parâme-
tro –applications. Para simplificar a transferência de arquivos para e a partir do Windows
e do Linux, crie um diretório no seu diretório inicial (home directory) no Linux como mo-
noapps. O diretório monoapps será exibido no Windows por meio de um compartilhamento
homes padrão do Samba, permitindo que os arquivos sejam copiados com o Windows
Explorer. O exemplo a seguir executará o xsp com dois diretórios-raiz da aplicação: o dire-
tório de instalação xsp e o subdiretório monoapps do meu diretório home:

$ mono /usr/bin/xsp.exe –application /:/usr/share/doc/xsp/test;/


➥monoapps:/home/steve/monoapps

Porta e endereço
O XSP ouvirá na porta 8080 por padrão.
O XSP ouvirá no endereço 0.0.0.0 por padrão. Se a máquina de teste do XSP for exi-
bida na Internet, é recomendável alterar os padrões da porta e do endereço IP.
O diretório-raiz e o diretório da aplicação especificados precisarão ter permissões +rx
para o usuário que estiver executando o XSP.
Crie um diretório apropriado para o arquivo MonoASP na máquina que está executan-
do o Mono com um subdiretório bin. (Eu utilizo monoapps e monoapps/bin.) Copie os
diretórios MainGreeting.aspx e MonoApp.dll criados pelo Delphi for .NET para a máquina que
está executando o Mono. Em uma janela de console, execute o XSP, incluindo monoapps
como diretório-raiz da aplicação. Você deve ver uma saída semelhante a esta:

Listening on port: 8080


Listening on address: 0.0.0.0
Root directory: /usr/share/doc/xsp/test
Hit Return to stop the server.
Carregue um navegador na máquina Linux e digite localhost:8080/MainGreeting.aspx
na barra de localização. Deve ser exibida uma página notavelmente semelhante àquela
que você viu no Windows. Digite um nome, selecione uma data e verifique se o co-
de-behind funciona adequadamente. Carregue um navegador no computador Windows
e insira http://{ip address}:8080/MainGreeting.aspx na barra de localização. A página deve
ser exatamente a mesma página executada de dentro do Delphi.

Algumas armadilhas e uma pequena extensão de exemplo


Nesse ponto, nosso exemplo funciona no Windows e no Linux. Uma abordagem alter-
nativa seria utilizar uma variável TDateTime e a função DateToStr para processar a seleção de
calendário. Modificando o handler de evento Button_Click para que ele se assemelhe ao
seguinte:

procedure TWebForm1.Button1_Click(sender: System.Object;


e: System.EventArgs);
var
lDate: TDateTime;
O ADO.NET com o Mono 199

begin
lDate := Calendar1.SelectedDate;
lblResponse.Text := lblResponse.Text + ' Hello, '+
txtbxName.Text+'!'+' You have selected ' + DateToStr(lDate)+'.';
end;

Você precisará adicionar SysUtils à cláusula uses.


Ao executar a aplicação no Windows, você deve receber os mesmos resultados obti-
dos ao utilizar Calendar1.SelectedDate.ToString. Entretanto, ao executar sob o Mono, esse
código irá gerar um erro de runtime. Um rastreamento de pilha será exibido no navega-
dor, mostrando uma System.TypeInitializationException que tenta inicializar Borland.
Vcl.SysUtils.Unit.
A questão aqui é que o melhor caminho para portabilidade é objetivar o CLR, utili-
zando classes dentro dos namespaces .NET e evitar incluir os assemblies de runtime da
Borland.

O ADO.NET com o Mono


Nossa próxima aplicação de exemplo demonstra uma aplicação Web que acessa infor-
mações em um banco de dados, combinando as capacidades do ASP.NET, do ADO.NET e
do Mono. O exemplo consiste em um formulário Web de Login para estabelecer acesso
ao banco de dados e a um formulário Web Employees para exibir as informações do ban-
co de dados. O exemplo requer acesso ao MS SQL Server e ao banco de dados Northwind.

NOTA
No exemplo, é solicitado ao usuário as informações de login do SQL Server. Isso é puramente para
propósitos ilustrativos. Em uma aplicação de produção, você deve estabelecer as informações es-
pecíficas de login da aplicação que possam ser validadas. Depois que o acesso de um usuário é va-
lidado, a aplicação ASP.NET pode acessar o SQL Server por meio de um Application Role. O Appli-
cation Role é configurado para fornecer as permissões necessárias e também limitado às permis-
sões exigidas pela aplicação.

Crie uma nova aplicação ASP.NET no Delphi. O nome da aplicação é MonoADO. Utili-
zando FlowLayout, coloque os controles Web no formulário de login como mostrado na
Figura 8.5.
Adicione uma declaração de constante e um handler de evento btnSubmit_Click( )
como mostrado aqui:

const
c_UrlStr = 'Employees.aspx?UserName={0}&Password={1}';
...
procedure TWebForm1.btnSubmit_Click(sender: System.Object;
e: System.EventArgs);
begin
if IsPostBack then
200 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Response.Redirect(System.String.Format(c_UrlStr, [txtbxUserName.Text,
txtbxPassword.Text]));
end;

FIGURA 8.5 O layout do formulário de acesso ao banco de dados Northwind.

u Localize o código no CD: \Code\Chapter 08\Ex04.

Isso chamará a segunda página na nossa aplicação, passando o UserName e Password for-
necidos na URL. Salve o formulário como Login.aspx.
Adicione uma nova página ASP.NET ao projeto. Esse formulário exibirá as informa-
ções sobre um funcionário a partir do banco de dados Northwind. Coloque um Label, Re-
peater e um Label, cada um em uma linha separada. O rótulo superior servirá como um
cabeçalho e o rótulo inferior exibirá a data/hora do relatório. O Repeater exibirá os dados
sobre o funcionário e é configurado de maneira semelhante ao exemplo 10 no Capítulo
27. (Localize o código no CD: \Code\Chapter 27\Ex10). Para este exemplo, as colunas Title e
HireDate foram adicionadas.
O método Page_Load( ) cria os componentes necessários para estabelecer a conectivi-
dade de banco de dados e estabelecer a vinculação ao componente Repeater. O código
aparece na Listagem 8.3.

Listagem 8.3 Handler de evento Page_Load( ) para o exemplo de banco de dados


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_sel = 'select * from employees';
4: c_cnstr = 'server=192.168.0.102;database=Northwind;'+'
5: 'Trusted_Connection=No;User ID={0};Pwd={1}';
O ADO.NET com o Mono 201

LISTAGEM 8.3 Continuação


6: var
7: sqlcn: SqlConnection;
8: sqlDA: SqlDataAdapter;
9: ds: DataSet;
10: dt: DataTable;
11: lDateTime: TDateTime;
12: lConnectionStr: String;
13: begin
14: // estabelece a string de conexão a partir da solicitação de página
15: lConnectionStr := System.String.Format(c_cnstr,
16: [Request.QueryString['UserName'],
17: Request.QueryString['Password']]);
18:
19: sqlcn := SqlConnection.Create(lConnectionStr);
20: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
21: ds := DataSet.Create;
22: sqlDA.Fill(ds, 'employees');
23: try
24: dt := ds.Tables['employees'];
25: Repeater1.DataSource := dt.DefaultView;
26: DataBind;
27: lDateTime := System.DateTime.Now;
28: lblProducedOn.Text := lblProducedOn.Text+' '+lDateTime.ToString;
29: finally
30: sqlcn.Close;
31: end;
32: end

u Localize o código no CD: \Code\Chapter 08\Ex04.

Observe a string de conexão constante c_cnstr (linha 4). Esse exemplo especifica um
endereço IP e inclui as informações de login do SQL Server na string de conexão, uma vez
que a aplicação será executada de uma máquina diferente no banco de dados. Os compo-
nentes UserName e Password da string de conexão serão construídos a partir das informações
passadas para a página Employees.aspx na URL (linha 15); mais uma vez, isso não deve ser
feito em uma ambiente de produção.
Compile e execute a aplicação, fornecendo um nome de usuário e uma senha válida
ao SQL Server. Depois de submetê-los, você deve ver a tela mostrada na Figura 8.6.
Para executar MonoADO no Mono, identifique ou crie o diretório-raiz da aplicação apro-
priado. Reutilizarei monoapps do exemplo ASP.NET. Copie os arquivos Login.aspx e Employe-
es.aspxs para o diretório-raiz da aplicação XSP no Linux. Copie MonoADO.dll para o diretó-
rio bin. Em seguida, carregue um navegador que aponte para localhost:8080/Login.aspx.
Alternativamente, aponte o navegador no seu computador Windows para o XSP. A vi-
sualização dos dados no Microsoft SQL Server em um navegador Linux é mostrada na Fi-
gura 8.7.
202 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

FIGURA 8.6 Resultados de um login bem-sucedido.

FIGURA 8.7 Tabela Northwind Employees no Linux.

Isso é realmente fenomenal. Embora o exemplo que implementamos seja algo trivial,
desafio-o a selecionar outro conjunto de ferramentas qualquer e construir uma aplicação
Web no Windows que possa ser diretamente implantada em um servidor Web Linux e
que obtenha os dados no MS SQL Server – e fazer isso em menos de 30 minutos.
O Mono e o Apache 203

O Mono e o Apache
Embora o XSP seja mais adequado a propósitos de teste e desenvolvimento, ele não é
concebido para distribuição em produção. O Mono torna o Apache (www.apache.org) HTTP
Server acessível a desenvolvedores .NET.

PESQUISA SOBRE SERVIDORES WEB


Na pesquisa realizada em fevereiro de 2004 sobre servidores Web feita pela Netcraft, mais de 47
milhões dos servidores Web capturados na pesquisa (67,21%) executava o Apache. O desenvolvi-
mento iniciou no centro do que viria a ser o servidor Web Apache na National Center for Super-
computing Applications (NCSA), na Universidade de Illinois, em Urbana-Champaign, no início da
década de 90. Quando o desenvolvedor-chave saiu da NCSA, um pequeno grupo de webmasters
que tinha corrigido e estendido o trabalho da NCSA se reuniu a fim de formar a equipe original
central da Apache. Entre 1995–1996, a Apache experimentou um crescimento meteórico e, a par-
tir de maio de 1996, dominou todos os outros servidores Web na pesquisa da Netcraft.

O Apache fornece vários benefícios: robustez, segurança, escalabilidade de preço/de-


sempenho e flexibilidade de plataforma são alguns dos principais. Essa plataforma de
distribuição de primeiro nível agora está disponível para desenvolvedores em Delphi que
criam aplicações ASP.NET.
O Mono fornece os módulos mod_mono e mod-mono-server para utilização no Apache,
hospedando tanto o runtime do Mono como do ASP.NET, permitindo ao Apache servir
páginas ASP.NET e suportar code-behind. O mod_mono suporta tanto o Apache 1.3 como o
Apache 2.0. Binários do Apache são incluídos em todas as distribuições importantes do
Linux. Alternativamente, o download do código-fonte pode ser facilmente feito (você
pode localizar um mirror [espelho] de download em www.apache.org) e construído.
O Apache precisa ser configurado para utilizar o mod_mono. Inicie adicionando as se-
guintes linhas a httpd.conf (normalmente localizado em /etc/httpd/conf/) :

AddType application/x-asp-net .aspx .ashx .asmx .ascx .asax .config


LoadModule mono_module modules/mod_mono.so
MonoUnixSocket /tmp/mod_mono_server

A diretiva AddType identifica extensões de arquivos para um tipo de conteúdo especi-


ficado – nesse caso, x-asp-net. A diretiva LoadModule faz com que o Apache carregue o
mod_mono na inicialização. Ajuste o caminho nessa entrada a fim de refletir o caminho ao
mod_mono da sua instalação. Se não tiver certeza, execute o mod_mono.so de localização em
um prompt de comando.
Você talvez também queira alterar o Apache para que ele ouça na mesma porta do
XSP 8080 adicionando Listen 8080 em httpd.conf. Para testar o Apache e o mod_mono nos
exemplos do Mono, adicione

Alias /mono "/usr/share/doc/xsp/test"

a httpd.conf. Isso redirecionará quaisquer solicitações a {ip da sua máquina de teste}/mono


para o diretório que contém os exemplos do Mono. Mais uma vez, se necessário ajuste o
caminho a xsp/test da sua distribuição.
204 Capítulo 8 Mono – um projeto .NET para múltiplas plataformas

Para testar a configuração do servidor, comece executando mod-mono-server.exe.


(O Red Carpet colocará isso em /usr/bin, e também em /usr/share/doc/xsp/test.)

cd /usr/share/doc/xsp/test
mono /usr/bin/mod-mono-server.exe –root . –applications /mono:.

Esses comandos executarão o servidor com a raiz web em /usr/share/doc/test/xsp e di-


recionarão as solicitações a /mono para o mesmo diretório. Os parâmetros para mod-mo-
no-server são os mesmo do XSP. Executar mod-mono-server.exe criará o arquivo de socket
Unix /tmp/mod_mono_server. Tanto o Apache como o mod-mono-server devem ter permissões
de gravação nesse arquivo. Há duas maneiras de fazer isso:
— Para fins de teste, isso pode ser feito executando mod-mono-server e emitindo chmod
666 /tmp/mod_mono_server no prompt de comando antes de executar ou reiniciar o
Apache.
— Em um cenário de produção, será preferível executar ambos

mod-mono-server and apache under the same user.

Para os objetivos desse teste, com o chmod atribua as permissões em /tmp/mod_mono_ser-


ver. Inicie ou reinicie o Apache com um comando como

/usr/sbin/apachectl –k (re)start

ou

/etc/init.d/httpd (re)start

Preste atenção às mensagens de erro; se tudo correr bem, carregue um navegador e


aponte-o para http://127.0.0.1:8080/mono/index.aspx. Omita o :8080 se você não incluiu
Listen 8080 em httpd.conf. Você deve ver a página inicial dos exemplos do Mono. Para in-
formações adicionais sobre o mod_mono e o Apache, visite www.go-mono/asp-net.html.
Você agora pode testar os exemplos deste capítulo copiando os arquivos .aspx para
/usr/share/doc/xsp/test e os arquivos .dll para /usr/share/doc/xsp/test/bin.

O Mono e o System.Windows.Forms
O Mono também implementa o componente System.Windows.Forms (SWF) da .NET, embo-
ra o desenvolvimento nessa área esteja avançando muito mais lentamente do que em
outras partes do framework. Em parte, isso ocorre devido a questões técnicas relaciona-
das à implementação do SWF. Entretanto, a promessa real e, portanto, o foco do desen-
volvimento para diversas plataformas, reside no ASP.NET e no ADO.NET. Aplicações ba-
seadas em navegador fornecem uma independência natural de plataforma no lado do
cliente, evitando várias questões técnicas complexas de uma aplicação GUI nativa. Con-
tudo, o trabalho prossegue no SWF para Mono.
No momento em que escrevia este capítulo, a implementação do Mono do Sys-
tem.Windows.Forms passava por mudanças estruturais (mono-0.30) e não era funcional. O
System.Windows.Forms está atualmente trilhando dois caminhos:
O Mono e o System.Windows.Forms 205

1. Uma implementação do SWF sobre o Wine, uma implementação de código-fonte


aberto da Windows API for X
2. Uma implementação do SWF utilizando GTK#, que fornece vinculações C# para
GTK+

Fundamentalmente, não vejo isso como um caminho firme para desenvolvedores


em Windows que procuram desenvolver para múltiplas plataformas. Mas o excelente
trabalho presente no Mono justifica ficar de olho nessa parte do projeto.

PARTICIPE DO MONO
A comunidade do código-fonte aberto tem por base o preceito de que uma pequena contribuição
de cada membro produz um número cada vez maior de retornos – o efeito bola de neve, se você
desejar. A melhor maneira de participar geralmente envolve uma progressão. Inicie utilizando o
Mono. Faça perguntas por meio de fóruns on-line e listas de mala direta. Aprenda as capacidades e
como implantá-las. Informe bugs e recursos necessários. Forneça respostas a outras pessoas que
estão começando.
Se seu progresso nunca for além do ponto de retribuir o que aprendeu, a comunidade continuará
a prosperar. Entretanto, deixe-me adverti-lo. Embora seja tremendamente animador experimen-
tar a força das novas tecnologias de desenvolvimento como o .NET, é um pouco extenuante con-
tribuir com seu desenvolvimento. Ao encontrar um bug, corrija-o. Ou, ao descobrir que um recur-
so é extremamente necessário, contribua com ele.
Se optar ou não por participar, saiba que o Projeto Mono fornece um framework poderoso para
que você tire proveito das duas habilidades de desenvolvimento no Delphi for .NET para um mun-
do de múltiplas plataformas.

AP R E N D A M A I S SO B RE O M O NO
A maioria dos recursos sobre o Mono pode ser encontrada no site Web www.go-mono.com. Para mo-
nitorar o progresso do desenvolvimento e a assistência às questões de distribuição, o Mono man-
tém várias listas de mala direta. Todas oferecem a opção de receber um resumo diário, minimizan-
do a inconveniência do tráfego.
NESTE CAPÍTULO
— Como a Garbage Collection CAPÍTULO 9
funciona
— Construtores Gerenciamento de
— Finalização

— O padrão Dispose –
memória e garbage
IDisposable
— Questões de desempenho
collection
com relação à finalização

U ma das principais causas de programas falhos é o


gerenciamento inadequado de memória pelos
desenvolvedores. Possivelmente os erros mais comuns
sejam não liberar memória desnecessária ou referir-se à
memória que já foi liberada. O .NET Framework fornece
o Garbage Collector (GC), responsável pelo
gerenciamento de memória – liberando assim o
desenvolvedor dessa tarefa. Este capítulo discute a
mecânica de como o GC funciona e então abrange
alguma utilização prática do gerenciamento de memória
em seu código. Para desenvolvedores em Delphi em
particular, o GC impõe algumas maneiras diferentes de
pensar sobre gerenciamento de recursos e limpeza.

Como a Garbage Collection


funciona
Você deve conhecer dois tipos de recursos que devem ser
gerenciados.. Estes são os recursos gerenciados e recursos
não-gerenciados. Recursos gerenciados são aqueles que o
CLR sabe gerenciar porque são definidos dentro das
especificações de tipo do CLR. Recursos não-gerenciados
são aqueles que o CLR não sabe gerenciar. Exemplos
desses recursos são os que exigem um Handle, como um
arquivo ou recurso do Windows (bitmap, ícone, mutex e
assim por diante).
O GC segue um mecanismo simples para liberar
memória no heap de objetos que não são mais utilizados
dentro da aplicação. Considere a Figura 9.1.
Essa figura representa o heap que contém alguns
objetos alocados em uma aplicação. Quando o GC inicia,
ele refere-se a algumas tabelas internas criadas pelo
Como a Garbage Collection funciona 207

Objeto A
Objeto B

Objeto C Objeto A
Objeto D Objeto D
Objeto E Objeto F

Objeto F

FIGURA 9.1 Objetos no heap.

Objeto A
Objeto D

Objeto F Objeto A
Objeto D

Objeto F

FIGURA 9.2 Heap compactado.

compilador JIT que armazena referências em objetos chamados raízes. Você pode pensar
nas raízes como posições de memória como objetos globais e estáticos, objetos na pilha
de threads, objetos referidos pela fila FReachable, objetos referidos por registradores de
CPU e assim por diante. Em outras palavras, raízes são objetos que ainda são referidos
pela aplicação em que o GC não pode liberar. O GC então constrói, em sua própria estru-
tura, raízes que correspondem aos objetos no heap. Ao terminar, a memória inacessível
no heap é liberada e os objetos acessíveis são compactados no heap, como mostrado na
208 Capítulo 9 Gerenciamento de memória e garbage collection

Figura 9.2. O GC também deve ajustar as referências à raiz porque os objetos ao quais elas
referem-se foram realocados.
Essa é uma explicação simplificada de como o GC funciona. Há obviamente muito
mais sobre isso; boa parte está além do que pode ser abrangido aqui. Por exemplo, as refe-
rências aninhadas também precisam ser tratadas como referências ativas (live references)
– isto é, objetos que são referidos por outros objetos. Isso não é mostrado nas Figuras 9.1
ou 9.2, mas você pode imaginar que quando um relacionamento como o Objeto D refe-
rencia o Objeto F, o Objeto F precisa ser tratado como ativo pelo GC da mesma maneira
que o Objeto D é tratado no processo de coleta/compactação.
Um dos aspectos sobre o modo como o GC gerencia a memória é o fato de ele contar
com um algoritmo geracional.

Garbage Collection geracional


Se o GC utilizasse a descrição simplificada recentemente apresentada, seu desempenho
seria inaceitável dado o número de objetos e a mudança de memória que aconteceriam
em uma única pass-through (ciclo, passagem). Por essa razão, o GC é baseado em um pa-
radigma geracional que melhora o desempenho, uma vez que o GC, na maioria das ve-
zes, só realizará sua mágica em uma pequena parte do heap durante uma dada execução.
No esquema geracional, o heap é organizado como mostrado na Figura 9.3.

Objeto A
Objeto B
Objeto C
Geração 0
Objeto D
Objeto E

Objeto F
Objeto G
Objeto H

Geração 1

Objeto I
Objeto J

Geração 3

FIGURA 9.3 Heap geracional.


Como a Garbage Collection funciona 209

Objeto A
Objeto B
Objeto C
Geração 0
Objeto D
Objeto E

Geração 1

Geração 3

FIGURA 9.4 Heap geracional – antes da coleta.

Grosso modo, a Figura 9.3 representa como o heap é. Essa representação não é exata,
mas serve a esse propósito ilustrativo.
Por exemplo, suponha que em nossa aplicação há vários objetos que acabamos de
criar. Eles poderiam aparecer como mostrado na Figura 9.4.
Esses objetos seriam a primeira geração de objetos no heap. Eles são a geração 0 por-
que nunca sobreviveram a um ciclo do GC. Na Figura 9.4, os objetos sombreados são
aqueles não mais referenciados pela aplicação. Em outras palavras, eles não são mais uti-
lizados. Cada geração tem um limiar máximo, com a geração 0 sendo a menor e a gera-
ção 2, a maior. Quando esse limiar é excedido, o GC inicia. O GC localizará objetos
não-referenciados e irá liberar o espaço no heap que eles ocupam. Agora, o GC compacta
o heap de modo que ele apareça como na Figura 9.5. A Figura 9.5 também mostra alguns
objetos adicionais que foram alocados a partir da passagem inicial do GC. Os objetos que
sobreviveram ao ciclo do GC agora são considerados como objetos na geração 1. Os obje-
tos alocados recentemente são a geração 0.
Mais uma vez, quando a geração 0 estiver cheia, o GC iniciará e realizará sua limpeza
da geração 0, tornando os objetos que sobreviveram à passagem objetos da geração 1.
Esse processo continua até um ponto em que o limiar máximo da geração 1 tenha sido
excedido e não possa conter outros objetos. O GC agora deve seguir os mesmos passos
com relação aos objetos da geração 1, localizando aqueles não mais utilizados e liberan-
do memória. Os objetos que sobrevivem a essa passagem tornam-se objetos da geração 2.
210 Capítulo 9 Gerenciamento de memória e garbage collection

Você deve ser capaz de perceber que, na maioria das vezes, o GC só funcionará na ge-
ração 0. Ignorando os objetos nas gerações 1 e 2, o desempenho do GC é aumentado.
Embora não seja necessário entender como o Garbage Collector funciona para desenvol-
ver aplicações .NET, isso é útil porque há implicações sobre como você escreve seu códi-
go que serão abordadas mais adiante.

Objeto F
Objeto A
Objeto A
Geração 0

Objeto A
Objeto C

Geração 1

Geração 3

FIGURA 9.5 Heap geracional depois da primeira passagem do GC.

Invocando o Garbage Collection


É improvável que você precise invocar o GC programaticamente. A classe System.GC defi-
ne vários métodos que você pode utilizar para iniciar ou determinar as informações so-
bre o GC.
Os dois métodos que você pode chamar para forçar uma coleta pelo GC são:

Collect(Generation: integer);
Collect( );

O primeiro recebe a geração que você deseja coletar como um parâmetro integer.
Isso força uma coleta inclusiva, portanto, passando 2, as gerações 0–2 serão coletadas. O
último método realiza uma coleta nas gerações 0– System.GC.MaxGeneration. System.GC.Max-
Construtores 211

Generation contém o número total de gerações suportado pelo GC. Atualmente, ele retor-
na o valor 2. É possível que o GC suporte mais gerações em algum momento.
Há alguns métodos informacionais adicionais em System.GC:

GetGeneration( ) recebe um parâmetro System.Object e retorna a geração do objeto.

GetTotalMemory( ) retorna um número de bytes hipotético a ser alocado .

Construtores
Uma das regras simples sobre construtores no Delphi for .NET é diferente no Delphi
Win32. Você não pode acessar membros de classe a partir de classes herdadas antes de
chamar o construtor herdado. Isso resultará em erro de tempo de compilação. Você pode
acessar membros da própria classe. Portanto, para as dadas classes, os comentários ano-
tam as instruções válidas e inválidas dentro do construtor.

TMyBase = class(TObject)
FBaseField: integer;
end;

TMyObject = class(TMyBase)
FMyObjectField: integer;
constructor Create;
end;

A implementação do construtor anterior seria

constructor TMyObject.Create;
begin
FBaseField := 3; // erro
FMyObjectField := 3; // válido
inherited;
FBaseField := 3; // válido
FMyObjectField := 3; // válido
end;

Finalização
A liberação dos recursos utilizados por objetos no mundo do .NET é diferente daquilo
com que os desenvolvedores Delphi estão habituados. Já sabemos que o GC irá liberar os
recursos gerenciados no heap quando eles não forem utilizados. Ainda é necessário libe-
rar recursos não-gerenciados, como um handle de arquivos, explicitamente porque o GC
não saberá fazer isso.
No passado, a liberação dos recursos utilizando o Delphi contava com o destrutor,
que era chamado sempre que o método Free( ) fosse invocado no objeto. O destrutor se
parecia a algo assim
212 Capítulo 9 Gerenciamento de memória e garbage collection

destructor TMyObject.Destroy;
begin
FreeResource;
inherited;
end;

Ainda há o construtor no Delphi for .NET, mas, agora, ele faz algo diferente. Antes
de entrarmos nos detalhes dessa diferença, vamos examinar como o .NET libera recursos
não-gerenciados.
Um objeto que contém recursos não-gerenciados deve sobrescrever um método Fina-
lize( ), que todos os objetos .NET contêm. Nesse método está o local em que você iria li-
berar todos os recursos não-gerenciados. Por exemplo, uma seção crítica é um recurso
não-gerenciado. A seguir, a declaração de uma classe que empacotará esse recurso
não-gerenciado:

TCriticalSection = class(TObject)
private
FSection: TRTLCriticalSection;
strict protected
procedure Finalize; override;
public
constructor Create;end;

Isso é obviamente um empacotador incompleto para uma seção crítica concebida


simplesmente para ilustrar como o finalizador seria implementado. A implementação do
método Finalize( ) é mostrada aqui:

procedure TCriticalSection.Finalize;
begin
DeleteCriticalSection(FSection);
inherited;
end;

Ela supõe que FSection foi inicializada em outra parte, como no constructor. Qual a
diferença entre isso e o padrão destructor com os quais todos os desenvolvedores em
Delphi estão acostumados? O método Finalize( ) é invocado pelo GC; ele não é aces-
sado diretamente quando um objeto não é mais referenciado. Assim, quando uma
instância do objeto TCriticalSection não é mais referenciada, o GC invocará o método
Finalize( ) no processo de liberação do objeto no heap. Essa é a maneira de assegurar
que seus recursos não-gerenciados sejam adequadamente limpos. Embora isso seja
conveniente, você precisa levar em consideração se o recurso não-gerenciado pode
permanecer até o GC iniciar. Por exemplo, você poderia estar guardando um handle
de arquivos, impedindo desnecessariamente o acesso a esse arquivo. É aí que o padrão
dispose entra.
O padrão Dispose – IDisposable 213

NOTA
A maior parte do código da aplicação nunca precisará manter recursos não-gerenciados brutos e
assim nunca precisará sobrescrever Finalize( ). Em geral, somente as classes FCL existentes,
componentes de terceiros de baixo nível e códigos de classe teriam de fazer isso. A maioria dos re-
cursos não-gerenciados utilizados já foi empacotada pelas classes FCL (FileStream, Control, Moni-
tor e outras).

O padrão Dispose – IDisposable


O padrão Dispose oferece aos desenvolvedores um mecanismo explícito para liberar re-
cursos não-gerenciados imediatamente em vez de contar com um ciclo do GC. Isso é rea-
lizado implementando a interface IDisposable.

Exemplo de IDisposable
Considere a classe TCriticalSection na seção anterior. A seguir, a definição dessa classe re-
trabalhada que implementa a IDisposable:

TCriticalSection = class(TObject, IDisposable)


private
FSection: TRTLCriticalSection;
FCSValid: Boolean;
strict protected
procedure Finalize; override;
public
constructor Create;
procedure Dispose;
end;

Você deve perceber que o objeto agora implementa a IDisposable e sua procedure Dis-
pose( ). Dessa forma, um campo FCSValid Boolean será inicializado como False e configura-
do como True quando a seção crítica for alocada. Além disso, a procedure do Finalize( ) é
mantida. Uma explicação sobre por que Finalize( ) é mantido vem a seguir. A implemen-
tação do Dispose( ) é mostrada aqui:

procedure TCriticalSection.Dispose;
begin
if FCSValid then
begin
DeleteCriticalSection(FSection);
GC.SuppressFinalize(Self);
FCSValid := False;
end;
end;

O método Dispose( ) libera o recurso não-gerenciado e então chama o método


SuppressFinalize( )do Garbage Collector. SuppressFinalize( ) impede que o GC chame o
214 Capítulo 9 Gerenciamento de memória e garbage collection

método Finalize( ) no objeto especificado. Por fim, FCSValid é configurado como False.
O método Finalize( ) re-codificado é mostrado aqui:

procedure TCriticalSection.Finalize;
begin
if FCSValid then
DeleteCriticalSection(FSection);
inherited;
end;

Tanto o método Finalize( ) como o Dispose( ) avaliam o valor do campo FCSValid


para determinar se há uma referência válida ao recurso não-gerenciado; se houver, ele
chama o método para liberar esse recurso.
A pergunta que poderia surgir é por que precisamos do método Finalize( ) se acaba-
mos de fornecer um método Dispose( ) que realiza a mesma coisa. A razão é que é garanti-
do que o método Finalize( ) será chamado pelo GC. O método Dispose( ) não. Não é ga-
rantido que ele seja chamado. Ele só é chamado se o método Free( ) do próprio objeto for
chamado utilizando o método padrão do Delphi de proteção de recursos como mostrado
aqui:

cs := TCriticalSection.Create;
try
// utiliza cs
finally
cs.Free;
end;

Em outras palavras, é possível que o desenvolvedor se esqueça de chamar o destruc-


tor do seu objeto, o que é possível a menos que ele saiba que deve fazer isso. Dado esse ce-
nário, Dispose( ) não seria chamado e o objeto ainda seria marcado para finalização pelo
GC porque GC.SuppressFinalize( ) não teria sido chamado. Além disso, FCSValid ainda se-
ria true, a menos que o handle nunca fosse alocado. O GC chamará o método Finalize( )
e, em última instância, a seção crítica será adequadamente liberada. Portanto, você pode
ver que ter os dois métodos, Dispose( ) e Finalize( ), é a abordagem mais segura. Se Dispo-
se( ) for invocado explicitamente, o recurso será liberado imediatamente e o GC não
terá de lidar com a finalização desse objeto, o que, a propósito, é um benefício ao desem-
penho do GC. Se Dispose( ) não for chamado, pelo menos o recurso será liberado.

Implementando a IDisposable automaticamente


Agora que você viu a maneira longa de implementar a IDisposable, esta seção mostra
como fazer isso utilizando a construção de destrutor, familiar aos desenvolvedores em
Delphi, e o método recomendável pela Borland.
Escrevendo um destrutor que segue o formato específico do

destructor Destroy; override;


O padrão Dispose – IDisposable 215

o compilador Delphi for .NET automaticamente implementará a interface IDisposable


para a sua classe. Vejamos a versão final da declaração da classe TCriticalSection:

TCriticalSection = class(TObject)
private
FSection: TRTLCriticalSection;
FCSValid: Boolean;
strict protected
procedure Finalize; override;
public
constructor Create;
destructor Destroy; override;
end;

Não deve surpreendê-lo o fato de a implementação ser exatamente igual ao método


Dispose( ), com a exceção de uma instrução inherited adicional:

destructor TCriticalSection.Destroy;
begin
if FCSValid then
begin
DeleteCriticalSection(FSection);
GC.SuppressFinalize(Self);
FCSValid := False;
end;
inherited;
end;

Se ela for praticamente a mesma, por que todo esse estardalhaço sobre utilizar uma
construção de destrutor versus implementar explicitamente a IDisposable? De um lado,
isso oferece aos atuais desenvolvedores Delphi a facilidade de trabalho que acompanha a
familiaridade. Mais importante, permite aos desenvolvedores escreverem objetos que
têm por alvo tanto plataformas .NET como Win32, sem diferenciar entre aqueles que so-
frem garbage collection e aqueles que não sofrem.
Eis algumas regras que você deve conhecer ao utilizar esse padrão destructor/dispose:
— O nome do destrutor deve ser Destroy.

— O destrutor deve ser sobrescrito com a diretiva override.

— O destrutor não deve possuir parâmetros.

— Você não pode fazer com que a interface IDisposable e o destrutor Destroy( ) sejam
implementados pelo seu objeto.

A questão a ser lembrada sobre o padrão destructor Destroy( ) é que, como ocorre
com Dispose( ), Destroy( ) não é chamado automaticamente e deve ser invocado cha-
mando o método Free( ) do objeto. Se um objeto encapsular um ou mais objetos in-
ternos que implementam IDispose ou tenham destrutores, o objeto externo também
216 Capítulo 9 Gerenciamento de memória e garbage collection

deverá implementar, encaminhando as chamadas Dispose feitas pelos usuários da


classe. Essa é a razão por que um projeto WinForm, por exemplo, sempre inclui o mé-
todo Dispose.

Questões de desempenho com relação à finalização


O GC, embora torne a vida dos programadores mais fácil, exige que o programador siga
certas diretrizes com relação aos finalizadores.
Se um objeto tiver um finalizador, ele será mantido em uma lista global chamada lis-
ta de finalização. O GC utiliza essa lista para determinar quais objetos precisam chamar o
método Finalize( ) durante uma interação. Durante uma execução inicial do GC na ge-
ração 0, ele irá percorrer a lista de finalização a fim de determinar se quaisquer objetos
não-referenciados podem ser liberados. Se puderem, uma referência a eles é colocada na
fila Freachable e a referência na lista de finalização será removida. A fila Freachable (pro-
nuncia-se “F-reachable” e significa “acessível por Finalize”) é uma lista separada de refe-
rências a objetos que precisam invocar seu método Finalize( ). O ato de adicionar uma
referência a um objeto nessa fila Freachable considera o objeto acessível e, portanto,
não-coletável. Há agora uma raiz para o objeto. Esse é um estado temporário para objetos
na fila Freachable. O GC, depois de passar por um ciclo de GC, carregará um thread espe-
cial de alta prioridade responsável por percorrer a fila Freachable, removendo as referên-
cias aos objetos e invocando seu método Finalize( ). Depois que isso ocorre, o objeto se
torna realmente inacessível e pode ser coletado na próxima execução do GC. Todo esse
trabalho deve mostrar que objetos com finalizadores podem ser caros. Eles consomem
memória e exigem dois ciclos de coleta para liberar sua memória.
Os desenvolvedores devem implementar finalizadores criteriosamente em peque-
nas classes que só empacotem recursos não-gerenciados. Observe que se essas classes
mantiverem referências a outras classes, as classes contidas não sofrerão garbage collection
porque ainda foram referenciadas nas raízes ativas na fila Freachable. Portanto, quando
possível, é melhor tentar utilizar as classes FCL existentes em vez de empacotar recursos
não-gerenciados nas suas próprias classes.
Além disso, o código no método Finalize( ) não pode chamar outros objetos porque
ele é executado em uma thread muito restrita. Nenhuma alocação de memória ou coer-
ção de tipo que empacote objetos seria válida. Dentro do método Finalize( ), você pode
referenciar objetos que são membros dele próprio; não é garantido que esses objetos ain-
da não chamaram seu próprio método Finalize( ) e assim eles podem estar em um estado
inválido.
NESTE CAPÍTULO
CAPÍTULO 10 — Interfaces System.Collections

— Classes System.Collections
Coleções — Criando uma coleção
fortemente tipificada
— Criando um dicionário
fortemente tipificado
Em algum momento, todos os programadores terão de
trabalhar com listas. Os tipos de listas podem variar de
diferentes maneiras. Por exemplo, elas podem variar na
maneira como são construídas. Podem variar no que
contêm. Algumas listas contêm dados heterogêneos,
enquanto outras estão limitadas a um tipo específico.
As listas podem variar na maneira como extraímos
dados delas e no método por meio do qual iteramos por
elas. Se você for um usuário do Delphi, provavelmente
conhecerá as classes TList, TStrings, TStringList e
TCollection. Você continua a ter acesso a essas classes; no
.NET, porém, você também tem o namespace
System.Collections, que fornece interfaces e tipos
próprios. Este capítulo se aprofunda no tópico sobre
coleções e enumerators (enumeradores) para ilustrar a
maneira como o .NET trabalha com listas utilizando o
Delphi for .NET.
O namespace System.Collections contém interfaces e
classes que definem e fornecem funcionalidades nas
coleções de objetos. Essas classes são: lists, queues,
stacks, arrays, hash-tables e dictionaries.

Interfaces System.Collections
A Figura 10.1 mostra a hierarquia das interfaces
definidas dentro do namespace System.Collections.
A Tabela 10.1 fornece uma breve descrição de cada
uma dessas interfaces.
218 Capítulo 10 Coleções

FIGURA 10.1 As interfaces em System.Collections.

TABELA 10.1 Interfaces System.Collections


Interface Descrição
IEnumerable Expõe uma interface Enumerator cujo implementador itera
unidirecionalmente por uma coleção.
ICollection Define o tamanho e os métodos de sincronização para coleções. Herda os
métodos da IEnumerable.
IList Define uma coleção cujos itens são acessíveis via um índice.
IDictionary Define uma coleção de pares chave/valor.
IEnumerator Define um método para iteração unidirecional por uma coleção.
IDictionaryEnumerator Define tipo enumerado para os itens da Dictionary.
IComparer Define um método para comparar dois objetos.
IHashCodeProvider Define o método para retornar o código hash de um objeto especificado.

Cada uma dessas interfaces define seu próprio conjunto de métodos que são imple-
mentados pelas classes de coleção (collection classes). As Tabelas 10.2–10.6 descrevem os
métodos para as principais interfaces listadas na Tabela 10.1.

Interface IEnumerable
A Tabela 10.2 descreve o método da interface IEnumerable.
Interfaces System.Collections 219

TABELA 10.2 Método IEnumerable


Método Descrição
GetEnumerator( ) Retorna uma instância da IEnumerator utilizada para iterar por uma Collection.

Interface ICollection
A Tabela 10.3 descreve as propriedades e métodos da interface ICollection.

TABELA 10.3 Propriedades e métodos da ICollection


Propriedade/método Descrição
Count Retorna o número de elementos contido na Collection.
IsSynchronized Retorna true se a Collection for thread-safe (protegida para acesso por
várias threads); caso contrário retorna false.
SyncRoot Retorna um objeto que pode fornecer acesso thread-safe à Collection.
CopyTo( ) Copia os objetos contidos na Collection para o array informado iniciando
no índice especificado.

Interface IList
A Tabela 10.4 descreve as propriedades e métodos da interface IList.

TABELA 10.4 Propriedades e métodos da IList


Propriedade/método Descrição
IsFixedSize Indica se a IList tem um tamanho fixo.
IsReadOnly Indica se a IList concede acesso de leitura (read-only) aos seus elementos.
Item Retorna um elemento da IList pelo índice especificado.
Add( ) Adiciona o item especificado à IList.
Clear( ) Esvazia a IList.
Contains( ) Indica se a IList contém o valor especificado.
IndexOf( ) Retorna o índice do item especificado.
Insert( ) Insere um item na IList em uma posição especificada.
Remove( ) Remove a primeira ocorrência de um item especificado.
RemoveAt( ) Remove o item no índice especificado .

Interface IDictionary
A Tabela 10.5 descreve as propriedades e métodos da interface IDictionary.
220 Capítulo 10 Coleções

TABELA 10.5 Propriedades e métodos da interface IDictionary


Propriedade/método Descrição
IsFixedSize Determina se a IDictionary tem um tamanho fixo.
IsReadOnly Determina se os elementos da IDictionary são somente de leitura (read-only).
Item Obtém/configura o elemento com uma chave especificada.
Keys Retorna uma ICollection das chaves de IDictionary.
Values Retorna uma ICollection dos valores de IDictionary.
Add( ) Adiciona um elemento especificado com a chave especificada.
Clear( ) Esvazia a IDictionary.
Contains( ) Determina se a IDictionary contém o elemento com a chave
especificada.
Remove( ) Remove o elemento com a chave especificada .

Interface IEnumerator
A Tabela 10.6 descreve as propriedades e métodos da interface IEnumerator.

TABELA 10.6 Propriedades e métodos da interface IEnumerator


Propriedade/método Descrição
Current Retorna o elemento atual da Collection associado à instância da interface
IEnumerator.
MoveNext( ) Avança a posição do elemento atual da Collection ao qual a IEnumerator
se refere.
Reset( ) Reposiciona a IEnumerator na sua posição inicial (um pouco antes do
primeiro elemento da Collection).

As classes dentro do namespace System.Collections implementam essas interfaces.


Não são as únicas classes que as implementam. Por exemplo, a interface IList é imple-
mentada por numerosas classes UI e bancos de dados. É importante examinar como essas
classes implementam essas interfaces – você então também saberá utilizar todas as outras
classes que as implementam. Este capítulo discute as classes existentes dentro do names-
pace System.Collections.

Classes System.Collections
O namespace System.Collections inclui várias classes predefinidas que você pode utilizar
para gerenciar listas de vários tipos. As seções a seguir abrangem essas classes.

A coleção Stack
A coleção Stack armazena objetos na ordem “primeiro a entrar, último a sair” (first-
in-last-out – FILO). Para adicionar objetos, iremos “empurrá-los” na Stack. Para remo-
vê-los, iremos “estourá-los” (pop) da Stack.
Classes System.Collections 221

A Stack implementa as interfaces ICollection, IEnumerable e ICloneable.

NOTA
A interface ICloneable não é um membro do namespace System.Collections. Ela é membro dos
namespaces System e define o método Clone( ) utilizado para criar um novo objeto idêntico.

Propriedades e métodos Stack


Os principais métodos que você utilizará com a Stack são Enqueue( ), Dequeue( ) e Peek( ). A
Tabela 10.7 lista os métodos para a coleção Stack não listados nas tabelas anteriores para
as interfaces que a Stack implementa.

TABELA 10.7 Métodos da coleção Stack


Método Descrição
Peek( ) Retorna o elemento superior da Stack sem removê-lo.
Pop( ) Retorna e remove o elemento superior da Stack.
Push( ) Adiciona um elemento à Stack. Esse elemento torna-se o elemento superior.
Syncronized( ) Retorna um wrapper (invólucro) thread-safe para a Stack.
ToArray( ) Copia os elementos da Stack para uma nova classe ArrayList .

Construtor da classe Stack


Para criar uma instância de Stack, você utilizaria um de seus três construtores como mos-
trado nos exemplos a seguir.

Para criar uma Stack vazia com uma capacidade inicial padrão de 10, utilize esta construção:
MyStack := Stack.Create( );

Para criar uma Stack vazia com uma capacidade especificada, passe a capacidade desejada para o
construtor da Stack como um parâmetro:

MyStack := Stack.Create(20);

Para criar uma Stack que contém elementos de uma outra Collection cuja capacidade é a da co-
leção ou a capacidade inicial padrão (a que for maior), utilize a construção a seguir:

MyStack := Stack.Create(AnotherCollection);

onde AnotherCollection é um objeto que implementa a interface ICollection.

Utilização da classe Stack


A Listagem 10.1 ilustra a utilização da classe Stack. Também ilustra como utilizar uma
instância de um IEnumerator para iterar pela Stack de modo que seja possível imprimir o
conteúdo da Stack.
222 Capítulo 10 Coleções

LISTAGEM 10.1 Exemplo da classe Stack


1: program d4dnStack;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: System.Collections;
6:
7: procedure ShowCollection(Collection: IEnumerable);
8: var
9: enumMyStack: IEnumerator;
10: begin
11: enumMyStack := Collection.GetEnumerator;
12: System.Console.WriteLine;
13: while enumMyStack.MoveNext do
14: System.Console.Write(enumMyStack.Current.ToString+' ');
15: System.Console.WriteLine;
16: end;
17:
18: var
19: MyStack: Stack;
20: i: integer;
21: begin
22: // Inicializa e preenche a Stack
23: MyStack := Stack.Create;
24: for i := 1 to 5 do
25: MyStack.Push('Item '+i.ToString);
26: ShowCollection(MyStack);
27:
28: // Estoura o item superior
29: System.Console.Write('Pop: '+MyStack.Pop.ToString);
30: ShowCollection(MyStack);
31:
32: // Examina o item superior
33: System.Console.Write('Peek: '+MyStack.Peek.ToString);
34: ShowCollection(MyStack);
35:
36: // Estoura os dois itens superiores
37: System.Console.WriteLine('Pop: '+MyStack.Pop.ToString);
38: System.Console.Write('Pop: '+MyStack.Pop.ToString);
39: ShowCollection(MyStack);
40:
41: // Empurra um outro item
42: System.Console.Write('Push: Item 6');
43: MyStack.Push('Item 6');
44: ShowCollection(MyStack);
45:
46: // Limpa a pilha
47: MyStack.Clear;
48: System.Console.WriteLine('Stack cleared');
Classes System.Collections 223

LISTAGEM 10.1 Continuação


49:
50: // Empurra um outro item
51: System.Console.Write('Push: Item 7');
52: MyStack.Push('Item 7');
53: ShowCollection(MyStack);
54:
55: System.Console.Readline;
56: end.

u Localize o código no CD: \Code\Chapter 10\Ex01\.

O código da Listagem 10.1 é uma aplicação console simples que inicializa uma pilha,
preenche-a e realiza algumas operações Stack básicas nela. A saída desse programa é gra-
vada na tela como mostrado na Figura 10.2.

FIGURA 10.2 Saída da classe Stack.

As linhas 25–45 inicializam e preenchem a Stack com cinco itens. A procedure Show-
Collection( ) (linhas 7–16) é utilizada para imprimir o conteúdo da Stack e é invocada de-
pois de cada operação Stack. ShowCollection( ) utiliza uma instância da interface IEnumera-
tor para iterar pelo conteúdo da Stack. Observe como a IEnumerator é obtida pelo método
GetEnumerator do parâmetro IEnumerable na linha 11. Utilizando a instância IEnumerator, é
possível iterar por cada item na Stack chamando o método IEnumerator.MoveNext( ). Como
o nome informa, o método Current( ) retorna o item corrente na coleção. Observe que
ShowCollection( ) recebe uma interface IEnumerable como um parâmetro. IEnumerable é a in-
terface que define a função GetEnumerator( ). Utilizando essa interface, o método ShowCol-
lection( ) pode ser reutilizado com qualquer classe que implementa IEnumerable, como a
Queue, que veremos a seguir.
O código no bloco principal do programa contém as operações básicas na Stack que
ilustram seu uso. Examine cada operação com a saída na tela.

A classe Queue
A coleção Queue armazena objetos de acordo com uma base FIFO. Para adicionar objetos, rea-
lizamos uma operação Enqueue( ). Para removê-los, realizamos uma operação Dequeue( ).
224 Capítulo 10 Coleções

Semelhante à classe Stack, a classe Queue implementa as interfaces ICollection, IEnume-


rable e ICloneable.

Propriedades e métodos da classe Queue


Os principais métodos que você utilizará com a Queue são Enqueue( ), Dequeue( ) e Peek( ). A
Tabela 10.8 lista os métodos da coleção Queue.

TABELA 10.8 Métodos da coleção Queue


Método Descrição
Enqueue( ) Adiciona um elemento à Queue. Quando adicionado, esse é o último elemento a
ser removido da Queue, a menos que outros sejam adicionados antes dele.
Dequeue( ) Remove e retorna o primeiro elemento no começo da Queue.
Peek( ) Retorna o elemento no começo da Queue. Essa operação não remove o
elemento da Queue.
Syncronize( ) Retorna um invólucro thread-safe para a Queue.
ToArray( ) Copia os elementos da Queue para uma nova classe ArrayList.
TrimToSize( ) Configura a capacidade da Queue como o número de elementos que ela
contém.

O construtor da classe Queue


Ao inicializar uma classe Queue, você tem a opção de especificar a capacidade inicial e o fa-
tor de crescimento da Queue. A capacidade inicial é simplesmente o tamanho inicial da
fila. O fator de crescimento é o valor pelo qual a capacidade inicial é multiplicada se uma
expansão for necessária na Queue. Portanto, uma Queue com uma capacidade inicial de 10 e
um fator de crescimento de 2 dobrará de tamanho quando necessário.
Para criar uma instância de Queue, você utilizaria um de seus quatro construtores
como mostrado nos exemplos a seguir.

Para criar uma Queue vazia com uma capacidade inicial padrão de 32 e um fator de crescimento
padrão de 2, utilize a seguinte construção:
MyQueue := Queue.Create( );

Para criar uma Queue vazia com uma capacidade especificada e o fator padrão de crescimento de
2, passe a capacidade inicial para o construtor da Queue como um parâmetro:

MyQueue := Queue.Create(20);

Para criar uma Queue vazia com uma capacidade especificada e o fator de crescimento especifica-
do, passe tanto a capacidade inicial como o fator de crescimento para o construtor da Queue como
parâmetros:
MyQueue := Queue.Create(20, 3);
Classes System.Collections 225

Para criar uma Queue que contém elementos de uma outra Collection cuja capacidade é a da co-
leção ou a capacidade inicial padrão (a que for maior) e o fator de crescimento padrão, utilize esta
construção:
MyQueue := Queue.Create(AnotherCollection);

onde AnotherCollection é um objeto que implementa a interface ICollection.

Utilização de Queue
A Listagem 10.2 ilustra a utilização do objeto Queue. Semelhante à Stack, a Queue também
utiliza uma IEnumerator para iterar pelos seus elementos.

LISTAGEM 10.2 Exemplo de Queue


1: program d4dnQueue;
2: {$APPTYPE CONSOLE}
3:
4: {%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\
➥v1.1.4322\system.drawing.dll'}
5:
6: uses
7: System.Collections;
8:
9: procedure ShowCollection(Collection: IEnumerable);
10: var
11: enumMyStack: IEnumerator;
12: begin
13: enumMyStack := Collection.GetEnumerator;
14: System.Console.WriteLine;
15: while enumMyStack.MoveNext do
16: System.Console.Write(enumMyStack.Current.ToString+' ');
17: System.Console.WriteLine;
18: end;
19:
20: var
21: MyQueue: Queue;
22: i: integer;
23: begin
24: // Inicializa e enfileira
25: MyQueue := Queue.Create( );
26: for i := 1 to 5 do
27: MyQueue.Enqueue('Item '+i.ToString( ));
28: ShowCollection(MyQueue);
29:
30: System.Console.Write('Dequeue: '+MyQueue.Dequeue.ToString);
31: ShowCollection(MyQueue);
32:
33: System.Console.Write('Peek: '+MyQueue.Peek.ToString);
226 Capítulo 10 Coleções

LISTAGEM 10.2 Continuação


34: ShowCollection(MyQueue);
35:
36: System.Console.WriteLine('Dequeue: '+MyQueue.Dequeue.ToString);
37: System.Console.Write('Dequeue: '+MyQueue.Dequeue.ToString);
38: ShowCollection(MyQueue);
39:
40: System.Console.Write('Enqueue: Item 6');
41: MyQueue.Enqueue('Item 6');
42: ShowCollection(MyQueue);
43:
44: MyQueue.Clear( );
45: System.Console.WriteLine('Queue cleared');
46:
47: System.Console.Write('Enqueue: Item 7');
48: MyQueue.Enqueue('Item 7');
49: ShowCollection(MyQueue);
50: System.Console.ReadLine;
51:
52: end.

u Localize o código no CD: \Code\Chapter 10\Ex02\.

O código da Listagem 10.2 é semelhante ao da Listagem 10.1 para a Stack. A utiliza-


ção desses dois objetos é bem semelhante. Eles diferem na ordem em que os itens são
removidos da coleção. A saída desse programa é gravada na tela como mostrado na
Figura 10.3.

FIGURA 10.3 Saída de Queue.

Na listagem, as linhas 25–27 inicializam a Queue com dados. O restante do bloco


principal realiza então algumas operações Dequeue( ) e Enqueue( ) contra ela. Observe a
diferença na maneira como os dados são removidos da Queue durante a operação Dequeue( ).
Diferentemente da Stack, o item removido sempre é o item no começo da coleção (o
primeiro item adicionado), enquanto na Stack é sempre o último item adicionado.
Classes System.Collections 227

A classe ArrayList
ArrayList é provavelmente a classe mais versátil do namespace System.Collections. As classes
Stack e Queue permitem modificar os objetos que elas contêm, não a referência real, mas os
campos e as propriedades dos objetos. ArrayList permite na verdade sobrescrever os ele-
mentos existentes com novas referências. Essa é uma pequena mas importante distinção.
Ela também tem suporte para outras operações úteis que permitem classificar, inverter, or-
denar, fazer operações de intervalo, localização de elementos e pesquisa binária.
A ArrayList implementa as interfaces IList, ICollection, IEnumerable e Icloneable.

Construtor de ArrayList
Para criar uma instância de ArrayList, você utilizaria um de seus três construtores como
mostrado nos exemplos a seguir.

Para criar uma ArrayList vazia com uma capacidade inicial padrão de 16, utilize esta construção:

MyArrayList := ArrayList.Create( );

Quando a ArrayList precisar ser expandida, sua capacidade será o dobro da capacidade inicializada.

Para criar uma ArrayList vazia com uma capacidade especificada, passe a capacidade desejada
para o construtor da ArrayList como um parâmetro:

MyArrayList := ArrayList.Create(20);

Para criar uma ArrayList que contém elementos de uma outra Collection cuja capacidade é a da
coleção ou a capacidade inicial padrão (a que for a maior), utilize a construção a seguir:
MyArrayList := ArrayList.Create(AnotherCollection);

onde AnotherCollection é um objeto que implementa a interface ICollection.

Propriedades e métodos da ArrayList


A Tabela 10.9 lista as propriedades e métodos da coleção ArrayList que não estão listados
nas suas interfaces.

TABELA 10.9 Propriedades e métodos da coleção ArrayList


Propriedade/método Descrição
Capacity O número de itens que a ArrayList pode conter antes de crescer.
Adapter( ) Cria um adaptador ArrayList ou invólucro, para uma IList.
AddRange( ) Adiciona elementos de uma ICollection ao final de uma ArrayList.
BinarySearch( ) Realiza uma pesquisa binária para um elemento especificado em uma
ArrayList classificada.
FixedSize( ) Retorna um invólucro de lista com um tamanho fixo. Os elementos dessa
lista podem ser modificados, mas não adicionados ou removidos.
GetEnumerator( ) Retorna uma instância IEnumerator utilizada para iterar pela ArrayList.
228 Capítulo 10 Coleções

TABELA 10.9 Continuação


Propriedade/método Descrição
GetRange( ) Retorna um intervalo (subconjunto da ArrayList) que, por si só, é uma
ArrayList.
InsertRange( ) Insere elementos a partir de uma ICollection no índice especificado.
LastIndexOf( ) Retorna a última ocorrência de um elemento dentro de uma ArrayList
ou uma parte da ArrayList.
ReadOnly( ) Retorna um invólucro de lista de leitura.
RemoveRange( ) Remove o subconjunto especificado de elementos da ArrayList.
Repeat( ) Retorna uma cópia da ArrayList. Essa não é uma cópia superficial.
Portanto, os elementos são cópias dos elementos originais. Ver o método
Clone( ).
Reverse( ) Inverte a ordem de todos ou de um intervalo dos itens da ArrayList.
SetRange( ) Copia uma ICollection para um intervalo especificado da ArrayList.
Sort( ) Classifica a ArrayList.
Syncronized( ) Retorna um invólucro thread-safe para a ArrayList.
ToArray( ) Copia os elementos da ArrayList para uma outra ArrayList.
TrimToSize( ) Configura Capacity da ArrayList igual a Count (itens contidos na
ArrayList).

Utilização de ArrayList
A Listagem 10.3 ilustra a utilização do objeto ArrayList. Esse exemplo ilustra como preen-
cher, enumerar, classificar e trabalhar com intervalos em uma ArrayList.

LISTAGEM 10.3 Exemplo de ArrayList


1: program d4dnArrayList;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: System.Collections;
6:
7: var
8: ClassicCars: ArrayList;
9: SubList: ArrayList;
10: SubList2: ArrayList;
11:
12: procedure ShowArrayListItems(aName: String; aArrayList: ArrayList);
13: var
14: i: integer;
15: begin
16: System.Console.WriteLine;
17: System.Console.Write(aName+': ');
18: for i := 0 to aArrayList.Count - 1 do
19: System.Console.Write(' {0}', [aArrayList[i].ToString]);
20: System.Console.WriteLine;
Classes System.Collections 229

LISTAGEM 10.3 Continuação


21: end;
22:
23: begin
24: // Inicializa e preenche a arraylist
25: ClassicCars := ArrayList.Create;
26: ClassicCars.Add('Camaro');
27: ClassicCars.Add('Mustang');
28: ClassicCars.Add('Chevelle');
29: ClassicCars.Add('Nova');
30: ClassicCars.Add('Corvette');
31: ClassicCars.Add('Thunderbird');
32: ClassicCars.Add('Firebird');
33: ClassicCars.Add('Satellite');
34:
35: ShowArrayListItems('ClassicCars', ClassicCars);
36:
37: // classifica a arraylist
38: ClassicCars.Sort;
39: ShowArrayListItems('ClassicCars', ClassicCars);
40:
41: // inverte a ArrayList
42: ClassicCars.Reverse;
43: ShowArrayListItems('ClassicCars', ClassicCars);
44:
45: // obtém um intervalo de itens, mas deixa-os na ArrayList original
46: SubList := ClassicCars.GetRange(2,3);
47: ShowArrayListItems('SubList', SubList);
48: ShowArrayListItems('ClassicCars', ClassicCars);
49:
50: // cria uma nova arraylist e a adiciona a ClassicCars
51: SubList2 := ArrayList.Create(3);
52: SubList2.Add('GTO');
53: SubList2.Add('Malibu');
54: SubList2.Add('El-Camino');
55: ClassicCars.AddRange(SubList2);
56: ShowArrayListItems('ClassicCars', ClassicCars);
57:
58: // reclassifica a arraylist
59: ClassicCars.Sort;
60: ShowArrayListItems('ClassicCars', ClassicCars);
61:
62: // Gera saída da contagem e capacidade
63: System.Console.WriteLine('ClassicCars count: {0}',
64: [ClassicCars.Count.ToString]);
65: System.Console.WriteLine('ClassicCars capacity: {0}',
66: [ClassicCars.Capacity.ToString]);
67:
68: // Apara a fim de dimensionar
230 Capítulo 10 Coleções

LISTAGEM 10.3 Continuação


69: ClassicCars.TrimToSize;
70: // Gera saída da contagem e capacidade
71: System.Console.WriteLine('After trimmed – -');
72: System.Console.WriteLine('ClassicCars count: {0}',
73: [ClassicCars.Count.ToString]);
74: System.Console.WriteLine('ClassicCars capacity: {0}',
75: ClassicCars.Capacity.ToString);
76: System.Console.ReadLine;
77:
78: end.

u Localize o código no CD: \Code\Chapter 10\Ex03\.

Na listagem, as linhas 8–10 declaram três objetos ArrayList – alClassicCars e dois arrays
adicionais para demonstrar o uso das operações de intervalo. ShowArrayListItems( ) é a pro-
cedure que imprime a saída no console. A Figura 10.4 mostra a saída desse programa.

FIGURA 10.4 Saída de ArrayList.

As linhas 25–33 criam e preenchem alClassicCars. As linhas 42–43 demonstram


como classificar e inverter a ordem da ArrayList, que você pode ver na saída de console.
Na linha 46, o programa obtém um subconjunto da ArrayList em alSubList. alSubList con-
terá três itens de alClassicCars começando no índice dois. Como isso é um array baseado
em zero, esse subconjunto iniciará no terceiro item da lista.
As linhas 51–55 criam uma outra ArrayList, alSubList2, e a preenche com três itens
não contidos em alClassicCars. Esses itens são adicionados à lista principal, alClassicCars,
utilizando o método AddRange( ). Nas linhas 63 e 66, Count e Capacity de alClassicCars estão
gravados. Essas propriedades exibem o número de itens que a coleção contém e o espaço
alocado, respectivamente. A linha 69 invoca o método TrimToSize( ) para configurar Capa-
city igual a Count.

A classe HashTable
A classe HashTable é uma que você utilizaria quando quer obter itens armazenados pelo
valor de uma chave. A HashTable converte internamente a chave em um código de hash,
Classes System.Collections 231

que é um índice numérico único que a HashTable, em última instância, utiliza para obter o
elemento solicitado. Os elementos são armazenados em buckets. Um bucket é um sub-
grupo de itens armazenados dentro da coleção. Os itens cujas chaves geram o mesmo có-
digo de hash são armazenados no mesmo bucket. Portanto, a HashTable utiliza o código de
hash para identificar o bucket, e então procura o item solicitado. Idealmente, só haveria
um item em cada bucket, tornando assim a HashTable muito eficaz para fazer uma pesqui-
sa com um tempo de pesquisa de O(1).
A HashTable implementa as interfaces IDictionary, ICollection, IEnumerable, ISerializa-
ble e ICloneable. Em vez de ser definida no namespace System.Collections, a interface
ISerializable é definida no namespace System.Runtime.Serialization. ISerializable define a
interface para serializar e desserializar classes.

Construtor de HashTable
A HashTable pode ser construída de uma entre várias maneiras. Os construtores alternati-
vos permitem especificar ou utilizar a capacidade padrão, fator de carga, provedor de có-
digo hash e comparador (comparer). Além disso, você pode especificar uma outra instân-
cia de IDictionary a partir da qual é possível copiar elementos e objetos de serializa-
ção/streaming para personalização de streaming. Os parâmetros específicos são

capacity as Integer – Determina o número ou buckets a ser alocado com base no fator de carga.

loadFactor as Single – Especifica a relação máxima de elementos para buckets. O padrão é 1.0.

hcp as IHashCodeProvider – Gera códigos de hash para chaves.

comparer as IComparer – Compara e determina se duas chaves são iguais.

d as IDictionary – Elementos do parâmetro IDictionary são copiados para a HashTable.

info as SerializationInfo – Objeto que contém informações necessárias para serializar a HashTable.

context as StreamingContext – Descreve a origem e o destino do stream serializado para a HashTable.

Propriedades e métodos da coleção HashTable


A Tabela 10.10 lista as propriedades e métodos para a coleção HashTable que não estão lis-
tados nas suas interfaces.

TABELA 10.10 Propriedades e métodos da coleção HashTable


Propriedade/método Descrição
ContainsKey( ) Determina se a HashTable contém a chave especificada.
ContainsValue( ) Determina se a HashTable contém o valor especificado.
GetObjectData( ) Retorna os dados exigidos para serializar a HashTable implementando
Iserializable.
Syncronized( ) Retorna um invólucro thread-safe para a HashTable.
OnDeserialization( ) Levanta um evento de desserialização quando a desserialização está
completa. Implementa a interface ISerializable.
232 Capítulo 10 Coleções

Utilização de HashTable
A Listagem 10.4 ilustra a utilização do objeto HashTable. Embora os exemplos anteriores
armazenem strings, este exemplo ilustra como preencher um HashTable com uma classe
personalizada contendo informações sobre os estados norte-americanos.

LISTAGEM 10.4 Exemplo de HashTable


1: program d4dnHashTable;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: System.Collections,
6: Borland.Vcl.SysUtils;
7:
8: type
9:
10: // Define uma classe a armazenar na HashTable
11: TStateInfo = class
12: StateName: String;
13: StateCapital: String;
14: Admission: TDateTime;
15: constructor Create(aName, aCapital: String; aAddmision: TDateTime);
16: end;
17:
18: var
19: htDemo: HashTable;
20:
21: procedure PrintStateInfo(aKey: String);
22: var
23: si: TStateInfo;
24: begin
25: // Primeiro obtém o item TStateInfo da HashTable
26: si := htDemo[aKey] as TStateInfo;
27: // Imprime seu conteúdo
28: Console.WriteLine(Format('State: %s, Capital: %s, Admission: %s',
29: [si.StateName, si.StateCapital, DateTostr(si.Admission)]));
30: end;
31:
32: { TStateInfo }
33: constructor TStateInfo.Create(aName, aCapital: String;
34: aAddmision: TDateTime);
35: begin
36: inherited Create;
37: StateName := aName;
38: StateCapital := aCapital;
39: Admission := aAddmision;
40: end;
41:
42: begin
Classes System.Collections 233

LISTAGEM 10.4 Continuação


43: // Inicializa e preenche a HashTable
44: htDemo := HashTable.Create( );
45: htDemo.Add('AL', TStateInfo.Create('Alabama', 'Montgomery',
46: EncodeDate(1819, 12, 14)));
47: htDemo.Add('FL', TStateInfo.Create('Florida', 'Tallahassee',
48: EncodeDate(1845, 03, 15)));
49: htDemo.Add('KY', TStateInfo.Create('Kentucky', 'Frankfort',
50: EncodeDate(1792, 06, 01)));
51: htDemo.Add('IL', TStateInfo.Create('Illinois', 'SpringField',
52: EncodeDate(1818, 12, 03)));
53: htDemo.Add('ME', TStateInfo.Create('Maine', 'Augusta',
54: EncodeDate(1820, 03, 15)));
55:
56: // Imprime os itens da HashTable pela Chave
57: PrintStateInfo('ME');
58: PrintStateInfo('KY');
59: PrintStateInfo('FL');
60: PrintStateInfo('IL');
61: PrintStateInfo('AL');
62: System.Console.ReadLine;
63:
64: end.

u Localize o código no CD: \Code\Chapter 10\Ex04\.

A Listagem 10.4 ilustra como armazenar um objeto (TStateInfo) e como obter esse
objeto utilizando uma chave de string. As linhas 11–16 contêm a definição da classe
TStateInfo, que define campos de informações básicos para estados norte-americanos.
As linhas 44–54 inicializam e preenchem a HashTable com dados. A procedure PrintSta-
teInfo( ) aceita um string como um parâmetro e utiliza o parâmetro como uma chave
para extrair uma instância TStateInfo da HashTable. Na obtenção da instância TStateIn-
fo, PrintStateInfo( ) grava os dados desse estado na tela como mostrado na Figura
10.5.

FIGURA 10.5 Saída da classe HashTable.


234 Capítulo 10 Coleções

Criando uma coleção fortemente tipificada


As coleções que você viu neste capítulo são genéricas pelo fato de poderem armazenar
instâncias heterogêneas de qualquer tipo de objeto ou tipo por valor empacotado (bo-
xed). É mais provável que você precise criar uma coleção que só armazene dados de um
tipo. Isso é chamado uma coleção fortemente tipificada.

Descendendo da CollectionBase
O namespace System.Collections contém uma classe abstrata, CollectionBase, da qual sua
coleção personalizada poderia descender a fim de criar coleções fortemente tipificadas.
Internamente, a CollectionBase utiliza uma instância ArrayList para armazenar elementos.
Os acesso à ArrayList ocorre por meio de uma interface IList fornecida pela propriedade
List. CollectionBase também define os métodos que precisam ser sobrescritos e impõe o
tipo que você quer armazenar. A Listagem 10.5 mostra uma coleção fortemente tipifica-
da que armazena as informações de estado no exemplo anterior.

LISTAGEM 10.5 Coleção fortemente tipificada


1: unit d4dnDevGuide.StateCollection;
2: interface
3: uses
4: System.Collections;
5:
6: type
7:
8: // Define uma classe para armazenar na Collection
9: TStateInfo = class
10: StateName: String;
11: StateCapital: String;
12: Admission: DateTime;
13: constructor Create(aName, aCapital: String; aAddmision: DateTime);
14: end;
15:
16: // Define a coleção fortemente tipificada
17: TStateInfoCollection = class(CollectionBase)
18: private
19: function GetStateInfo(Index: Integer): TStateInfo;
20: procedure SetStateInfo(Index: Integer; const Value: TStateInfo);
21: procedure VerifyType(Value: TObject);
22: strict protected
23: // Eventos de verificação de tipos
24: procedure OnInsert(index: integer; value: TObject); override;
25: procedure OnSet(index: integer; oldValue: TObject;
26: newValue: TObject); override;
27: procedure OnValidate(value: TObject); override;
28: public
29: constructor Create;
Criando uma coleção fortemente tipificada 235

LISTAGEM 10.5 Continuação


30: function Add(value: TStateInfo): Integer;
31: function IndexOf(value: TStateInfo): Integer;
32: procedure Insert(index: integer; value: TStateInfo);
33: procedure Remove(value: TStateInfo);
34: function Contains(value: TStateInfo): Boolean;
35: procedure PrintItems;
36: property StateInfo[Index: Longint]: TStateInfo read GetStateInfo
37: write SetStateInfo;
38: end;
39:
40: implementation
41:
42: { TStateInfo }
43: constructor TStateInfo.Create(AName, ACapital: String;
44: AAddmision: DateTime);
45: begin
46: inherited Create( );
47: StateName := AName;
48: StateCapital := ACapital;
49: Admission := AAddmision;
50: end;
51:
52: { TStateInfoCollection }
53:
54: constructor TStateInfoCollection.Create;
55: begin
56: inherited Create;
57: end;
58:
59: function TStateInfoCollection.GetStateInfo(Index: Integer): TStateInfo;
60: begin
61: Result := List[Index] as TStateInfo;
62: end;
63:
64: procedure TStateInfoCollection.SetStateInfo(Index: Integer;
65: const Value: TStateInfo);
66: begin
67: List[Index] := Value;
68: end;
69:
70: procedure TStateInfoCollection.OnInsert(Index: integer; Value: TObject);
71: begin
72: VerifyType(Value);
73: end;
74:
75: procedure TStateInfoCollection.OnSet(Index: integer; OldValue:
76: TObject; NewValue: TObject);
77: begin
236 Capítulo 10 Coleções

LISTAGEM 10.5 Continuação


78: VerifyType(NewValue);
79: end;
80:
81: procedure TStateInfoCollection.OnValidate(Value: TObject);
82: begin
83: VerifyType(Value);
84: end;
85:
86: function TStateInfoCollection.Add(Value: TStateInfo): Integer;
87: begin
88: Result := List.Add(Value);
89: end;
90:
91: function TStateInfoCollection.IndexOf(Value: TStateInfo): Integer;
92: begin
93: Result := List.IndexOf(Value);
94: end;
95:
96: procedure TStateInfoCollection.Insert(Index: integer; Value: TStateInfo);
97: begin
98: List.Insert(Index, Value);
99: end;
100:
101: procedure TStateInfoCollection.Remove(Value: TStateInfo);
102: begin
103: List.Remove(Value);
104: end;
105:
106: function TStateInfoCollection.Contains(Value: TStateInfo): Boolean;
107: begin
108: result := List.Contains(Value);
109: end;
110:
111: procedure TStateInfoCollection.PrintItems;
112: const
113: fmt = 'State: {0}, Capital: {1}, Admission: {2}';
114: var
115: i: Integer;
116: si: TStateInfo;
117: begin
118: for i := 0 to Count -1 do
119: begin
120: si := TStateInfo(List[i]);
121: System.Console.WriteLine(System.String.Format(fmt, [si.StateName,
122: si.StateCapital, si.Admission.ToShortDateString( )]));
123: end;
124: end;
125:
Criando uma coleção fortemente tipificada 237

LISTAGEM 10.5 Continuação


126: procedure TStateInfoCollection.VerifyType(Value: TObject);
127: begin
128: if not (Value is TStateInfo) then
129: raise ArgumentException.Create('Invalid Type');
130: end;
131:
132: end.

u Localize o código no CD: \Code\Chapter 10\Ex05\.

Essa é uma unit que será utilizada em um projeto a ser discutido em breve. Essa unit
define dois tipos: a classe TStateInfo (linhas 9–14) e a classe TStateInfoCollection (linhas
19–40). Como os nomes indicam, TStateInfoCollection é a coleção fortemente tipificada
que armazenará instâncias TstateInfo.
Os passos necessários para implementar essa coleção são
1. Crie uma propriedade indexada para obter itens na Collection.

2. Sobrescreva eventos de verificação de tipos OnInsert, OnSet e OnValidate e adicione


código a fim de levantar uma exceção dado um tipo inválido (um tipo que não é
uma classe TStateInfo ).
3. Implemente métodos para trabalhar com a coleção, utilizando os mesmos nomes
padrão que IList e ArrayList utilizam.

A propriedade StateInfo (linhas 36–37) é a propriedade indexada para TStateInfoCol-


lection. Seus métodos getter e setter, GetStateInfo( ) e SetStateInfo( ), utilizam a lista in-
terna para obter e armazenar elementos.
Ao examinar o código, observe que TStateInfoCollection expõe basicamente os méto-
dos que internamente realizam funções equivalentes contra a instância de IList interna.
Por exemplo, o método TStateInfoCollection.Contains( ) simplesmente invoca o mesmo
método em List:

108: result := List.Contains(value);

A maioria dos outros métodos realiza uma função semelhante.


Observe que os três eventos de verificação de tipos determinam se o item sendo ma-
nipulado é do tipo TStateInfo passando-o para a procedure VerifyType( ). Quando um tipo
diferente é utilizado, esses eventos levantam uma exceção apropriada.
Por fim, a função PrintItems( ) (linhas 111–124) simplesmente faz um loop pela co-
leção e grava sua saída no console.

Utilizando coleção fortemente tipificada


A Listagem 10.6 ilustra uma aplicação Delphi que utiliza essa coleção. Observe que não é
diferente do que utilizar qualquer uma das outras coleções discutidas neste capítulo.
238 Capítulo 10 Coleções

LISTAGEM 10.6 Programa Delphi for .NET utilizando TStateInfoCollection


1: program d4dnStateCollDemo;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: d4dnDevGuide.StateCollection;
6:
7: var
8: sicDemo: TStateInfoCollection;
9:
10: begin
11: sicDemo := TStateInfoCollection.Create( );
12: sicDemo.Add(TStateInfo.Create('Alabama', 'Montgomery',
13: DateTime.Create(1819, 12, 14)));
14: sicDemo.Add(TStateInfo.Create('Florida', 'Tallahassee',
15: DateTime.Create(1845, 03, 15)));
16: sicDemo.PrintItems( );
17: System.Console.ReadLine;
18: end.

u Localize o código no CD: \Code\Chapter 10\Ex05\.

Criando um dicionário fortemente tipificado


Como ocorre com Collection, provavelmente você precise de uma classe Dictionary forte-
mente tipificada. No exemplo de HashTable apresentado neste capítulo, os dados armaze-
nados poderiam ser qualquer tipo que descende de System.Object. A seção a seguir ilustra
como criar uma Dictionary que impõe o tipo TStateInfo.

Descendendo da DictionaryBase
O namespace System.Collections define a classe DictionaryBase. Nessa classe, você deve de-
rivar suas classes Dictionary fortemente tipificadas. A Listagem 10.7 mostra essa classe.

LISTAGEM 10.7 Classe Dictionary fortemente tipificada


1: unit d4dnDevGuide.StateDictionary;
2: interface
3:
4: uses
5: System.Collections;
6:
7: type
8:
9: // Define uma classe a armazenar no dicionário
10: TStateInfo = class
11: StateName: String;
Criando um dicionário fortemente tipificado 239

LISTAGEM 10.7 Continuação


12: StateCapital: String;
13: Admission: DateTime;
14: constructor Create(AName, ACapital: String; AAddmision: DateTime);
15: end;
16:
17: // Define o dicionário fortemente tipificado
18: TStateInfoDictionary = class(DictionaryBase)
19: private
20: function GetStateInfo(key: String): TStateInfo;
21: procedure SetStateInfo(key: String; const Value: TStateInfo);
22: function GetKeys: ICollection;
23: function GetValues: ICollection;
24: procedure VerifyType(Value: TObject);
25: strict protected
26: // Eventos de verificação de tipos
27: procedure OnInsert(key: TObject; value: TObject); override;
28: procedure OnSet(Key: TObject; OldValue: TObject;
29: NewValue: TObject); override;
30: procedure OnValidate(key: TObject; value: TObject); override;
31: public
32: constructor Create;
33: procedure Add(key: String; value: TStateInfo);
34: procedure Remove(key: String);
35: function Contains(key: String): Boolean;
36:
37: procedure PrintItems;
38:
39: property StateInfo[key: String]: TStateInfo read GetStateInfo
40: write SetStateInfo;
41: property Keys: ICollection read GetKeys;
42: property Values: ICollection read GetValues;
43: end;
44:
45: implementation
46:
47: { TStateInfo }
48: constructor TStateInfo.Create(AName, ACapital: String;
49: AAddmision: DateTime);
50: begin
51: inherited Create( );
52: StateName := AName;
53: StateCapital := ACapital;
54: Admission := AAddmision;
55: end;
56:
57: { TStateInfoDictionary }
58:
59: constructor TStateInfoDictionary.Create;
240 Capítulo 10 Coleções

LISTAGEM 10.7 Continuação


60: begin
61: inherited Create;
62: end;
63:
64: procedure TStateInfoDictionary.OnInsert(key: TObject; value: TObject);
65: begin
66: VerifyType(Value);
67: end;
68:
69: procedure TStateInfoDictionary.OnSet(Key: TObject; OldValue:
70: TObject; NewValue: TObject);
71: begin
72: VerifyType(NewValue);
73: end;
74:
75: procedure TStateInfoDictionary.OnValidate(key: TObject; Value: TObject);
76: begin
77: VerifyType(Value);
78: end;
79:
80: procedure TStateInfoDictionary.Add(key: String; value: TStateInfo);
81: begin
82: Dictionary.Add(key, value);
83: end;
84:
85: procedure TStateInfoDictionary.Remove(key: String);
86: begin
87: Dictionary.Remove(key);
88: end;
89:
90: function TStateInfoDictionary.Contains(key: String): Boolean;
91: begin
92: result := Dictionary.Contains(key);
93: end;
94:
95: procedure TStateInfoDictionary.PrintItems;
96: const
97: fmt = 'State: {0}, Capital: {1}, Admission: {2}';
98: var
99: si: TStateInfo;
100: enum: IDictionaryEnumerator;
101: begin
102: enum := GetEnumerator;
103: while enum.MoveNext( ) do
104: begin
105: si := TStateInfo(enum.Value);
106: writeln(System.String.Format(fmt, [si.StateName, si.StateCapital,
107: si.Admission.ToShortDateString( )]));
Criando um dicionário fortemente tipificado 241

LISTAGEM 10.7 Continuação


108: end;
109: end;
110:
111: function TStateInfoDictionary.GetStateInfo(key: String): TStateInfo;
112: begin
113: Result := Dictionary[key] as TStateInfo;
114: end;
115:
116: procedure TStateInfoDictionary.SetStateInfo(key: String;
117: const Value: TStateInfo);
118: begin
119: Dictionary[key] := Value;
120: end;
121:
122: function TStateInfoDictionary.GetKeys: ICollection;
123: begin
124: Result := Dictionary.Keys;
125: end;
126:
127: function TStateInfoDictionary.GetValues: ICollection;
128: begin
129: Result := Dictionary.Values;
130: end;
131:
132: procedure TStateInfoDictionary.VerifyType(Value: TObject);
133: begin
134: if not (Value is TStateInfo) then
135: raise ArgumentException.Create('Invalid Type');
136: end;
137:
138: end.

u Localize o código no CD: \Code\Chapter 10\Ex06\.

Você perceberá que o código contido aqui é muito semelhante ao da Listagem 10.5.
Portanto, não iremos rever isso, exceto para dizer que essa implementação utiliza inter-
namente uma classe Dictionary em vez de uma List como ocorre com a coleção fortemen-
te tipificada. Observe que os mesmos eventos de validação e métodos adicionais são im-
plementados aqui e diferem um pouco nos seus parâmetros porque trabalha com um
Dictionary.

Utilizando o dicionário fortemente tipificado


A Listagem 10.8 mostra um programa Delphi que utiliza Dictionary fortemente tipificada
definida anteriormente.
242 Capítulo 10 Coleções

LISTAGEM 10.8 Programa Delphi for .NET utilizando TStateInfoDictionary


1: program d4dnStateDicDemo;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: d4dnDevGuide.StateDictionary in 'd4dnDevGuide.StateDictionary.pas';
6:
7: var
8: sicDemo: TStateInfoDictionary;
9:
10: begin
11: sicDemo := TStateInfoDictionary.Create( );
12: sicDemo.Add('AL', TStateInfo.Create('Alabama', 'Montgomery',
13: DateTime.Create(1819, 12, 14)));
14: sicDemo.Add('FL', TStateInfo.Create('Florida', 'Tallahassee',
15: DateTime.Create(1845, 03, 15)));
16: sicDemo.PrintItems( );
17 Console.ReadLine( );
18: end.

u Localize o código no CD: \Code\Chapter 10\Ex06\.

Mais uma vez, esse código é praticamente idêntico ao utilizado na classe TStateCol-
lection e portanto não será elaborado.

NOTA
Não é necessário derivar de CollectionBase ou DictionaryBase para criar descendentes forte-
mente tipificados. Você poderia criar seus próprios objetos que implementam as interfaces ICol-
lection, IEnumerable, IList e Idictionary. Entretanto, essas duas classes já fazem isso e, portan-
to, lhe poupam o esforço.
NESTE CAPÍTULO
CAPÍTULO 11 — O tipo System.String

— A classe StringBuilder
Trabalhando com as — Formatação de string

classes String e — Especificadores de formato

StringBuilder

O Capítulo 5 lida com a linguagem Delphi do ponto


de vista de tipos de dados do .NET. Tipos String, embora
sejam um tipo, têm algumas características especiais
como formatação, imutabilidade, como operar com
eles e outras. Ao desenvolver aplicações .NET, Strings
são provavelmente um dos tipos com os quais você
mais conta. Essas características únicas da String
são discutidas nas seções neste capítulo.
Este capítulo discute as classes System.String e
System.Text.StringBuilder. Ele também abrange
a formatação de texto.

O tipo System.String
É suficiente dizer que o tipo System.String é
provavelmente o tipo de dados mais amplamente
utilizado. Na linguagem Delphi, o tipo String mapeia
diretamente para WideString. Uma WideString é uma
string Unicode que significa que cada caractere tem dois
bytes de largura. De fato, o tipo de dados WideString é
um alias para o tipo System.String no .NET. Esse
mapeamento do tipo Delphi String/WideString para um
tipo de dados System.String do .NET é um detalhe de
implementação. O tipo String do Delphi no nível da
linguagem não se comporta de maneira idêntica a
System.String. O comportamento (ou implementação) é
o mesmo somente se você converter um tipo de dados
String do Delphi em um System.String. Uma dessas
diferenças é que strings do Delphi inicialmente são
vazias ('') com um comprimento zero. Tipos
System.String inicialmente referenciam nil.
244 Capítulo 11 Trabalhando com as classes String e StringBuilder

ATENÇÃO
Ao converter um String do Delphi em um System.String, você deve ter cuidado para tratar o
caso em que o System.String será nil (quando o String do Delphi estiver vazio).

Para o propósito da discussão neste capítulo, ao referenciarmos Strings, estaremos


falando sobre o tipo de dados System.String.

ATENÇÃO
O CLR define um System.String como indexado por zero. Entretanto, o seguinte revela que o
Delphi faz algum tipo de magia a fim de que o System.String comporte-se como um String do
Delphi:

var
ds: String;
ss: System.String;
begin
ds := 'ABC';
ss := 'ABS';
Console.WriteLine(ds[1]);
Console.WriteLine(ss[1]);
end;
As duas instruções WriteLine( ) geram a saída do caractere "A". Você iria esperar que, em vez dis-
so, a segunda WriteLine( ) escrevesse "A". Isso não é um defeito, mas mais por razões de projeto
– provavelmente para manter a retrocompatibilidade com strings do Delphi. Você ainda pode usar
um índice começado em zero com um System.String utilizando o indexador padrão

Console.WriteLine(SS.Chars[1]);
que gera a saída "B" como esperado.

Imutabilidade da string no .NET


O que significa exatamente strings imutáveis? Significa que após instanciar um tipo String,
você não pode alterar seu valor. Isso não quer dizer que o código a seguir irá falhar:

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);


var
s: System.String;
begin
s := 'Classic Cars';
s := s.ToUpper;
s := s.ToLower;
end;

Significa que o código anterior exige duas instâncias de string (ou alocações de me-
mória) mesmo que ambas sejam referidas pela mesma variável. Considere o código gera-
do em IL para as atribuições das strings anteriores:
O tipo System.String 245

.maxstack 1
.locals (String V_0)
L_0000: ldstr "Classic Cars"
L_0005: stloc.0
L_0006: ldloc.0
L_0007: call String.ToUpper
L_000c: stloc.0
L_000d: ldloc.0
L_000e: call String.ToLower
L_0013: stloc.0
L_0014: ret

Particularmente, examine as duas chamadas a String.ToUpper e String.ToLower. A im-


plementação dessas duas funções, em última instância, alocará memória às strings em
que elas irão operar. Na realidade essa operação é a que realiza uma alocação de string.
Isso é equivalente a invocar a instrução IL newobj, que cria uma instância de tipo. O fato é
que você não pode atribuir ou alterar o valor de uma string. O código talvez apareça
como se uma variável alfanumérica tivesse sido modificada. Na realidade, uma segunda
string é alocada. Quando s é atribuído ao resultado de ToUpper, ele se refere a uma nova
instância String na memória. A original, que se refere a "Classic Cars", agora está disponí-
vel para o garbage collector para recuperação.
Operações a seguir mostram que realizar várias operações não altera a string original:

s := 'Xavier Pacheco';
Console.WriteLine(s.ToUpper);
Console.WriteLine(s);

A saída seria

XAVIER PACHECO
Xavier Pacheco

Considere a instrução a seguir:

s := 'Chmh';
s := s.Insert(4, 'ro').Replace('h', 'a').ToUpper;

A Figura 11.1 ilustra o que na verdade está na memória.

FIGURA 11.1 A alocação de memória com operações de String.


246 Capítulo 11 Trabalhando com as classes String e StringBuilder

Primeiro, a memória é alocada para a string 'Chmh'. Em seguida, o método Insert( )


faz com que uma outra alocação de memória ocorra para a string 'Chmhro'. O método
Replace( ) faz com que ainda ocorra uma outra alocação de memória para conter a string
'Camaro'. Por fim, uma última alocação de memória ocorre para manter a string 'CAMARO' e
a variável s é configurada para referenciar essa string final. Quatro alocações de memória
foram feitas nessa operação.
Apesar de todas as alocações de memória, o CLR trata o gerenciamento de String
bem eficientemente. Na maioria das vezes, você não deve se preocupar com as aloca-
ções uma vez que o garbage collector cuidará delas. Entretanto, se você fizer uma gran-
de quantidade de manipulação de string – talvez em um loop em que os resultados seri-
am muitas alocações – será recomendável utilizar a classe StringBuilder, discutida mais
adiante. Para ilustrar a eficiência do CLR, considere o código a seguir:

var
s1, s2: String;
begin
s1 := 'delphi';
s2 := 'delphi';
Console.WriteLine(System.Object.ReferenceEquals(s1, s2));
Console.ReadLine;
end.

A função System.ReferenceEquals( ) retorna True se duas variáveis se referirem à mesma


instância de objeto. True é retornado por causa da maneira como o CLR trata strings lite-
rais. Essa técnica é chamada internalização de string (string interning).
Um outro benefício da imutabilidade de strings é que elas são thread-safe. Como as
strings não podem ser modificadas, não há nenhuma questão quanto à sincronização de
threads.
Agora que você entende a natureza das strings do .NET, é hora de examinar as várias
operações de string.

Operações com strings


Há numerosas operações que você pode realizar na classe String por meio dos seus vários
métodos. Esta seção discute algumas dessas operações.

NOTA
A Delphi Runtime Library contém rotinas próprias de manipulação de strings. Você pode examinar
a ajuda on-line do Delphi para obter informações sobre esses métodos.

Comparação de strings
A maneira fácil de comparar strings é simplesmente utilizar o operador de igualdade
como mostrado aqui:

if s1 = s2 then
Console.WriteLine('Strings are equal');
O tipo System.String 247

Alternativamente, você pode utilizar o método String.Compare( ):

if (System.String.Compare(s1, s2) = 0) then


Console.WriteLine('Strings are equal');

O valor de retorno do método Compare( ), se menor do que zero, indica que s1 é me-
nor do que s2. Um valor zero indica igualdade. Por fim, um valor maior do que zero indi-
ca que s1 é maior do que s2.
A função String.CompareOrdinal( ) compara duas strings de acordo com o valor numé-
rico de cada caractere dentro da string. Isso é diferente do método String.Compare( ), que é
baseado em uma comparação alfabética ou de idioma.

NOTA
O operador de comparação no nível de linguagem (=) chama @WStrCmp na unit Borland.Delp-
hi.System. Essa função realiza uma comparação entre caracteres em um loop personalizado.
A correspondência mais próxima da função de string no .NET é System.String.CompareOrdi-
nal( ). Sua melhor aposta ao escrever código portável seria permanecer com a versão .NET.

Considere o caso a seguir:

s1 := 'delphi';
s2 := 'Delphi';
r1 := System.String.CompareOrdinal(s1, s2);
Console.WriteLine(r1);

r2 := System.String.Compare(s1, s2, False);


Console.WriteLine(r2);

A saída que você veria aqui é

32
-1

O primeiro valor indica que s1 é maior do que s2. O segundo valor indica que s1 é
menor do que s2.
Uma outra situação interessante envolve comparações de string utilizando culturas
diferentes. Considere

s1 := 'delphi';
s2 := 'DELPHI';
r1 := System.String.Compare(s1, s2, True, CultureInfo.Create('en-US'));
Console.WriteLine(r1);
r2 := System.String.Compare(s1, s2, True, CultureInfo.Create('tr-TR'));
Console.WriteLine(r2);

Neste exemplo, o parâmetro booleano instrui a função a ignorar a distinção entre


maiúsculas e minúsculas de Strings. Examine, porém, a saída:
248 Capítulo 11 Trabalhando com as classes String e StringBuilder

0
1

Neste exemplo, ao utilizar a cultura norte-americana, as strings são iguais. Entretan-


to, ao utilizar a cultura turca, elas não são iguais. A razão tem a ver com as letras I e i. Na
língua inglesa, sem distinção entre maiúsculas e minúsculas, elas são iguais. No idioma
turco, elas são diferentes como indicado pela comparação. Sem entrar nos detalhes das
diferenças entre culturas é importante entender que, ao trabalhar com strings, existem
essas idiossincrasias.
Várias versões sobrecarregadas dos métodos Compare( ) e CompareOrdinal( ) permitem
honrar a distinção entre maiúsculas e minúsculas, utilizar informações sobre a cultura,
comparações de substrings e assim por diante. Você poderia pesquisar essas opções so-
brecarregadas na documentação da Microsoft da classe String.

G L O B A L I Z A Ç Ã O E LO C A L I Z A Ç Ã O
Ao projetar uma aplicação para a comunidade mundial, é importante levar em conta como os vários
idiomas tratam a formatação e apresentação de certos elementos. Exemplos disso são classificações
de caracteres, formatação de data e hora, números, moedas e convenções de medidas, critérios de
classificação e assim por diante. O processo para que sua aplicação funcione em muitas culturas/lo-
calidades é chamado globalização. O processo de adaptar uma aplicação globalizada a uma cultu-
ra/localidade específica é chamado localização. No .NET Framework, o namespace System.Globa-
lization contém as classes que tratam informações relacionadas à cultura. Uma dessas classes é a
classe CultureInfo, que é passada para muitas rotinas de formatação de texto. Uma boa idéia seria
examinar os muitos artigos no site Web msdn.microsoft.com sobre a globalização.

Rotinas de modificação de strings


O método String.Concat( ) é um outro método intensamente sobrecarregado. O có-
digo a seguir demonstra a versão do método Concat( ) que concatena as representações
de string de diferentes objetos quando um array of object é passado como um parâmetro.

var
s: System.String;
i: Integer;
d: double;
ObjAry: Array[0..2] of System.Object;
begin
s := 'X';
i := 55;
d := 23.23;

ObjAry[0] := s;
ObjAry[1] := System.Object(i);
ObjAry[2] := System.Object(d);
Console.WriteLine(System.String.Concat(ObjAry));
end.
O tipo System.String 249

Aqui, a saída mostrada seria "X5523.23" a menos que você esteja na Noruega; nesse
caso, seria lida "X5523,23".
Esse exemplo mostra como concatenar duas strings:

s1 := '1967 ';
s2 := 'Camaro';
Console.WriteLine(System.String.Concat(s1, s2));

Você poderia examinar as outras variações da função Contact( ). Naturalmente, a


maneira mais simples de concatenar strings é utilizar o operador de adição (+). Uma for-
ma alternativa do exemplo anterior seria

s1 := '1967 ';
s2 := 'Camaro';
Console.WriteLine(s1 + s2);

Já vimos a função String.Insert( ) em uso. Basicamente, ela fornece uma posição ini-
cial e uma string para inserir como mostrado aqui. Tenha em mente que strings são base-
adas em zero ao utilizar funções System.String que recebem um parâmetro de índice.

s1 := '1967 Camaro';
s2 := S1.Insert(5, 'Z28 ');

Esse código produz a string "1967 Z28 Camaro".


Você também pode utilizar a função String.Remove( ) para remover um número espe-
cificado de caracteres de um local especificado dentro de uma string:

s1 := '1967 Z28 Camaro';


s2 := S1.Remove(5, 4);

Esse código produz a string "1967 Camaro".


O método String.Replace( ) substitui uma string especificada por uma outra string.
Por exemplo, o código a seguir

s1 := '1967 Z28 Camaro';


s2 := S1.Replace('Z28', 'SS');

produz a string "1967 SS Camaro".


Utilizar a função String.Split( ) é útil quando é necessário separar duas strings por
um delimitador. Por exemplo, dado o par nome/valor, normalmente ele é representado
como "Name=Value". O código a seguir ilustra como você poderia utilizar essa função:

var
nv: System.String;
sAry: array of System.String;
begin
nv := 'Name=Xavier Pacheco';
sAry := nv.Split(['=']);
end;
250 Capítulo 11 Trabalhando com as classes String e StringBuilder

A função String.Split( ) separa as strings e as retorna em um array de strings.


Você pode converter todos os caracteres em uma string em letras maiúsculas utili-
zando a função String.ToUpper( ) e, inversamente, alterar todos os caracteres para letras
minúsculas utilizando a função String.ToLower( ).

Outras operações de string


Para determinar o comprimento de uma String, utilize a propriedade String.Length.
Para copiar uma string, simplesmente atribua uma string a uma outra,

s2 := s1;

ou utilize a função String.Copy( ):

s2 := System.String.Copy(s1);

Observe que há uma pequena diferença entre essas duas operações. Executar a linha
a seguir depois de realizar as operações mostra a diferença:

Console.WriteLine(System.Object.ReferenceEquals(s1, s2));

Essencialmente, ao utilizar o operador de atribuição (:=), você faz uma atribuição de


referência. Isto é, as duas variáveis s1 e s2 irão referenciar o mesmo espaço de memória
que contém a string. A função String.Copy( ) cria uma alocação adicional para a string e
copia o conteúdo da primeira alocação para a segunda alocação.
Há três funções para aparar (trim) uma string – String.Trim( ), String.TrimStart( ) e
String.TrimEnd( ). A função Trim( ) remove todas as ocorrências de um conjunto de carac-
teres do início e fim de uma String. TrimStart( ) e TrimEnd( ) fazem a mesma coisa, mas so-
mente do início e fim, respectivamente. O código a seguir

s := '%^ This string has extraneous characters. *)(';


s := s.Trim(['%', '(', ')', ' ', '*', '^']);

remove os caracteres especificados no array de caracteres da String inicial.


As funções String.PadLeft( ) e String.PadRight( ) alinham um caractere especificado à
esquerda e à direita para uma string com um comprimento especificado. Então, por
exemplo, o código

s := 'The String';
Console.WriteLine(s.PadLeft(20, '.'));

mostra “..........The String”.

A classe StringBuilder
Enquanto a classe String representa uma string imutável, a classe StringBuilder repre-
senta uma string mutável. É recomendável utilizar a classe StingBuilder se precisar ma-
nipular uma string freqüentemente. A classe StringBuilder é definida no namespace
System.Text.
A classe StringBuilder 251

A construção de uma StringBuilder é feita utilizando um dos seus muitos construto-


res sobrecarregados. Os parâmetros dos construtores permitem especificar os valores das
quatro propriedades a seguir:
— Capacity – A propriedade Capacity especifica o tamanho inicial do membro de array
de caracteres contido pela classe StringBuilder. O valor padrão é 16. O StringBuil-
der dobra o tamanho do array de caracteres quando o tamanho existente é menor
que o necessário por uma operação como de acrescentar. Uma vez que essas ope-
rações causam um impacto sobre o desempenho, é recomendável alocar o tama-
nho necessário, se conhecido, durante a construção da StringBuilder.
— Length – Refere-se ao comprimento da string nessa instância.

— MaxCapacity – A propriedade MaxCapacity especifica o número máximo de caracteres


que pode ser contido pela instância atual da classe StringBuilder. Por padrão, esse
valor é Int32.MaxValue. A propriedade StringBuilder.MaxCapacity não pode ser altera-
da depois que StringBuilder é criada.
— Chars – A propriedade Chars é a propriedade indexadora (indexer property) para o ar-
ray interno de caracteres sendo mantido pela StringBuilder. Chars utiliza a indexa-
ção baseada em zero.

Métodos StringBuilder
A classe StringBuilder oferece os métodos mostrados na Tabela 11.1.

TABELA 11.1 Métodos StringBuilder


Método Descrição
Append( ) Um método intensamente sobrecarregado que acrescenta a
representação de string do objeto especificado à string mantida pela
StringBuilder.
AppendFormat( ) Semelhante à funcionalidade Append( ), mas utiliza os especificadores de
formato fornecidos e as informações sobre culturas.
EnsureCapacity( ) Garante que o array de caracteres tenha pelo menos a capacidade
especificada.
Equals( ) Compara duas classes StringBuilder (ou objetos) com o mesmo
MaxCapacity, Capacity, Length e conteúdo de array de caracteres.
Insert( ) Insere a representação String de um Object especificado na posição
especificada.
Remove( ) Remove um intervalo especificado de caracteres.
Replace( ) Substitui ocorrências de um caractere especificado ou string por uma
outra String especificada.
ToString( ) Converte a StringBuilder mutável em uma String imutável.
252 Capítulo 11 Trabalhando com as classes String e StringBuilder

Uso da StringBuilder
O código a seguir é apenas um exemplo simples de como utilizar a classe StringBuilder. As
várias operações alteram a string interna mantida pela StringBuilder. A instrução final
converte o conteúdo da StringBuilder em uma instância de classe String imutável.

var
sb: StringBuilder;
s: System.String;
begin
sb := StringBuilder.Create('Xavier');
sb.AppendFormat('LastName: {0}', 'Pacheco');
Console.WriteLine(sb);
sb.Replace('rL', 'r L');
Console.WriteLine(sb);
sb.Insert(0, 'FirstName: ');
Console.WriteLine(sb);
s := sb.ToString;
Console.WriteLine(s);

Console.ReadLine;

end.

STRINGBUILDER APRIMORADA
Talvez pareça que a funcionalidade da classe StringBuilder é limitada examinando a lista de mé-
todos que ela suporta. Não sabemos por que a Microsoft não foi bem-sucedida ao tornar a
StringBuilder a mais funcional e completa possível. Felizmente, Hallvard Vassbotn certamente foi
bem-sucedido e construiu sua própria StringBuilder. A versão de Hallvard mostra que é possível
estender a classe StringBuilder no Delphi (apesar de ser selada) com o objetivo de tornar essa
classe útil disponível para o Win32. A StringBuilder aprimorada de Hallvard foi inicialmente pu-
blicada na The Delphi Magazine. O código-fonte desse charme pode ser encontrado no diretório
do CD que acompanha este capítulo intitulado: \HallvardGoesToTown.

Formatação de string
A classe String tem uma função Format( ) estática que utilizamos para formatar strings
semelhantes à função Format( ) na RTL do Delphi. O tipo de formatação que ela utiliza é
chamado formatação composta. A versão comumente utilizada da função String.For-
mat( ) sobrecarregada recebe uma string de formatação e valores que ela aplica à string
de formatação. A string de formatação consiste em texto fixo e marcadores de lugar in-
dexados.
A forma mais simples de String.Format( ) é declarada como

Format(format: System.String; arg0: System.Object);

que seria utilizada como


Formatação de string 253

S := System.String.Format('You are {0} years old', System.Object(age));

com age sendo uma variável de inteiro. O código a seguir ilustra a utilização mais comum
que recebe um array de argumentos para preencher os marcadores de lugar na string de
formatação:

const
fStr = 'Name: {0}, age: {1}, Shoe size: {2}';
...
begin
age := 23;
S := System.String.Format(fStr, ['Xavier Pacheco', 38, 8.5]);

O código anterior produz a string "Name: Xavier Pacheco, age: 38, Shoe size: 8.5".
Vamos examinar o uso da string de formatação um pouco mais detalhadamente.
Os marcadores de lugar indexados correspondem a um dos valores no array de valor.
Os marcadores de lugar no item de formatação recebem a sintaxe a seguir:

{index [,alignment][:formatString]}

O marcador de lugar deve iniciar e terminar com chaves de abertura e fechamento as


({ }), respectivamente. Para exibir chaves na string, elas devem ser dobradas: {{ e }}.
O componente de índice identifica o parâmetro correspondente na lista de parâme-
tros. Por exemplo, se a string de formatação mostrada no trecho de código anterior fosse
escrita como

fStr = 'Name: {2}, age: {0}, Shoe size: {1}';

a string resultante quando utilizada na função String.Format( ) seria "Name: 8.5, age: Xavier
Pacheco, Shoesize: 38". Você pode até especificar o mesmo parâmetro mais de uma vez na
string de formatação como

fStr = 'Name: {2}, age: {2}, Shoe size: {2}';

O componente de alinhamento é um valor de inteiro que especifica a largura de


campo e o alinhamento à direita ou à esquerda com base no seu valor com sinal. Um va-
lor positivo indica alinhamento à direita, enquanto um valor negativo indica alinha-
mento à esquerda. Examine o código a seguir:

Console.WriteLine(System.String.Format('{0,15} \ {1,15}',
['Blue', 'Crayon']));
Console.WriteLine(System.String.Format('{0,15} \ {1,15}',
['Yellow', 'Chalk']));

Isso produz a saída de

Blue \ Crayon
Yellow \ Chalk
254 Capítulo 11 Trabalhando com as classes String e StringBuilder

Observe como as strings são alinhadas à direita. Se os valores fossem negativos, a saí-
da seria

Blue \ Crayon
Yellow \ Chalk

Se o comprimento do valor de string exceder o valor de alinhamento, será ignorado.


A string de formatação opcional pode ser um especificador, padrão ou personaliza-
do, de formato.

Especificadores de formato
Os especificadores de formato são strings que especificam como aplicar a formatação a
um tipo base. Eles são em geral utilizados com funções de formatação como o método
String.Format( ) ou ToString( ). Os especificadores de formato podem ser utilizados sozi-
nhos ou com um provedor de formato (format provider), que será discutido mais adiante.
O .NET Framework fornece um conjunto de especificadores de formato padrão para nú-
meros, data, hora e tipos enumerados. Se os especificadores de formato padrão não aten-
derem sua necessidade, você pode aplicar especificadores de formato personalizados às
funções de formatação.

Especificadores de formato numérico


Os especificadores de formato numérico padrão são utilizados a fim de formatar tipos
numéricos para string em padrões predefinidos. O especificador de formato assume esta
forma:

Ann

Onde A é um especificador de formato de texto e nn é um especificador de precisão de


opção representado como um inteiro. O especificador de precisão, se fornecido, poderia
variar de 0–99 e, em geral, especifica o número de dígitos significativos à direita da casa
decimal.
A Tabela 11.2 mostra os vários especificadores de formato numérico que podem ser
utilizados com funções de formatação. Os detalhes sobre esses especificadores de forma-
to podem ser encontrados na documentação on-line da Microsoft.

TABELA 11.2 Especificadores de formato numérico


Especificador de formato Nome
C ou c Moeda
D ou d Decimal
E ou e Científico (exponencial)
F ou f De ponto fixo
G ou g Geral
N ou n Número
Especificadores de formato 255

TABELA 11.2 Continuação


Especificador de formato Nome
P ou p Percentagem
R ou r De arredondamento
X ou x Hexadecimal

A Listagem 11.1 ilustra o uso dos especificadores numéricos.

LISTAGEM 11.1 Utilização de especificadores de formato numérico


1: program NumFmtSpec;
2:
3: {$APPTYPE CONSOLE}
4:
5: var
6: MyCurrency: Currency;
7: MyDecimal: System.Int32;
8: MyDouble: System.Double;
9: MyPercent: System.Double;
10:
11: begin
12: MyCurrency := 23.12;
13: MyDecimal := 8654;
14: MyDouble := 87;
15: MyPercent := 0.25;
16:
17: Console.Writeline('Currency: {0:C2}', MyCurrency);
18: Console.Writeline('Decimal: {0:D4}', System.Object(MyDecimal));
19: Console.Writeline('Scientific: {0:E10}', System.Object(MyDouble));
20: Console.Writeline('Fixed-point: {0:F3}', System.Object(MyDouble));
21: Console.Writeline('General: {0:G3}', System.Object(MyDouble));
22: Console.Writeline('Number: {0:N3}', System.Object(MyDouble));
23: Console.Writeline('Percent: {0:P0}', System.Object(MyPercent));
24: Console.Writeline('Round-trip: {0:R}', System.Object(MyDouble));
25: Console.Writeline('Hexadecimal: {0:X}', System.Object(MyDecimal));
26:
27: Console.ReadLine;
28: end.

u Localize o código no CD: \Code\Chapter 11\Ex01.

As linhas 12–15 inicializam as variáveis utilizadas no programa. As linhas 17–25 exi-


bem essas variáveis com uma formatação diferente. A saída desse programa é

Currency: $23.12
Decimal: 8654
Scientific: 8.7000000000E+001
256 Capítulo 11 Trabalhando com as classes String e StringBuilder

Fixed-point: 87.000
General: 87
Number: 87.000
Percent: 25 %
Round-trip: 87
Hexadecimal: 21CE

Se os especificadores de formato padrão não atenderem a necessidade da formatação


numérica, você poderá utilizar strings de formatação personalizadas. A Tabela 11.3 mos-
tra a lista de especificadores de formato numérico personalizados.

TABELA 11.3 Especificadores de formato numérico personalizados


Especificador de formato personalizado Nome
0 Marcador de lugar zero.
# Marcador de lugar de dígito.
. Ponto de fração decimal.
, Separador de milhar e escalonamento de números.
% Marcador de lugar de percentagem.
E0, E+0, E-0, e0, e+0, e-0 Notação científica.
‘AAA', “AAA' String literal.
; Separador de seção.
Outros caracteres Todos os outros caracteres são utilizados literalmente.

A Listagem 11.2 mostra como alguns desses especificadores de formato personaliza-


dos podem ser utilizados. Para obter informações detalhadas sobre essas strings de for-
matação, consulte a ajuda on-line da Microsoft.

LISTAGEM 11.2 Exemplo de especificadores de formato numérico personalizados


1: program CustNumFmtSpec;
2:
3: {$APPTYPE CONSOLE}
4:
5: var
6: MyDecimal: System.Int32;
7: MyDouble: System.Double;
8: MyPercent: System.Double;
9:
10: begin
11: MyDouble := 123456.78;
12: MyDecimal := 12;
13: MyPercent := 0.25;
14:
15: Console.Writeline('Format {{A:#####}}: {0:#####}',
16: System.Object(MyDouble));
Especificadores de formato 257

LISTAGEM 11.2 Continuação


17: Console.Writeline('Format {{A:00000}}: {0:00000}',
18: System.Object(MyDouble));
19: Console.Writeline('Format {{A:###-##-####}}: {0:###-##-####}',
20: System.Object(MyDouble));
21: Console.Writeline('Scientific: {0:E10}', System.Object(MyDouble));
22: Console.Writeline('Fixed-point: {0:F3}', System.Object(MyDouble));
23: Console.Writeline('General: {0:G3}', System.Object(MyDouble));
24: Console.Writeline('Number: {0:N3}', System.Object(MyDouble));
25: Console.Writeline('Percent: {0:P0}', System.Object(MyPercent));
26: Console.Writeline('Round-trip: {0:R}', System.Object(MyDouble));
27: Console.Writeline('Hexadecimal: {0:X}', System.Object(MyDecimal));
28: Console.ReadLine;
29: end.

u Localize o código no CD: \Code\Chapter 11\Ex02.

As linhas 6–8 inicializam as variáveis utilizadas pelo programa. As linhas 15–28 utili-
zam esses valores ao demonstrar as diferentes maneiras de formatar sua exibição.

Especificadores de formato de data e hora


Os especificadores de formato de data e hora padrão são utilizados para formatar tipos de
data e hora para string em padrões predefinidos. O especificador de formato é um único
caractere.
A Tabela 11.4 mostra os vários especificadores de formato de data/hora que podem
ser utilizados com funções de formatação. Os detalhes sobre esses especificadores de for-
mato podem ser encontrados na documentação on-line da Microsoft.

TABELA 11.4 Especificadores de formato de data/hora


Especificador Nome Descrição
de formato
d Padrão de data curta Exibe o padrão de acordo com
DateTimeFormatInfo.ShortDatePattern.
D Padrão de data longa Exibe o padrão de acordo com
DateTimeFormatInfo.LongDatePattern.
t Padrão de hora curta Exibe o padrão de acordo com
DateTimeFormatInfo.ShortTimePattern.
T Padrão de hora longa Exibe o padrão de acordo com
DateTimeFormatInfo.LongTimePattern.
f Padrão de data/hora Exibe uma combinação de padrões de datas longas e horas
integral (hora curta) curtas.
F Padrão de data/hora Exibe o padrão de acordo com DateTimeFormatInfo.
integral (hora longa) FullDateTimePattern.
g Padrão geral de Exibe uma combinação de padrões de datas curtas e horas
data/hora (hora curta) curtas.
258 Capítulo 11 Trabalhando com as classes String e StringBuilder

TABELA 11.4 Continuação


Especificador Nome Descrição
de formato
G Padrão geral de Exibe uma combinação de padrões de datas curtas e horas
data/hora (hora longa) longas.
M ou m Padrão dia, mês Exibe o padrão de acordo com
DateTimeFormatInfo.MonthDayPattern.
R ou r Padrão RFC1123 Exibe o padrão de acordo com
DateTimeFormatInfo.RFC1123Pattern.
s Padrão de data/hora Exibe o padrão de acordo com DateTimeFormatInfo.
classificável (ISO 8601) SortableDateTimePattern.
u Padrão universal de Exibe o padrão de acordo com DateTimeFormatInfo.
data/hora classificável UniversalSortableDateTimePattern.
U Padrão universal de Exibe o padrão de acordo com DateTimeFormatInfo.
data/hora classificável FullDateTimePattern.
Y ou y Padrão de mês do ano Exibe o padrão de acordo com
DateTimeFormatInfo.YearMonthPattern.

O código da Listagem 11.3 mostra alguns exemplos da utilização dos especificadores


de formato de data/hora.

LISTAGEM 11.3 Utilização de especificadores de formato de data/hora


1: var
2: MyDateTime: System.DateTime;
3: ci: CultureInfo;
4: ccName: String;
5:
6: begin
7: MyDateTime := System.DateTime.Now;
8: ccName := CultureInfo.CurrentCulture.Name;
9: ci := CultureInfo.Create('cs-CZ');
10:
11: Console.WriteLine('d (cs-CZ): '+MyDateTime.ToString('d', ci));
12: Console.WriteLine(System.String.Format('d ({0}): {1}', [ccName,
13: MyDateTime.ToString('d')]));
14: Console.WriteLine(System.String.Format('D ({0}): {1}', [ccName,
15: MyDateTime.ToString('D')]));
16: Console.WriteLine(System.String.Format('t ({0}): {1}', [ccName,
17: MyDateTime.ToString('t')]));
18: Console.WriteLine('t (cs-CZ): '+MyDateTime.ToString('t', ci));
19: Console.WriteLine(System.String.Format('T ({0}): {1}', [ccName,
20: MyDateTime.ToString('T')]));
21: Console.WriteLine('T (cs-CZ): '+MyDateTime.ToString('T'), ci);
22: Console.WriteLine(System.String.Format('f ({0}): {1}', [ccName,
23: MyDateTime.ToString('f')]));
24: Console.WriteLine('f (cs-CZ): '+MyDateTime.ToString('f', ci));
Especificadores de formato 259

LISTAGEM 11.3 Continuação


25: Console.WriteLine(System.String.Format('g ({0}): {1}', [ccName,
26: MyDateTime.ToString('g')]));
27: Console.WriteLine(System.String.Format('G ({0}): {1}', [ccName,
28: MyDateTime.ToString('G')]));
29: Console.WriteLine(System.String.Format('m ({0}): {1}', [ccName,
30: MyDateTime.ToString('m')]));
31: Console.WriteLine(System.String.Format('r ({0}): {1}', [ccName,
32: MyDateTime.ToString('r')]));
33: Console.WriteLine(System.String.Format('s ({0}): {1}', [ccName,
34: MyDateTime.ToString('s')]));
35: Console.WriteLine(System.String.Format('u ({0}): {1}', [ccName,
36: MyDateTime.ToString('u')]));
37: Console.WriteLine(System.String.Format('U ({0}): {1}', [ccName,
38: MyDateTime.ToString('U')]));
39: Console.WriteLine('U (cs-CZ): '+MyDateTime.ToString('U', ci));
40: Console.WriteLine(System.String.Format('y ({0}): {1}', [ccName,
41: MyDateTime.ToString('y')]));
42: Console.ReadLine;
43: end.

u Localize o código no CD: \Code\Chapter 11\Ex03.

A saída produzida por essa aplicação é mostrada aqui:

d (cs-CZ): 4.1.2004
d (en-US): 1/4/2004
D (en-US): Sunday, January 04, 2004
t (en-US): 10:13 PM
t (cs-CZ): 22:13
T (en-US): 10:13:29 PM
T (cs-CZ): 10:13:29 PM
f (en-US): Sunday, January 04, 2004 10:13 PM
f (cs-CZ): 4. ledna 2004 22:13
g (en-US): 1/4/2004 10:13 PM
G (en-US): 1/4/2004 10:13:29 PM
m (en-US): January 04
r (en-US): Sun, 04 Jan 2004 22:13:29 GMT
s (en-US): 2004-01-04T22:13:29
u (en-US): 2004-01-04 22:13:29Z
U (en-US): Monday, January 05, 2004 5:13:29 AM
U (cs-CZ): 5. ledna 2004 5:13:29
y (en-US): January, 2004
260 Capítulo 11 Trabalhando com as classes String e StringBuilder

NOTA
A saída exibida aqui irá variar dependendo da localidade atual.

Neste exemplo, exibi a data de acordo com a localidade em que o programa é executa-
do. A linha 8 obtém o nome da cultura atual. Para torná-lo mais interessante, utilizei Cultu-
reInfo para a República Tcheca de modo que você possa ver como a utilização de diferentes
culturas provavelmente resultará em diferentes formatações. Isso ocorre onde quer que
você veja CS-cz como o nome da cultura na saída exibida, como nas linhas 11 e 21.
Às vezes, os especificadores de formato de data/hora padrão não são adequados
para um propósito – nesse momento, você pode utilizar especificadores de formato per-
sonalizados de data/hora. A Tabela 11.5 mostra os vários especificadores de formato
personalizados de data/hora.

TABELA 11.5 Especificadores de formato de data/hora personalizados


Especificador de formato Descrição
d Dia atual do mês exibido sem zeros à esquerda (1–31).
dd Dia atual do mês exibido com zeros à esquerda (01–31).
ddd Dia abreviado do mês.
dddd Nome completo do dia do mês.
f-fffff Frações de segundos exibidas com o número de dígitos indicado pelo
número de especificadores f.
g, gg, ggg, ... Exibe a era (AD).
h Exibe a hora (1–12) tanto a.m. como p.m. sem zeros à esquerda.
hh Exibe a hora (01–12) tanto a.m. como p.m. com zeros à esquerda para
horas de único dígito.
H Exibe a hora no formato de relógio de 24 horas (0–23) sem zeros à
esquerda.
HH Exibe a hora no formato de relógio de 24 horas (00–23) com zeros à
esquerda para horas de único dígito.
m Exibe o minuto no intervalo 0–59 sem zeros à esquerda.
mm Exibe o minuto no intervalo 00–59 com zeros à esquerda para minutos
de único dígito.
M Exibe o mês (1–12) sem zeros à esquerda.
MM Exibe o mês (01–12) com zeros à esquerda para meses de único
dígito.
MMM Exibe o nome abreviado do mês.
MMMM Exibe o nome integral do mês.
s Exibe os segundos no intervalo 0–59 sem zeros à esquerda.
ss Exibe os segundos no intervalo 00–59 com zeros à esquerda para
segundos de único dígito.
t Exibe o primeiro caractere da designação a.m., p.m.
tt Exibe ambos os caracteres da designação a.m., p.m.
Especificadores de formato 261

TABELA 11.5 Continuação


Especificador de formato Descrição
y Exibe o ano como um número máximo de dois dígitos. Se um único
dígito, é exibido como um único dígito.
yy Exibe o ano como um número máximo de dois dígitos. Se um único
dígito, tem um zero à esquerda (03).
yyyy Exibe o ano integral incluindo o século.
z Exibe o deslocamento de fuso horário em horas integrais somente sem
zeros à esquerda se um único dígito.
zz Exibe as variações de fuso horário em horas integrais somente com
zeros à esquerda se um único dígito.
zzz Exibe as variações de fuso horário em horas e minutos.
: Separador de hora.
/ Separador de data.

O código a seguir ilustra a utilização de especificadores de data/hora personalizados:

var
MyDateTime: System.DateTime;
begin
MyDateTime := System.DateTime.Now;
Console.WriteLine('ddd: '+MyDateTime.ToString('ddd'));
Console.WriteLine('yyyy: '+MyDateTime.ToString('yyyy'));
Console.WriteLine('zz: '+MyDateTime.ToString('zz'));
Console.WriteLine('ggg: '+MyDateTime.ToString('ggg'));
Console.ReadLine;
end.

u Localize o código no CD: \Code\Chapter 11\Ex04.

Esse código produzirá a seguinte saída, que talvez varie dependendo da localidade
atual:

ddd: Sun
yyyy: 2004
zz: -07
ggg: A.D.

Especificadores de formato de tipos enumerados


Ao lidar com tipos enumerados, há quatro especificadores de formato que você pode uti-
lizar, como mostrado na Tabela 11.6.
262 Capítulo 11 Trabalhando com as classes String e StringBuilder

TABELA 11.6 Especificadores de formato de tipos enumerados


String de formatação Descrição
G ou g Exibe o tipo enumerado como um valor de string. Se não for possível,
exibirá o valor de inteiro. Se o atributo Flags estiver configurado, os
valores do tipo enumerado serão concatenados.
F ou f Exibe o tipo enumerado como um valor de string. Se não for possível,
exibirá o valor de inteiro. Exibe uma adição dos valores se possível com
valores de string sendo concatenados e separados por uma vírgula.
D ou d Exibe o tipo enumerado como um valor Integer.
X ou x Exibe o tipo enumerado como um valor hexadecimal.

Dado um tipo enumerado definido como

type
[Flags]
TMyColor = (Red=0, Green=1, Blue=2, Black=4, White=8, Orange=16);

O código a seguir ilustra o uso de cada especificador de formato de tipo enumerado:

MyColor := TMyColor.Green or TMyColor.Black;


Console.WriteLine(Enum(MyColor).ToString('G'));
Console.WriteLine(Enum(MyColor).ToString('F'));
Console.WriteLine(Enum(MyColor).ToString('D'));
Console.WriteLine(Enum(MyColor).ToString('X'));

u Localize o código no CD: \Code\Chapter 11\Ex05.

Esse código produziria a seguinte saída:

Green, Black
Green, Black
5
05

NOTA
No exemplo anterior, você esperaria ser capaz de declarar o tipo enumerado TMyColor como
TMyColor = (Red, Green, Blue, Black, White, Orange);
Entretanto, fazer isso revela um bug no compilador. Basicamente, o compilador ignora o atributo
[Flags] e atribui valores numéricos seqüenciais aos enumerados (0, 1, 2, 3, 4, 5). O compilador
deve utilizar valores de bit-flag (0, 1, 2, 4, 8, 16). Codificando diretamente os valores de bit-flag
para cada valor enumerado, como ilustrado anteriormente, você pode contornar esse problema e
obter os resultados esperados.
NESTE CAPÍTULO
CAPÍTULO 12 — Classes no namespace
System.IO

Operações de arquivo — Trabalhando com o sistema


de diretórios
e de streaming — Trabalhando com arquivos

— Streams

— Acesso assíncrono a streams

Invariavelmente, em algum momento todos os — Monitorando a atividade


do diretório
desenvolvedores precisam trabalhar com o sistema de
arquivos. O namespace System.IO fornece as várias — Serialização
classes para ler e gravar arquivos. Ele também fornece
classes streaming e classes para trabalhar com o sistema
de diretórios do Windows. Este capítulo abrange as
classes definidas no namespace System.IO para trabalhar
com arquivos e streams.

Classes no namespace System.IO


Várias classes no namespace System.IO fornecem a
capacidade de realizar E/S assíncrona e síncrona. As
classes relacionadas a diretórios e arquivos lidam com os
diretórios e arquivos como se eles existissem em disco.
Streams lidam com a leitura e gravação de dados em
outras mídias (incluindo arquivos). Por exemplo, um
stream pode ser um arquivo, memória ou um
compartilhamento de rede. A Tabela 12.1 lista as várias
classes para trabalhar com diretórios e arquivos.

TABELA 12.1 Classes Directory e File no System.IO


Classe Propósito
Directory Fornece métodos estáticos para realizar operações
em diretórios como copiar, mover, renomear,
criar e excluir. Os métodos na classe Directory
realizam uma verificação de segurança. Portanto,
se você pretende realizar mais de uma operação,
utilize a classe DirectoryInfo.
DirectoryInfo Fornece métodos de instância para realizar
operações em diretórios como copiar, mover,
renomear, criar e excluir. Esses métodos não
realizam uma verificação de segurança.
Portanto, essa é a classe ótima a ser utilizada se
você realizar várias operações.
264 Capítulo 12 Operações de arquivo e de streaming

TABELA 12.1 Continuação


Classe Propósito
File Fornece métodos estáticos para operações em arquivos como copiar, mover,
excluir, abrir e fechar. Esses métodos realizam uma verificação de segurança;
portanto, se você pretende realizar várias operações, utilize a classe FileInfo.
FileInfo Fornece métodos de instância para operações em arquivos como copiar, mover,
excluir, abrir e fechar. Esses métodos não realizam uma verificação de segurança;
portanto, essa é a classe ótima a ser utilizada se você realizar várias operações.
FileSystemInfo A classe base para as classes DirectoryInfo e FileInfo.
Path Permite processamento para diversas plataformas de strings de diretório.

A Tabela 12.2 lista as várias classes em System.IO que lidam com operações de strea-
ming.

TABELA 12.2 Classes Stream no System.IO


Classe Propósito
BufferedStream Adiciona um processo de buffer a outros streams para melhorar o
desempenho das operações de leitura e gravação do stream. Um
BufferedStream armazena os dados em cache na memória para reduzir as
chamadas ao sistema operacional.
MemoryStream Fornece armazenamento na memória de dados em streaming.
NetworkStream Fornece a capacidade de transmitir por uma conexão de rede. O
NetworkStream na verdade está contido no namespace System.Net.Sockets.
FileStream Fornece a capacidade de ler/gravar dados de/para arquivos aleatoriamente
por meio de um método Seek( ). FileStream pode ser utilizado assíncrona e
sincronamente.
BinaryReader Fornece a capacidade de ler dados primitivos em um stream.
BinaryWriter Fornece a capacidade de gravar dados primitivos em um stream.
StringReader Lê caracteres em strings.
StringWriter Grava caracteres em strings.
StreamReader Fornece a capacidade de ler caracteres em um stream utilizando uma
codificação específica para a conversão de bytes/caracteres.
StreamWriter Fornece a capacidade de gravar caracteres em um stream utilizando a
codificação para a conversão de caracteres/bytes.
TextReader A classe base para as classes StringReader e StreamReader.
TextWriter A classe base para as classes StringWriter e StreamWriter.
Stream Stream é a classe base abstrata para todas as outras classes Stream. Esse é o
tipo a ser utilizado como um parâmetro quando você quer aceitar um objeto
Stream.

As seções a seguir ilustram o uso de algumas das classes listadas na tabelas 12.1 e
12.2.
Trabalhando com o sistema de diretórios 265

Trabalhando com o sistema de diretórios


Para trabalhar com diretórios, você utiliza as classes Directory ou DirectoryInfo. A classe Di-
rectory fornece métodos estáticos e é conveniente pelo fato de que você não tem de criar
uma instância da classe para utilizá-la. Entretanto, cada chamada invoca uma verificação
de segurança; portanto, ela talvez seja ineficiente. Nesse caso, você utilizaria a classe Di-
rectoryInfo. Essas classes fornecem os métodos para manipular (criar, excluir, mover, co-
piar) diretórios e navegar pelas hierarquias de diretórios.

Criando e excluindo diretórios


O código a seguir mostra como criar um diretório utilizando a classe DirectoryInfo:

dirInfo := DirectoryInfo.Create('c:\ddgtemp');
if not dirInfo.Exists then
dirInfo.&Create;

Primeiro, uma instância de DirectoryInfo é criada e atribuída à variável dirInfo. Pas-


sando um caminho válido para o construtor Create( ), você associa esse diretório ao obje-
to DirectoryInfo; no entanto, você não cria o diretório. Para realmente criar o diretório,
você deve chamar o método Create( ). Além disso, observe o uso do método Direc-
toryInfo.Exists( ), que verifica a existência do diretório a que ele está associado.

NOTA
Quando o compilador Delphi vê os nomes de tipos e métodos como Create( ), type, begin e ou-
tros, ele supõe que estes se referem às construções da linguagem Delphi e não a construções .NET.
Você pode desativar esse comportamento prefixando um caractere com “e” comercial (&) na fren-
te do nome.

Alternativamente, você pode utilizar o método CreateDirectory( ) da classe Directory


como mostrado aqui:

if not Directory.Exists('c:\ddgtemp') then


dirInfo := Directory.CreateDirectory('c:\ddgtemp')
else
dirInfo := DirectoryInfo.Create('c:\ddgtemp');

Você também perceberá que o método Directory.Exists( ) recebe o parâmetro de ca-


minho. Como Directory fornece métodos estáticos, você deve passar informações como
parâmetros para esses métodos.
Para excluir um diretório, você pode chamar o método Directory.Delete( ), que rece-
be o caminho do diretório e um valor Boolean indicando se os subdiretórios devem ser ex-
cluídos. Se o parâmetro recursivo de exclusão for false, uma exceção será levantada se
houver subdiretórios ou arquivos dentro do diretório que você está tentando excluir. O có-
digo a seguir mostra como essa chamada se pareceria:

Directory.Delete('c:\ddgtemp', True); // remove subdiretórios e arquivos


266 Capítulo 12 Operações de arquivo e de streaming

Como opção, você pode chamar o método Delete( ) da classe DirectoryInfo, que só re-
quer o indicador recursivo:

dirInfo.Delete(True);

u Localize o código no CD: \Code\Chapter 12\Ex01\.

Movendo e copiando diretórios


Ocasionalmente , você precisará copiar e/ou mover diretórios. As classes Directory e Di-
rectoryInfo contêm métodos para mover, mas não para copiar diretórios. Esta seção ilus-
tra como utilizar o método Move( ) predefinido e uma técnica para copiar diretórios.
Mover um diretório é uma instrução simples de uma linha:

Directory.Move('c:\source', 'c:\target');

O método Move( ) recebe dois parâmetros – um diretório de origem e um de destino.


Copiar um diretório demanda um pouco mais de trabalho. Sem um método predefinido
para isso, você deve executar sua própria procedure. A Listagem 12.1 ilustra uma das ma-
neiras de realizar isso.

LISTAGEM 12.1 Uma procedure CopyDirectory( )


1: procedure CopyDirectory(SourceDir, TargetDir: String);
2: var
3: FilesToCopy: array of String;
4: i: integer;
5: fa: FileAttributes;
6: srcFile: String;
7: tgtFile: String;
8: begin
9: if (SourceDir = '') or (TargetDir = '') then
10: Exit;
11:
12: // aloca o caractere separador de diretório
13: if SourceDir[SourceDir.Length-1] < > Path.DirectorySeparatorChar then
14: SourceDir := SourceDir + Path.DirectorySeparatorChar;
15:
16: if TargetDir[TargetDir.Length-1] < > Path.DirectorySeparatorChar then
17: TargetDir := TargetDir + Path.DirectorySeparatorChar;
18:
19: { Alternatively, you can use
20:
21: SourceDir := SysUtils.IncludeTrailingPathDelimiter(SourceDir);
22: TargetDir := SysUtils.IncludeTrailingPathDelimiter(TargetDir);
23: }
24: if Directory.Exists(SourceDir) and not
25: SameFileName(SourceDir, TargetDir) then
26: begin
Trabalhando com o sistema de diretórios 267

LISTAGEM 12.1 Continuação


27: FilesToCopy := Directory.GetFileSystemEntries(SourceDir);
28: for i := Low(FilesToCopy) to High(FilesToCopy) do
29: begin
30: srcFile := FilesToCopy[i];
31:
32: fa := System.IO.File.GetAttributes(srcFile);
33: if (fa and FileAttributes.Directory) = FileAttributes.Directory then
34: begin
35: tgtFile := TargetDir+Path.GetFileName(srcFile);
36: if not Directory.Exists(tgtFile) then
37: Directory.CreateDirectory(tgtFile);
38: // Como é um diretório, faz uma recursão nele.
39: CopyDirectory(srcFile, tgtFile);
40: end else // Caso contrário, copia o arquivo.
41: System.IO.File.Copy(srcFile, tgtFile, True)
42: end;
43: end;
44: end;

u Localize o código no CD: \Code\Chapter 12\Ex01\.

A idéia por trás desse código é iterar pelo diretório original e subdiretórios e criar o
diretório e subdiretórios correspondentes no local alvo. O objetivo também é, no proces-
so, copiar arquivos não-diretório. Essa função recursiva realiza isso. Além disso, ilustra al-
guns métodos adicionais das classes em System.IO.
Primeiro, a função assegura que os caminhos do diretório de origem e de destino
terminem com o caractere separador de diretório. Isso ocorre nas linhas 13–17 acrescen-
tando o caractere retornado pela propriedade Path.DirectorySeparatorChar. Utilizar essa
propriedade assegura que você obterá os caracteres específicos à plataforma que, no
Windows, é uma barra (\).

DICA
Observe que você também pode utilizar a função IncludeTrailingPathDelimiter( ) na unit
SysUtils.

O restante da função itera por todos os arquivos no diretório original para obter uma
lista desses arquivos por meio do método Directory.GetFileSystemEntries( ) (linha 27).
Esse método retorna a lista de todos os nomes de arquivos, incluindo diretórios dentro
de um diretório especificado. À medida que o código itera pelo array, ele verifica cada ar-
quivo a fim de determinar se é um diretório examinando seu FileAttribute (linha 32–33).
Um FileAttribute é um tipo enumerado que pode ser uma combinação de um ou mais va-
lores listados na Tabela 12.3.
268 Capítulo 12 Operações de arquivo e de streaming

TABELA 12.3 Valores do tipo enumerado FileAttributes


Valor Descrição
Archived Status de arquivado para backup ou remoção.
Compressed O arquivo está compactado.
Device Não utilizado/reservado
Directory O arquivo é um diretório.
Encrypted Determina se o arquivo está criptografado (se referir-se a um arquivo) ou
se arquivos criados recentemente nesse diretório forem criptografados (se
referirem-se a um diretório).
Hidden O arquivo está oculto. Ele não será incluído em uma listagem de diretório
normal.
Normal O arquivo é normal; nenhum outro atributo pode ser configurado.
NotContentIndexed O arquivo não pode ser indexado pelo serviço de indexação de contexto
do sistema operacional.
Offline O arquivo está off-line e os dados não estão imediatamente disponíveis.
ReadOnly O arquivo está como somente leitura.
ReparsePoint O arquivo contém um ponto de reparse, um bloco definido pelo usuário
dos dados associados com um outro arquivo ou diretório.
SparseFile O arquivo é esparso contendo principalmente zeros.
System O arquivo é do sistema operacional ou é utilizado exclusivamente pelo
sistema operacional.
Temporary O arquivo é temporário.

Se o arquivo for um diretório, este será criado no local de destino e o diretório origi-
nal será recursivamente movido. Esse processo continua até que o arquivo não seja um
diretório – que, nesse caso, é copiado para o local de destino utilizando o método
File.Copy( ).

Examinando informações no diretório


A classe DirectoryInfo herda de FileSystemInfo várias propriedades que contêm informa-
ções sobre o diretório. Essas propriedades estão listadas na Tabela 12.4.

TABELA 12.4 Propriedades de FileSystemInfo


Propriedade Descrição
Attributes Refere-se ao tipo enumerado FileAttribute (Ver Tabela 12.1).
CreationTime A data/hora em que o arquivo foi criado.
CreationTimeUtc A data/hora em que arquivo foi criado de acordo com o Coordinated
Universal Time (UTC).
Extension Extensão do arquivo.
Exists Especifica se o arquivo existe.
FullName O nome de caminho completo do arquivo ou diretório.
LastAccessTime Última vez em que o arquivo ou diretório foi acessado.
Trabalhando com arquivos 269

TABELA 12.4 Continuação


Propriedade Descrição
LastAccessTimeUtc Última vez em que o arquivo ou diretório foi acessado de acordo com o
horário UTC.
LastWriteTime Última vez em que ocorreu uma gravação no arquivo ou diretório.
LastWriteTimeUtc Última vez em que ocorreu uma gravação no arquivo ou diretório de acordo
com o horário UTC.
Name Retorna o nome do arquivo se for um arquivo. Retorna o último nome em
uma hierarquia de diretórios (se ele existir e for um diretório).

A Listagem 12.2 apresenta uma procedure de exemplo que mostra algumas dessas
informações em uma ListBox.

LISTAGEM 12.2 Examinando informações no diretório


1: procedure TWinForm.btnGetDirInfo_Click(sender: System.Object;
2: e: System.EventArgs);
3: begin
4: ListBox1.Items.Add(dirInfo.FullName);
5: ListBox1.Items.Add(dirInfo.Name);
6: ListBox1.Items.Add(System.String.Format('Creation time: {0}',
7: [dirInfo.CreationTime.ToString]));
8: ListBox1.Items.Add(System.String.Format('Last Accessed: {0}',
9: [dirInfo.LastAccessTime.ToString]));
10: ListBox1.Items.Add(System.String.Format('Last Written to: {0}',
11: [dirInfo.LastWriteTime.ToString]));
12: ListBox1.Items.Add('Attributes: ');
13: ListBox1.Items.Add(' '+Enum(dirInfo.Attributes).ToString);
14: end;

u Localize o código no CD: \Code\Chapter 12\Ex02\.

Não há muito a explicar aqui além de indicar o requisito para coerção de tipo na pro-
priedade dirInfo.Attributes de modo que seu método ToString( ) possa ser invocado (li-
nha 13).

Trabalhando com arquivos


Como ocorre com as classes Directory e DirectoryInfo, as classes File e FileInfo existem no
namespace System.IO, que fornece a funcionalidade de manipulação de arquivos. Como
as classes directory e file descendem da mesma classe base, elas contêm algumas das mes-
mas propriedades e sua utilização é quase idêntica.
270 Capítulo 12 Operações de arquivo e de streaming

Criando e excluindo arquivos


Há várias outras maneiras de criar um arquivo. Uma é utilizar o método File.Create-
Text( ) para criar um arquivo de texto. Esse método retorna um StreamWriter que permite
gravar no arquivo. Essa técnica é ilustrada aqui:

var
sw: StreamWriter;
begin
if not System.IO.File.Exists('c:\deleteme.txt') then
begin
sw := System.IO.File.CreateText('c:\deleteme.txt');
try
sw.Write('hello world');
finally
sw.Close;
end;
end;
end;

A classe StreamWriter é descendente da classe TextWriter e é utilizada para gravar uma


série de caracteres em um stream. Discutiremos outros detalhes sobre streams mais adi-
ante neste capítulo. Por enquanto, simplesmente considere que esse código criou e gra-
vou o arquivo especificado em um stream de caracteres.
Para excluir um arquivo, simplesmente utilize o método File.Delete( ):

if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Delete('c:\deleteme.txt');

u Localize o código no CD: \Code\Chapter 12\Ex03\.

Movendo e copiando arquivos


Para mover um arquivo para um local diferente, utilize o método File.Move( ):

if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Move('c:\deleteme.txt', 'c:\deleteme2.txt');

Esse método recebe dois parâmetros – um arquivo de origem e um arquivo de desti-


no. Copiar um arquivo é igualmente simples.

if System.IO.File.Exists('c:\deleteme.txt') then
System.IO.File.Copy('c:\deleteme.txt', 'c:\copy_deleteme.txt');

Examinando informações no arquivo


Examinar as informações no arquivo é idêntico a examinar as informações no diretório.
A Listagem 12.3 ilustra isso.
Streams 271

LISTAGEM 12.3 Examinando informações no arquivo


1: if OpenFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.Ok then
2: begin
3: fi := FileInfo.Create(OpenFileDialog1.FileName);
4: TextBox1.Clear;
5: TextBox1.AppendText(OpenFileDialog1.FileName);
6: TextBox1.AppendText(crlf+'Created: '+fi.CreationTime.ToString);
7: TextBox1.AppendText(crlf+'Last Accessed: '+fi.LastAccessTime.ToString);
8: TextBox1.AppendText(crlf+'Last Written: '+fi.LastWriteTime.ToString);
9: TextBox1.AppendText(crlf+'Length: '+fi.Length.ToString);
10: TextBox1.AppendText(crlf+'Extension: '+fi.Extension);
11: TextBox1.AppendText(crlf+'Attributes: '+Enum(fi.Attributes).ToString);
12: end;

u Localize o código no CD: \Code\Chapter 12\Ex03\.

A linha 3 obtém a instância da classe FileInfo do nome de arquivo passado para o


construtor FileInfo. O código remanescente (linhas 5–11) adiciona os vários valores de
propriedade a uma TextBox. Para manipular o conteúdo de um arquivo, utilize os objetos
stream em System.IO, discutido na seção a seguir.

Streams
Ao utilizar streams, é melhor pensar sobre os diferentes tipos de streams de uma maneira
abstrata. Todos são um stream ou um bloco de dados para gravação e leitura. A mídia que
mantém esses dados diferencia as diferentes classes stream. Portanto, um FileStream hos-
peda um bloco de dados em um arquivo; um MemoryStream hospeda um bloco de dados na
memória. Os métodos com os quais você lê e grava em um stream são os mesmos. Os
exemplos a seguir utilizarão um FileStream; entretanto, a utilização de MemoryStream ou Net-
workStream será, na maioria das vezes, idêntica.
Como as classes de streaming encapsulam o bloco de dados, é necessário leitores e
escritores para ler de um stream e gravar em um stream, respectivamente. Para um stream
“baseado em texto”, essas classes são StreamReader e StreamWriter. Para dados binários, essas
classes são BinaryReader e BinaryWriter. A utilização dessas classes será ilustrada nas seções a
seguir.

Trabalhando com Text File Streams


Gravar em um stream de texto, um FileStream especificamente, requer um objeto File-
Stream e um objeto StreamWriter. A Listagem 12.4 ilustra essa técnica.

LISTAGEM 12.4 Criando e gravando texto em um FileStream


1: var
2: MyFileStream: FileStream;
3: MyStreamWriter: StreamWriter;
4: begin
272 Capítulo 12 Operações de arquivo e de streaming

LISTAGEM 12.4 Continuação


5: // Cria e grava o arquivo
6: MyFileStream := FileStream.Create('c:\ddgdemo.txt',
7: FileMode.OpenOrCreate, FileAccess.Write);
8: try
9: MyStreamWriter := StreamWriter.Create(MyFilestream);
10: try
11: MyStreamWriter.BaseStream.Seek(0, SeekOrigin.End);
12: MyStreamWriter.WriteLine('Hello Delphi for .NET');
13: MyStreamWriter.WriteLine('Delphi for .NET Developer's Guide');
14: finally
15: MyStreamWriter.Close;
16: end;
17: finally
18: MyFileStream.Close;
19: end;
20: end.

u Localize o código no CD: \Code\Chapter 12\Ex04\.


As linhas 6–7 ilustram o uso do construtor FileStream.Create( ). Esse construtor é sobre-
carregado e a versão utilizada aqui recebe um caminho e os parâmetros FileMode e FileAccess.

NOTA
A Listagem 12.4 mostra como acrescentar um arquivo por meio do flag FileMode.OpenOrCreate.
Isso é somente para ilustração. A maneira mais fácil de acrescentar um arquivo é utilizar o atributo
FileMode.Append em vez de FileMode.OpenOrCreate.

FileMode instrui o sistema operacional sobre como o arquivo deve ser aberto. A Tabela
12.5 lista os vários valores de FileMode.

TABELA 12.5 Valores de FileMode


Valor Descrição
Append Abre ou cria um arquivo e busca o final do arquivo. Tentativas de ler resultam em
uma exceção. Deve ser utilizado em conjunção com FileAccess.Write. Requer
FileIOPermissionAccess.Append.
Create Cria um novo arquivo, sobrescrevendo um arquivo existente. Requer
FileIOPermissionAccess.Write e FileIOPermissionAccess.Append.
CreateNew Cria um novo arquivo. Se o arquivo já existir, levanta uma exceção. Requer
FileIOPermissionAccess.Write.
Open Abre um arquivo existente. FileMode.Access deve permitir a abertura desse
arquivo. Levanta uma exceção se o arquivo não existir. Requer
FileIOPermissionAccess indicado pelo parâmetro FileMode.
OpenOrCreate Abre o arquivo se ele existir. Cria um novo arquivo se ele não existir. Requer
FileIOPermissionAccess indicado pelo parâmetro FileMode.
Truncate Abre um arquivo existente e configura seu tamanho como zero. Truncate exige
FileIOPermissionAccess.Write.
Streams 273

NOTA
O tipo enumerado FileIOPermissionAccess não faz parte do namespace System.IO, mas do
System.Security.Permissions. Esse namespace define as classes que têm a ver com o controle de
acesso a operações de sistema e a recursos. Essas permissões são definidas utilizando o sistema
Code Access Security (CAS). Administradores podem conceder permissões ao código e aos usuá-
rios utilizando a .NET Configuration Tool. Para informações adicionais sobre o CAS, visite
www.msdn.Microsoft.com e pesquise “Code Access Security”.

O outro parâmetro passado para o construtor FileStream.Create( ) era FileAccess, que


determina o tipo de acesso permitido. A Tabela 12.6 lista os vários valores que podem ser
passados.

TABELA 12.6 Valores de FileAccess


Valor Descrição
Read Os dados podem ser lidos no arquivo.
ReadWrite O arquivo pode ser lido e gravado.
Write O arquivo pode ser gravado.

O stream de arquivo (FileStream) pode então ser passado para o construtor de Stream-
Writer a fim de obter a instância StreamWriter para o Stream especificado. Nesse ponto, o
StreamWriter pode ser utilizado para gravar dados no stream.
Ao lidar com streams, há o conceito de uma posição atual em que os dados são lidos,
ou gravados, dentro do bloco de dados do stream. Isso poderia estar no começo do stre-
am, no final do stream ou em algum lugar no meio. Isso é representado pelo tipo enume-
rado SeekOrigin. Você pode configurar a posição atual de um stream utilizando seu méto-
do Seek( ), que recebe um deslocamento e um valor de SeekOrigin. Os valores de SeekOrigin
estão listados na Tabela 12. 7.

TABELA 12.7 Valores de SeekOrigin


Valor Descrição
Begin Representa o começo do stream.
Current Representa a posição atual do stream.
End Representa o final do stream.

O parâmetro offset é uma posição relativa a SeekOrigin. Portanto, como mostrado na


linha 11 da Listagem 12.4, o valor de SeekOrigin passado é End com um deslocamento de
zero (0). Isso configura a posição atual do stream exatamente como o final. Portanto,
quaisquer dados gravados no stream serão efetivamente acrescentados ao stream (ver
nota anterior sobre a utilização de Filemode.Append( ). De fato, executar essa demo duas
vezes ilustra isso. Gravar no stream requer a utilização dos métodos StreamWriter.Write( )
ou StreamWriter.WriteLine( ).
274 Capítulo 12 Operações de arquivo e de streaming

Gravar em um FileStream na verdade não grava os dados no arquivo subjacente até o


método StreamWriter.Close( ) ou StreamWriter.Flush( ) ser chamado. Close( ) grava os da-
dos no stream especificado e fecha, impedindo assim operações de gravação adicionais.
Flush( ) simplesmente grava os dados no stream especificado.
Ler em um stream, especificamente em um FileStream, é semelhante a gravar. A Lista-
gem 12.5 ilustra isso.

LISTAGEM 12.5 Criando e lendo texto em um FileStream


1: var
2: MyFileStream: FileStream;
3: MyStreamReader: StreamReader;
4: begin
5: // Cria e lê no arquivo
6: MyFileStream := FileStream.Create('c:\demo.txt', FileMode.OpenOrCreate,
7: FileAccess.Read);
8: try
9: MyStreamReader := StreamReader.Create(MyFilestream);
10: try
11: MyStreamReader.BaseStream.Seek(0, SeekOrigin.Begin);
12: while MyStreamReader.Peek < > -1 do
13: Console.WriteLine(MyStreamReader.ReadLine);
14: finally
15: MyStreamReader.Close;
16: end;
17: finally
18: MyFileStream.Close;
19: end;
20: end.

u Localize o código no CD: \Code\Chapter 12\Ex04\.

Neste exemplo, observe que estamos utilizando um StreamReader em vez de uma


StreamWriter. Além disso, a linha 11 utiliza um SeekOrigin de Begin, o que faz sentido por-
que a intenção é ler no stream. Entretanto, isso não é necessário porque a posição do
arquivo já está no começo do arquivo recém-aberto. Ela é mostrada para propósitos ilus-
trativos. StreamReader tem alguns métodos que merecem ser mencionados aqui, não
necessariamente mostrados no código de exemplo. Peek( ) examina o próximo caractere
no stream, mas não avança a posição do stream. Quando Peek( ) retorna um valor de -1,
o final do stream foi alcançado. Read( ) lê o próximo caractere (ou um array de caracteres
utilizando a versão sobrecarregada de Read( )) e avança a posição do stream. ReadLine( ) lê
e retorna um String contendo uma linha inteira dentro de um TextStream. A posição tam-
bém é avançada para a posição seguindo a linha. ReadToEnd( ) lê da posição atual para o
final do stream. ReadBlock( ) permite especificar alguns caracteres em um stream para
leitura.
Com esse entendimento sobre FileStream, você deve ser capaz de manipular streams
de texto de qualquer tipo. A seção a seguir ilustra o trabalho com streams binários.
Streams 275

Trabalhando com streams de arquivos binários


Para trabalhar com streams binários, utilizamos diferentes classes reader e writer. São as
classes BinaryWriter e BinaryReader. Esse par writer/reader funciona de maneira semelhante
às suas contrapartes de texto. Entretanto, elas contêm vários métodos para gravar/ler ti-
pos primitivos. A Listagem 12.6 ilustra a gravação de dados binários em um FileStream.

LISTAGEM 12.6 Gravando dados binários em um FileStream


1: const
2: cAry: array[1..8] of char = ('o', 'h', ' ', 'y', 'e', 'a', 'h', '!');
3: var
4: MyFileStream: FileStream;
5: MyBinWriter: BinaryWriter;
6: begin
7:
8: MyFileStream := FileStream.Create('ddgdemo.dat', FileMode.OpenOrCreate,
9: FileAccess.ReadWrite);
10: try
11: MyBinWriter := BinaryWriter.Create(MyFileStream);
12: try
13: MyBinWriter.Write(True);
14: MyBinWriter.Write('Unten Gleeben Globbin Globin');
15: MyBinWriter.Write(23);
16: MyBinWriter.Write(23.23);
17: MyBinWriter.Write(cAry);
18: finally
19: MyBinWriter.Close;
20: end;
21: finally
22: MyFileStream.Close;
23: end;
24: end;

u Localize o código no CD: \Code\Chapter 12\Ex05\.

Aqui, você observará que a principal diferença entre StreamWriter e BinaryWriter é o


uso do método Write( ) intensamente sobrecarregado (linhas 13–17). Diferentes versões
do método Write( ) gravam diferentes tipos primitivos. No exemplo, você vê Boolean,
String, Integer, Double e um array of char sendo gravado no arquivo.
Para ler em um stream que contém dados binários, você deve utilizar os métodos
ReadXXXX( ) correspondentes. Isso é demonstrado na Listagem12.7.

LISTAGEM 12.7 Lendo dados binários em um FileStream


1: var
2: MyFileStream: FileStream;
3: MyBinReader: BinaryReader;
276 Capítulo 12 Operações de arquivo e de streaming

LISTAGEM 12.7 Continuação


4: MyCharAry: array of Char;
5: i: integer;
6: begin
7: MyFileStream := FileStream.Create('demo.dat', FileMode.Open,
8: FileAccess.Read);
9: try
10: MyBinReader := BinaryReader.Create(MyFileStream);
11: try
12: Console.WriteLine('Boolean: {0}', [MyBinReader.ReadBoolean]);
13: Console.WriteLine('String: {0}', [MyBinReader.ReadString]);
14: Console.WriteLine('Integer: {0}', [MyBinReader.ReadByte]);
15: Console.WriteLine('Double: {0}', [MyBinReader.ReadDouble]);
16: MyCharAry := MyBinReader.ReadChars(8);
17: for i := Low(MyCharAry) to High(MyCharAry) do
18: Console.Write(MyCharAry[i]);
19: finally
20: MyBinReader.Close;
21: end;
22: finally
23: MyFileStream.Close;
24: end;
25: end;

u Localize o código no CD: \Code\Chapter 12\Ex05\.

Você perceberá que há diferentes métodos ReadXXXX( ) para ler dados no stream (li-
nhas 12–16). O método ReadChars( ) é interessante pelo fato de permitir ler um número
específico de caracteres no stream.

Acesso assíncrono a streams


Em algumas aplicações, é útil permitir que o usuário continue a trabalhar com a aplica-
ção enquanto algum processamento acontece no segundo plano. O capítulo 14 abran-
ge threads em detalhes e como o processamento paralelo é realizado. Com base no mo-
delo de threading, os streams fornecem a capacidade de permitir acesso assíncrono ao
stream subjacente. A Listagem 12.8 ilustra essa técnica para ler um FileStream assincro-
namente.

LISTAGEM 12.8 Acesso assíncrono ao arquivo


1: unit WinForm;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
Acesso assíncrono a streams 277

LISTAGEM 12.8 Continuação


7: System.Windows.Forms, System.Data, System.IO, System.Threading;
8:
9: type
10:
11: TWinForm = class(System.Windows.Forms.Form)
12: strict private
13: procedure Button1_Click(sender: System.Object; e: System.EventArgs);
14: private
15: { Private Declarations }
16: procedure MyCallback(result: IAsyncResult);
17: end;
18:
19: TByteArray = array of Byte;
20:
21: TFileState = class(System.Object)
22: public
23: FFilePath: String;
24: FByteArray: TByteArray;
25: FFStream: FileStream;
26: public
27: constructor Create(aFilePath: String; aByteArraySize: Integer;
28: aFileStream: FileStream);
29: property FilePath: String read FFilePath;
30: property FStream: FileStream read FFStream;
31: property ByteArray: TByteArray read FByteArray;
32: end;
33:
34: implementation
35:
36: procedure TWinForm.Button1_Click(sender: System.Object;
37: e: System.EventArgs);
38: var
39: MyFileStream: FileStream;
40: MyFileState: TFileState;
41: MyFileInfo: FileInfo;
42: begin
43: MyFileStream := FileStream.Create('c:\ddgdemo.txt', FileMode.Open,
44: FileAccess.Read, FileShare.Read, 1, True);
45: try
46: MyFileInfo := FileInfo.Create('c:\ddgdemo.txt');
47: MyFileState := TFileState.Create('c:\ddgdemo.txt', MyFileInfo.Length,
48: MyFileStream);
49: MyFileStream.BeginRead(MyFileState.ByteArray, 0, MyFileInfo.Length,
50: MyCallBack, MyFileState);
51: finally
52: MyFileStream.Close;
53: end;
54: end;
278 Capítulo 12 Operações de arquivo e de streaming

LISTAGEM 12.8 Continuação


55:
56: procedure TWinForm.MyCallback(result: IAsyncResult);
57: var
58: MyState: TFileState;
59: i: integer;
60: begin
61: MyState := result.AsyncState as TFileState;
62: for i := 1 to 10 do
63: begin
64: ListBox1.Items.Add(System.String.Format('In callback for File {0}',
65: [MyState.FilePath]));
66: Thread.Sleep(1500);
67: end;
68: MyState.FStream.Close;
69: end;
70:
71: { TFileState }
72:
73: constructor TFileState.Create(aFilePath: String; aByteArraySize: Integer;
74: aFileStream: FileStream);
75: begin
76: inherited Create;
77: FFilePath := aFilePath;
78: SetLength(FByteArray, aByteArraySize);
79: FFStream := aFileStream;
80: end;
81:
82: end.

u Localize o código no CD: \Code\Chapter 12\Ex06\.

A Listagem 12.8 é uma listagem parcial do exemplo no CD. As classes Stream como
FileStream têm dois métodos para leitura e gravação assíncrona de um arquivo – Begin-
Read( ) (linha 49) e BeginWrite( ). A Listagem 12.8 demonstra o uso do método BeginRe-
ad( ). BeginWrite( ) funciona de maneira semelhante, mas realiza operações de gravação.
BeginRead( ) recebe os parâmetros listados na Tabela 12.8.

ATENÇÃO
Antes de poder executar esse programa, você deve criar o arquivo c:\ddgdemo.txt ou modificar o
código para trabalhar com um arquivo de texto existente.

TABELA 12.8 Parâmetros BeginRead( )


Parâmetro Descrição
Buffer O buffer para ler dados.
Offset Um deslocamento de bytes no buffer, que é o ponto inicial em que os dados serão
gravados.
Monitorando a atividade do diretório 279

TABELA 12.8 Continuação


Parâmetro Descrição
Count Número máximo de bytes a ler.
Callback Uma função de retorno de chamada que será invocada quando a operação de
leitura estiver completa.
State Um objeto utilizado para distinguir solicitações de leitura.

A operação básica é uma em que você invoca BeginRead( ) em um stream. Você espe-
cifica os parâmetros listados na Tabela 12.8, passando opcionalmente uma função de re-
torno (callback) que será chamada quando a operação estiver completa. À medida que a
operação acontece, no segundo plano, o usuário pode continuar a realizar outras fun-
ções dentro da aplicação. Nesse exemplo, o parâmetro passado como a CallBack é o méto-
do MyCallback do formulário principal. Para mostrar que isso é de fato uma operação as-
síncrona, esse método grava em uma ListBox no formulário e então repousa por cerca de
um segundo e meio. Durante esse tempo, o usuário ainda é capaz de digitar na TextBox no
formulário.
A classe TFileState é utilizada para conter as informações sobre o arquivo incluindo o
parâmetro de buffer da função BeginRead( ).

Monitorando a atividade do diretório


FileSystemWatcher é uma classe útil para eventos de monitoração que ocorrem em um ar-
quivo ou dentro de um diretório. Essa classe invoca certos eventos sempre que o diretó-
rio ou arquivo é criado, modificado, renomeado ou excluído. Você fornece os handlers
de evento que ocorrem quando um desses eventos é levantado. A Listagem 12.9 é uma
aplicação simples que demonstra como capturar esses eventos e produzir o tipo de altera-
ção para o usuário .

LISTAGEM 12.9 Exemplo de FileSystemWatcher


1: unit WinForm1;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
7: System.Windows.Forms, System.Data, System.IO;
8:
9: type
10: TWinForm1 = class(System.Windows.Forms.Form)
11: private
12: { Private Declarations }
13: procedure OnChanged(source: System.Object; e: FileSystemEventArgs);
14: procedure OnRenamed(source: System.Object; e: RenamedEventArgs);
15: public
16: fsw: FileSystemWatcher;
280 Capítulo 12 Operações de arquivo e de streaming

LISTAGEM 12.9 Continuação


17: constructor Create;
18: end;
19:
20: implementation
21:
22: constructor TWinForm1.Create;
23: begin
24: inherited Create;
25: InitializeComponent;
26: fsw := FileSystemWatcher.Create('c:\work\');
27: fsw.NotifyFilter := NotifyFilters.Attributes or
28: NotifyFilters.CreationTime or NotifyFilters.DirectoryName or
29: NotifyFilters.FileName or NotifyFilters.LastAccess or
30: NotifyFilters.LastWrite or NotifyFilters.Size;
31: fsw.Filter := '*.*';
32: Include(fsw.Changed, OnChanged);
33: Include(fsw.Created, OnChanged); // Utiliza o handler de evento OnChanged
34: Include(fsw.Deleted, OnChanged); // Utiliza o handler de evento OnChanged
35: Include(fsw.Renamed, OnRenamed);
36: fsw.EnableRaisingEvents := True;
37: end;
38:
39: const
40: crlf = #13#10;
41:
42: procedure TWinForm1.OnChanged(source: TObject; e: FileSystemEventArgs);
43: begin
44: TextBox1.Text := TextBox1.Text +
45: System.String.Format('Change Type: {0}, File: {1}'+crlf,
46: [e.ChangeType, e.FullPath]);
47: end;
48:
49: procedure TWinForm1.OnRenamed(source: TObject; e: RenamedEventArgs);
50: begin
51: TextBox1.Text := TextBox1.Text +
52: System.String.Format('Change Type: {0}, Renamed From: {1} To: {2}'
53: +crlf, [e.ChangeType, e.OldFullPath, e.FullPath]);
54: end;
55:
56: end.

u Localize o código no CD: \Code\Chapter 12\Ex07\.

ATENÇÃO
Antes de ser possível executar o programa da Listagem 12.9, você deve criar um diretório,
c:\work, ou modificar o código a fim de trabalhar em um diretório existente.
Serialização 281

A Listagem 12.9 é o código parcial do exemplo no CD. O construtor FileSystemWatcher


recebe um caminho de diretório a monitorar. Uma versão sobrecarregada desse constru-
tor permite especificar opcionalmente certos tipos de arquivos a monitorar. A proprieda-
de NofityFilter contém valores do tipo enumerado NofityFilters que especificam os tipos
de alterações a monitorar. Estas podem ser combinadas usando o operador binário OR
(linhas 27–30). A Tabela 12.9 lista os valores de NotifyFilters.

TABELA 12.9 Valores de NotifyFilters


Valor Descrição
Attributes Alterações nos atributos de arquivo ou pasta.
CreationTime Alterações na data/hora em que o arquivo ou pasta é criado.
DirectoryName Alterações no nome de diretório.
FileName Alterações em um nome de arquivo.
LastAccess Alterações na data em que o arquivo ou pasta foi acessado/aberto pela última vez.
LastWrite Alterações na data em que ocorreu uma gravação no arquivo ou pasta.
Security Alterações nas configurações de segurança dos arquivos ou pastas.
Size Alterações no tamanho do arquivo ou pasta.

A propriedade FileSystemWatcher.Filter permite especificar tipos de arquivos com


base na sua extensão ou nome (linha 31). Por fim, você atribui os eventos que deseja mo-
nitorar. Como os eventos Changed, Created, and Deleted do FileSystemWatcher são do mesmo
tipo, este exemplo reutiliza o handler de evento OnChanged para os três (linhas 32–34). Os
eventos FileSystemWatcher estão listados na Tabela 12.10.

TABELA 12.10 Eventos de FileSystemWatcher


Evento Descrição
Changed Dispara quando um arquivo ou diretório foi alterado no diretório monitorado.
Created Dispara quando um arquivo ou diretório foi criado no diretório monitorado.
Deleted Dispara quando um arquivo ou diretório foi excluído no diretório monitorado.
Renamed Dispara quando um arquivo ou diretório foi renomeado no diretório monitorado.

Os eventos Changed, Created e Deleted passam um parâmetro FileSystemEventArgs a partir


do qual você pode determinar as informações sobre alterações como o nome de arquivo
e tipo de alteração. O evento Renamed passa um parâmetro RenamedEventArgs, que difere do
tipo FileSystemEventArgs. RenamedEventArgs contém dados adicionais sobre o nome de um
arquivo antigo, como OldFullPath e OldName. Ao executar a aplicação, você verá como as al-
terações dentro do diretório são monitoradas.

Serialização
Daqui para frente, este capítulo discutirá como trabalhar com arquivos e streams. Há
obviamente uma excelente utilidade em ser capaz de gravar dados em vários tipos de mí-
282 Capítulo 12 Operações de arquivo e de streaming

dia como disco e memória. Há uma utilidade ainda maior em ser capaz de gravar seus ti-
pos .NET, incluindo seus estados na mesma mídia. Isso é possível utilizando serializati-
on. Serialization é um processo que converte classes em um stream de bytes que pode ser
persistido ou transferido para um outro processo e até para um outro computador. Dese-
rialization é o processo inverso de ler o stream de bytes em um arquivo ou em um com-
putador remoto e reconstruir a classe que ele contém, inclusive seu estado.
Pense nas possibilidades. Você pode criar sua própria classe personalizada, criar e sal-
var uma instância, definição de classe e dados em um arquivo. Posteriormente, você
pode carregar e reconstruir essa classe de acordo com o estado exato em que ela estava
antes de salvá-la. Você também pode enviar sua classe pela rede para um outro computa-
dor em um outro país, onde ela é reconstruída e utilizada. De fato, esse segundo exemplo
é uma descrição simples de como o ASP.NET remoting funciona. Remoting será discuti-
do nos capítulos 29 e 30 deste livro.

Como a serialização funciona


Uma classe pode tornar-se serializável de duas maneiras. Ela pode ser declarada com o
atributo [Serializable] ou implementar a interface ISerializable. Este capítulo discute o
primeiro e mais fácil método de aplicar o atributo [Serializable]. Visite http://www. Delphi-
Guru.com para um exemplo de implementação da ISerializable no Delphi for .NET.
Pense na classe a seguir:

[Serializable]
TClassicCar = class(System.Object)
public
CarName: String;
Manufacturer: String;
YearIntroduced: DateTime;
end;

Neste exemplo, a classe TClassicCar contém três campos – dois são String e o outro é
um DateTime. Ao serializar uma classe, o CLR não apenas sabe como tratar a classe base,
TClassicCar, mas também sabe como serializar as classes com que ela está relacionada. Se
serializar um objeto, seus membros, desde que também sejam serializáveis, também se-
rão serializados. É possível impedir que um membro seja serializado aplicando o atributo
[NonSerialized] como mostrado aqui:

[Serializable]
TClassicCar = class(System.Object)
public
CarName: String;
[NonSerialized]
Manufacturer: String;
YearIntroduced: DateTime;
end;
Serialização 283

No exemplo anterior, o campo, CarName, não será persistido com a classe.


Internamente, quando uma classe é serializada, o CLR cria um gráfico de objeto.
Gráfico de objeto é uma maneira de o CLR armazenar objetos com seus objetos associati-
vos. A cada objeto é atribuído um identificador. Quando o objeto é serializado, os obje-
tos aos quais ele referencia são associados ao objeto principal pelo seu identificador. Du-
rante a fase de reconstrução, quando um objeto é desserializado, ele pode referenciar um
objeto que ainda não foi desserializado e, ainda assim, reter um identificador para o obje-
to referenciado. Uma vez que o CLR encontra o objeto correspondente durante a desseri-
alização, ele pode arrumar as referências correspondendo os identificadores. Isso permite
ao processo de serialização persistir objetos no stream em qualquer ordem, em vez de ser
necessário persistir objetos em linha entre si.

Formatters
Depois de declarar uma classe como serializável, você precisa decidir sobre um formato a
ser utilizado ao persistir o objeto. O .NET Framework inclui dois formatos – SOAP e biná-
rio. Também é possível criar seu próprio formato personalizado. Para salvar classes no
formato binário, você deve utilizar o BinaryFormatter, que está contido no namespace
System.Runtime.Serialization.Formatters.Binary. Para utilizar o formato SOAP, você deve
utilizar o SoapFormatter, que está contido no namespace System.Runtime.Serialization. For-
matters.Soap.

NOTA
O BinaryFormatter existe no assembly mscorlib.dll. Você só precisa adicionar o namespace à
cláusula uses para utilizá-lo. O SoapFormatter existe no assembly System.Runtime.Serializa-
tion.Formatters.Soap.dll. Você deve adicionar essa referência por meio da caixa de diálogo Add
Reference, além de adicionar o namespace à cláusula uses antes de ser capaz de utilizar a classe So-
apFormatter.

O formatter converte objetos no formato em que eles serão serializados. O formatter


também reconstrói os objetos a partir do formato subjacente. É até mesmo possível (mas
relativamente trabalhoso) criar seu próprio formatter personalizado.

Um exemplo de serialização/desserialização
A Listagem 12.10 mostra o código parcial para um exemplo no CD que ilustra o processo
de serialização e desserialização.

LISTAGEM 12.10 Exemplo de serialização/desserialização


1: program Serialize;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\
➥v1.1.4322\System.Runtime.Serialization.Formatters.Soap.dll'}
284 Capítulo 12 Operações de arquivo e de streaming

LISTAGEM 12.10 Continuação


6:
7: uses
8: SysUtils,
9: System.IO,
10: System.Runtime.Serialization.Formatters.Soap,
11: System.Runtime.Serialization.Formatters.Binary;
12:
13: type
14:
15: [Serializable]
16: TClassicCar = class(System.Object)
17: public
18: FCarName: String;
19: [NonSerialized]
20: FManufacturer: String;
21: FYearIntroduced: DateTime;
22: public
23: constructor Create(aCar, aMan: String; aYearIntro: DateTime); override;
24: property YearIntroduced: DateTime read FYearIntroduced;
25: property CarName: String read FCarName;
26: property Manufacturer: String read FManufacturer;
27: end;
28:
29: TClassicCarArray = array[1..2] of TClassicCar;
30:
31: { TClassicCar }
32:
33: constructor TClassicCar.Create(aCar, aMan: String; aYearIntro: DateTime);
34: begin
35: inherited Create;
36: FCarName := aCar;
37: FManufacturer := aMan;
38: FYearIntroduced := aYearIntro;
39: end;
40:
41: const
42: carOutput = 'Model: {0}, Mfg: {1}, Year: {2}';
43: var
44: MyFileStream: FileStream;
45: MySoFormatter: SoapFormatter;
46: MyClassicCar: TClassicCar;
47: MyClassicCarArray: TClassicCarArray;
48: i: integer;
49: begin
50: // Adiciona um carro clássico
51: MyClassicCar := TClassicCar.Create('Camaro', 'Chevrolet',
52: EncodeDate(1967, 1, 1));
53: MyClassicCarArray[1] := MyClassicCar;
Serialização 285

LISTAGEM 12.10 Continuação


54:
55: // Adiciona um outro carro clássico
56: MyClassicCar := TClassicCar.Create('Mustang', 'Ford',
57: EncodeDate(1964, 1, 1));
58: MyClassicCarArray[2] := MyClassicCar;
59:
60: MyFileStream := FileStream.Create('c:\car.xml', FileMode.Create);
61: try
62: MySoFormatter := SoapFormatter.Create;
63: MySoFormatter.Serialize(MyFileStream, MyClassicCarArray);
64: finally
65: MyFileStream.Close;
66: end;
67:
68: // Relê o objeto serializado
69: MyFileStream := System.IO.File.OpenRead('c:\car.xml');
70: try
71: MySoFormatter := SoapFormatter.Create;
72: MyClassicCarArray :=
73: TClassicCarArray(MySoFormatter.Deserialize(MyFileStream));
74: for i := Low(MyClassicCarArray) to High(MyClassicCarArray) do
75: Console.WriteLine(System.String.Format(carOutput,
76: [MyClassicCarArray[i].CarName, MyClassicCarArray[i].Manufacturer,
77: MyClassicCarArray[i].YearIntroduced]));
78: finally
79: MyFileStream.Close;
80: end;
81:
82: Console.ReadLine;
83: end.

u Localize o código no CD: \Code\Chapter 12\Ex08\.

As linhas 16–27 definem a classe TClassicCar, declarada como uma classe serializável
pelo seu atributo [Serializable] na linha 15. Essa classe contém três membros que tam-
bém serão serializados juntamente com ela.
A linha 29 declara um array bidimensional de TClassicCar. Essa listagem irá serializar
o array que, por sua vez, irá serializar todas as instâncias TClassicCar que ela contém.
As linhas 50–58 realizam a configuração das instâncias TClassicCar e atribuem essas
instâncias ao array.
As linhas 60–66 ilustram como o array é serializado. Na linha 63, chamamos o méto-
do Serialize da instância SoapFormatter, passando para ele a saída de FileStream e o array a
ser serializado. Mais uma vez, isso converte o array e seus objetos relacionados no forma-
to serializado – nesse caso, SOAP. Se tivéssemos utilizado a classe BinaryFormatter, o for-
mato seria binário. A Figura 12.1 mostra o conteúdo do arquivo car.xml à medida que ele
é carregado no Internet Explorer.
286 Capítulo 12 Operações de arquivo e de streaming

A segunda parte desse programa, linhas 69–80, ilustra o processo de desserializar o


array no arquivo. O método SoapFormatter.Deserialize( ) aceita um stream de arquivo
como um parâmetro. Ele retorna um tipo Object e, portanto, deve ser convertido (type-
cast) no tipo esperado da classe apropriada. Nesse caso, ele espera um array of TClassicCar.
As linhas 96–99 simplesmente gravam o conteúdo do array no Console.

ATENÇÃO
Na linha 73, o “hard-cast” para TClassicCarArray é necessário porque o compilador Delphi não
permite um “as-cast” em um tipo de array estático. O “hard-cast” é implementado como uma ins-
trução IL isinst. Isso significa que se o objeto serializado não for um array de TClassicCar, nenhu-
ma exceção será levantada, mas nil será retornado. Qualquer desreferenciação da variável
MyClassicCarArray resultará no levantamento de uma NullReferenceException. Nesse exemplo
específico, sabemos o que o arquivo contém; mas, no geral, deve-se verificar se MyClassicCar-
Array foi atribuído.

FIGURA 12.1 Car.xml – um objeto serializado.

A serialização é um mecanismo simples, porém muito poderosos não apenas para


salvar, mas também para transferir objetos e seus estados para outros processos.
NESTE CAPÍTULO
CAPÍTULO 13 — Princípios básicos da
construção de componentes

Desenvolvendo — Componentes de exemplo

— Pintura pelo usuário:


controles WinForms o controle PlayingCard

personalizados
por Steve Teixeira

A VCL (Visual Component Library) do Delphi foi


pioneira na combinação de RAD com a construção de
componentes nativos. Em termos de produtividade, a
capacidade de criar e utilizar componentes dentro do
contexto de um framework comum de componentes
orientados a objetos e uma IDE RAD é que distingue o
Delphi de outras ferramentas. Os projetistas do .NET
Framework entenderam isso claramente e, no final,
criaram uma plataforma altamente produtiva em que
componentes e aplicações podem tirar vantagem de um
framework comum no contexto de um ambiente RAD.
A capacidade no Delphi 8 de escrever componentes
.NET personalizados significa que você nunca fica preso
aos controles padrão disponíveis na IDE nem precisa
procurar e comprar soluções de componentes de
terceiros. A capacidade de incorporar seus próprios
componentes personalizados às suas aplicações Delphi
significa que você tem controle total sobre a interface
com o usuário da aplicação. Controles personalizados
são a palavra final quanto à aparência e ao
comportamento da sua aplicação.
Embora o Delphi 8 ofereça a capacidade de criar
uma variedade de componentes, de WinForm e
WebForm até VCL, este capítulo se concentra nos
componentes WinForm. Sendo uma ferramenta nativa
de desenvolvimento .NET, os componentes WinForms
que você constrói no Delphi 8 podem ser facilmente
utilizados em outras IDEs .NET com outras linguagens
.NET como Microsoft Visual Studio.NET with C# ou
Visual Basic .NET.
288 Capítulo 13 Desenvolvendo controles WinForms personalizados

Se sua especialidade for projetar componentes, você irá apreciar todas as informações
que este capítulo oferece. Você aprenderá todos os aspectos do projeto de componentes,
do conceito à integração no ambiente Delphi. Você também aprenderá as armadilhas do
projeto de componentes e algumas dicas e truques para desenvolver componentes alta-
mente funcionais e extensíveis.
Mesmo que seu principal interesse seja desenvolvimento de aplicações, e não proje-
to de componentes, você vai tirar bastante proveito deste capítulo. Incorporar um ou
dois componentes personalizados aos seus programas é a maneira ideal de aprimorar e
tornar a produtividade das suas aplicações mais interessante. Invariavelmente, ao escre-
ver sua aplicação você se encontrará em uma situação em que, dentre todos os compo-
nentes a sua disposição, nenhum é adequado a uma tarefa particular. É aí que entra em
cena o projeto de componentes. Você será capaz de personalizar um componente a fim
de atender suas necessidades exatas e, tomara, projetá-lo de maneira suficientemente in-
teligente para utilizá-lo repetidas vezes nas aplicações subseqüentes.

Princípios básicos da construção


de componentes
As seções a seguir discutem as habilidades básicas necessárias para que você comece a es-
crever componentes. Em seguida, mostraremos como aplicar essas habilidades demons-
trando como projetamos alguns componentes úteis.

Quando construir um componente


Por que escrever um controle personalizado quando provavelmente é menos trabalhoso
operar bem com um componente existente ou um hacking rápido e sujo que fará o servi-
ço? Há várias razões para escrever seu próprio controle personalizado:
— Você quer projetar um novo elemento da interface com o usuário que possa ser
utilizado em mais de uma aplicação.
— Você quer tornar sua aplicação mais robusta separando elementos em classes lógi-
cas orientadas a objetos.
— Você pode não encontrar um componente WinForm .NET ou controle ActiveX
adequado às suas necessidades particulares.
— Você reconhece que há um mercado para um componente particular e quer criar
um componente para compartilhar com outros desenvolvedores .NET, por diver-
são ou lucro.
— Você simplesmente quer aumentar seu conhecimento sobre o Delphi e o Micro-
soft .NET Framework.
Princípios básicos da construção de componentes 289

DICA
Uma das melhores maneiras de aprender a criar componentes personalizados é examinar o códi-
go-fonte dos próprios componentes no framework. Isso certamente é verdade quanto à VCL, uma
vez que o código-fonte da VCL é um recurso inestimável para desenvolvedores VCL. Entretanto, a
Microsoft não disponibiliza o código-fonte do .NET Framework, tornando isso bastante difícil para
os desenvolvedores .NET. Entretanto, é possível inspecionar partes do código-fonte utilizando um
utilitário que descompila os assemblies .NET de volta para o código-fonte. Um tipo de utilitário
como esse é o .NET Reflector da Lutz Roeder, disponível na Web em http://www.aisto.com/roe-
der/dotnet/. O .NET Reflector permite examinar os assemblies da Microsoft (ou quaisquer outros)
visualizando todas as classes, tipos, métodos, dados e recursos. Com essa ferramenta, você pode
explorar a FCL utilizando o descompilador embutido (built-in) para ver como os criadores da .NET
implementaram o framework.

Escrever componentes personalizados pode parecer uma tarefa bem difícil, mas não
acredite nos boatos. Escrever um componente personalizado só depende de você. Natu-
ralmente, escrever componentes altamente funcionais e complexos pode ser difícil, mas
você também pode criar componentes muito úteis de maneira relativamente fácil.

Passos da criação de componentes


Supondo que você já tenha definido um problema e tenha uma solução baseada em
componente, os seis passos básicos para escrever seu componente WinForm são:
1. Decidir sobre uma classe ancestral.

2. Criar a unit do componente dentro de um Package.

3. Adicionar propriedades, métodos e eventos ao seu novo componente.


4. Testar seu componente.

5. Adicionar seu componente à ToolPalette da IDE.

6. Completar a documentação do componente com o código-fonte.

Discutiremos cada um desses passos, em mais ou menos detalhes, por todo este capítulo.

Decidindo sobre a uma classe ancestral


Há quatro superclasses básicas das quais seus componentes irão descender: controles
existentes na FCL ou de terceiros, controles de usuário, controles personalizados e com-
ponentes não-visuais. Por exemplo, se precisar simplesmente estender o comportamen-
to de um controle existente como TextBox, você estenderá um controle FCL existente.
Controles de usuário funcionam como formulários, fornecendo uma superfície de proje-
to em que você pode manipular o controle como um formulário. Se precisar definir uma
classe de componente visual inteiramente nova, você irá lidar com um controle persona-
lizado. Por fim, se quiser criar um componente que possa ser editado no Object Inspec-
tor do Delphi, mas que não tenha uma característica visual em tempo de execução,
você criará um componente não-visual. Diferentes classes .NET representam esses diver-
sos tipos de componentes; a Tabela 13.1 oferece uma referência rápida.
290 Capítulo 13 Desenvolvendo controles WinForms personalizados

TABELA 13.1 Classes base de componentes


Classe .NET Tipos de controles personalizados
TObject Embora as classes que descendem diretamente de TObject
não sejam componentes estritamente falando, elas merecem
ser mencionadas. Utilizaremos TObject como uma classe
base para classes reutilizáveis com as quais você não precisa
trabalhar em tempo de projeto (design time). Um bom
exemplo é o objeto System.IO.File.
System.ComponentModel.Component Esse é um ponto de partida para muitos componentes
não-visuais. Notavelmente, essa classe implementa a
interface IComponent, permitindo que ela forneça
funcionalidades em tempo de projeto dentro da IDE.
System.Windows.Forms.Control Essa é a classe base para todos os componentes renderizados
visualmente em um formulário. Ela fornece propriedades
comuns e eventos específicos para os controles visuais.
System.Windows.Forms.UserControl A classe UserControl opera como um formulário na IDE,
fornecendo uma superfície de projeto em que você pode
colocar outros componentes. Utilize-a como uma classe base
ao criar um componente composto de dois ou mais
componentes.
ComponentName Essa é uma classe existente como TextBox, Button ou
ListView. Utilize um componente já estabelecido como uma
classe base para seus componentes personalizados se quiser
estendê-los em vez de criar um novo componente a partir do
zero. Boa parte dos seus componentes personalizados
entrará nessa categoria.

É extremamente importante entender essas várias classes e também as capacidades


dos componentes existentes. Na maior parte do tempo, você descobrirá que um compo-
nente existente já fornece boa parte da funcionalidade que você exige do seu novo com-
ponente. Só conhecendo as capacidades dos componentes existentes é que você será ca-
paz de decidir a partir de qual componente derivar seu novo componente. Não há como
“injetar” esse conhecimento no seu cérebro a partir deste livro. O que podemos dizer é
que você precisa se esforçar para aprender os componentes e classes-chave da FCL e a
única maneira de fazer isso é utilizá-los, mesmo que somente de maneira experimental.

Criando uma unit para o componente


Ao decidir sobre um componente do qual seu novo componente irá descender, prossiga
e crie uma unit para seu novo componente. Examinaremos os passos para projetar um
novo componente nas várias seções a seguir. Como queremos focalizar os passos e não a
funcionalidade dos componentes, esse componente não fará nada além de ilustrar esses
passos necessários.
O componente é apropriadamente identificado como JustWorthless. JustWorthless irá
descender de System.Windows.Forms.Control e, portanto, poderá ser renderizado em um for-
mulário. Esse componente também herdará as muitas propriedades, métodos e eventos
que já pertencem a Control.
Princípios básicos da construção de componentes 291

A maneira mais fácil de começar é criar um novo package na IDE selecionando File,
New, Package no menu principal. Em seguida, utilize a caixa de diálogo New Items a fim
de que você tenha um ponto de partida para a unit do componente. Selecione File, New,
Other no menu principal e a caixa de diálogo New Items será exibida como mostrado na
Figura 13.1.

FIGURA 13.1 A caixa de diálogo New Items.

Selecione Component for Windows Forms em Delphi a partir de .NET\New Files e


uma nova unit será criada contendo uma classe chamada TComponent que descende de
System.ComponentModel.Component. Como queremos demonstrar a criação de um controle vi-
sual, iremos modificar a unit adicionando System.Windows.Forms à cláusula uses, alterando a
classe ancestral para System.Windows.Forms.Control e renomeando a classe como JustWorthless. A
Listagem 13.1 mostra essa unit.

LISTAGEM 13.1 d8dg.Worthless.pas – um componente Delphi de exemplo


1: unit d8dg.Worthless;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
7: System.Windows.Forms;
8:
9: type
10: JustWorthless = class(System.Windows.Forms.Control)
11: {$REGION 'Designer Managed Code'}
12: strict private
13: /// <resumo>
14: /// Variável designer necessária.
15: /// </resumo>
292 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.1 Continuação


16: Components: System.ComponentModel.Container;
17: /// <resumo>
18: /// Método necessário para suporte ao Designer - não modifique
19: /// o conteúdo desse método com o editor de código.
20: /// </resumo>
21: procedure InitializeComponent;
22: {$ENDREGION}
23: strict protected
24: /// <resumo>
25: /// Limpa quaisquer recursos sendo utilizados.
26: /// </resumo>
27: procedure Dispose(Disposing: Boolean); override;
28: private
29: { Private Declarations }
30: public
31: constructor Create; overload;
32: constructor Create(Container: System.ComponentModel.IContainer);
➥overload;
33: end;
34:
35: implementation
36:
37: uses
38: System.Globalization;
39:
40: {$REGION 'Windows Form Designer generated code'}
41: /// <resumo>
42: /// Método necessário para suporte ao Designer - não modifique
43: /// o conteúdo desse método com o editor de código.
44: /// </resumo>
45: procedure JustWorthless.InitializeComponent;
46: begin
47: Self.Components := System.ComponentModel.Container.Create;
48: end;
49: {$ENDREGION}
50:
51: constructor JustWorthless.Create;
52: begin
53: inherited Create;
54: //
55: // Necessário para suporte ao Windows Form Designer
56: //
57: InitializeComponent;
58: //
59: // TODO: Adicione qualquer código de construtor depois da chamada
InitializeComponent
60: //
61: end;
Princípios básicos da construção de componentes 293

LISTAGEM 13.1 Continuação


62:
63: constructor JustWorthless.Create(Container:
➥System.ComponentModel.IContainer);
64: begin
65: inherited Create;
66: //
67: // Necessário para suporte ao Windows Form Designer
68: //
69: Container.Add(Self);
70: InitializeComponent;
71: //
72: // TODO: Adicione qualquer código de construtor depois da chamada
InitializeComponent
73: //
74: end;
75:
76: procedure JustWorthless.Dispose(Disposing: Boolean);
77: begin
78: if Disposing then
79: begin
80: if Components < > nil then
81: Components.Dispose( );
82: end;
83: inherited Dispose(Disposing);
84: end;
85:
86: end.

u Localize o código no CD: \Code\Chapter 13.

Neste ponto, você pode ver que JustWorthless não é mais do que um componen-
te-esqueleto. Nas seções a seguir, você adicionará propriedades, métodos e eventos a
JustWorthless.

Criando propriedades
Esta seção mostra como adicionar os vários tipos de propriedades aos seus componentes.
Vamos adicionar propriedades de cada tipo comum ao componente JustWorthless para
ilustrar as diferenças entre cada tipo. Cada tipo de propriedade é editado de maneira um
pouco diferente no Object Inspector. Examinaremos cada um desses tipos e como eles
são editados.

Adicionando propriedades simples aos componentes


Propriedades simples se referem a números, strings, caracteres e tipos DateTime. Elas po-
dem ser editadas diretamente pelo usuário a partir do Object Inspector e não exigem ne-
nhum método de acesso especial. A Listagem 13.2 mostra o componente JustWorthless
com três propriedades simples.
294 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.2 Propriedades simples


1: type
2: JustWorthless = class(System.Windows.Forms.Control)
3: private
4: // Armazenamento de dados interno
5: FIntegerProp: Integer;
6: FStringProp: String;
7: FCharProp: Char;
8: public
9: constructor Create; overload;
10: constructor Create(Container: System.ComponentModel.IContainer);
11: overload;
12: published
13: // Tipos de propriedades simples
14: property IntegerProp: Integer read FIntegerProp write FIntegerProp;
15: property StringProp: String read FStringProp write FStringProp;
16: property CharProp: Char read FCharProp write FCharProp;
17: end

u Localize o código no CD: \Code\Chapter 13.

Aqui, o armazenamento interno de dados para o componente é declarado em uma se-


ção private que inicia na linha 3. As propriedades que se referem a esses campos de armaze-
namento são declaradas na seção published que começa na linha 12, o que significa que, ao
instalar o componente no Delphi, você pode editar as propriedades no Object Inspector.
As propriedades e eventos na parte published de uma classe recebem automaticamente o
atributo [Browsable(True)], fazendo com que eles apareçam no Object Inspector.

NOTA
Ao escrever componentes, a convenção do Object Pascal é prefixar nomes de campo privados ini-
ciando com a letra F.
O Delphi Win32 também utiliza a convenção de prefixação de nomes de tipos com a letra T, mas
essa convenção geralmente não é utilizada no Delphi for .NET, visto que ele não se adapta às con-
venções .NET.

DICA
Se quiser assegurar compatibilidade com todas as convenções .NET, incluindo convenções para
atribuição de nomes, projeto de biblioteca, localização (localization), segurança e desempenho,
você deverá examinar o utilitário FxCop (http://www.gotdotnet.com/team/fxcop/). O FxCop ana-
lisa seus assemblies .NET e produz relatórios sobre uma variedade de questões relacionadas à con-
formidade com a convenção.

Adicionando propriedades de tipos enumerados a componentes


Você pode editar as propriedades de tipo enumerado definidas pelo usuário e proprieda-
des booleanas no Object Inspector dando um clique duplo na seção Value ou selecionan-
do o valor da propriedade em uma lista drop-down. Um exemplo dessa propriedade é a
Princípios básicos da construção de componentes 295

propriedade FlatStyle localizada nos controles do tipo botão. Para criar uma propriedade
de tipo enumerado, você primeiro deve definir o tipo enumerado desta maneira:

EnumType = (Zero, One, Two, Three);

Você então define o campo de armazenamento interno a fim de que ele contenha
o valor especificado pelo usuário. A Listagem 13.3 mostra a nova propriedade de tipo
enumerado de para o componente JustWorthless juntamente com uma nova proprieda-
de booleana.

LISTAGEM 13.3 Propriedades de tipo enumerado


1: JustWorthless = class(System.Windows.Forms.Control)
2: private
3: // Armazenamento de dados interno
4: FIntegerProp: Integer;
5: FStringProp: string;
6: FCharProp: Char;
7: FBooleanProp: Boolean;
8: FEnumProp: EnumType;
9: public
10: constructor Create; overload;
11: constructor Create(Container: System.ComponentModel.IContainer);
12: overload;
13: published
14: // Tipos de propriedades simples
15: property IntegerProp: Integer read FIntegerProp write FIntegerProp;
16: property StringProp: string read FStringProp write FStringProp;
17: property CharProp: Char read FCharProp write FCharProp;
18: property BooleanProp: Boolean read FBooleanProp write FBooleanProp;
19: // propriedade enum
20: property EnumProp: EnumType read FEnumProp write FEnumProp;
21: end

u Localize o código no CD: \Code\Chapter 13.

Se fosse instalar esse componente, suas propriedades de tipo enumerado aparece-


riam no Object Inspector como mostrado na Figura 13.2.

NOTA
Não é recomendável a utilização de propriedades do tipo set (conjunto) para componentes Win-
Forms. Embora o Object Inspector saiba como editar as propriedades set para aplicações VCL,
esse não é o caso para uma aplicação WinForms, uma vez que ela trata propriedades set como pro-
priedades de tipo enumerado. As propriedades set também, naturalmente, não são inerentemen-
te suportadas no Microsoft Visual Studio .NET porque não são compatíveis com CLS, assim suas
propriedades set não serão adequadamente reconhecidas nesse ambiente.
296 Capítulo 13 Desenvolvendo controles WinForms personalizados

FIGURA 13.2 O Object Inspector mostrando valores da propriedade de tipo enumerado


para JustWorthless.

Adicionando propriedades objeto a componentes


As propriedades também podem ser objetos ou outros componentes. Por exemplo, as
propriedades Font e Icon de um Form são objetos complexos. Quando uma propriedade é
um objeto, é mais útil aos usuários do componente permitir que a propriedade seja ex-
pandida no Object Inspector de modo que suas próprias propriedades também possam
ser modificadas. Isso pode ser realizado com um pouco de codificação extra, como você
verá mais adiante.
O primeiro passo para adicionar uma propriedade e objeto ao componente Just-
Worthless é definir um objeto que servirá como o tipo dessa propriedade. Esse objeto é
mostrado na Listagem 13.4.

LISTAGEM 13.4 Definição de SomeObject


type
SomeObject = class(TObject)
private
FProp1: Integer;
FProp2: string;
public
constructor Create;
published
property Prop1: Integer read FProp1 write FProp1;
property Prop2: string read FProp2 write FProp2;
end;

A classe SomeObject descende diretamente de TObject, embora ela possa descender de


praticamente qualquer classe base. Atribuímos duas propriedades próprias a essa classe:
Princípios básicos da construção de componentes 297

Prop1 e Prop2, que são tipos simples de propriedade. Também adicionamos um construtor,
que serve para inicializar os campos com algum valor interessante:

constructor SomeObject.Create;
begin
inherited;
FProp1 := 1971;
FProp2 := 'hello';
end;

Agora, podemos adicionar um campo do tipo SomeObject ao componente JustWorthless.


Entretanto, como essa propriedade é um objeto, ela deve ser criada. Caso contrário,
quando o usuário coloca um componente JustWorthless no formulário, não haverá uma
instância de SomeObject que ele possa editar. Portanto, é necessário modificar o código de
construção para JustWorthless criar uma instância de SomeObject. A Listagem 13.5 mostra a
declaração de JustWorthless com sua nova propriedade objeto.

LISTAGEM 13.5 Adicionando propriedades objeto


1: JustWorthless = class(System.Windows.Forms.Control)
2: private
3: // Armazenamento de dados interno
4: FIntegerProp: Integer;
5: FStringProp: string;
6: FCharProp: Char;
7: FBooleanProp: Boolean;
8: FEnumProp: EnumType;
9: FSomeObj: SomeObject;
10: protected
11: procedure Init; virtual;
12: public
13: constructor Create; overload;
14: constructor Create(Container: System.ComponentModel.IContainer);
15: overload;
16: published
17: // Tipos de propriedades simples
18: property IntegerProp: Integer read FIntegerProp write FIntegerProp;
19: property StringProp: string read FStringProp write FStringProp;
20: property CharProp: Char read FCharProp write FCharProp;
21: property BooleanProp: Boolean read FBooleanProp write FBooleanProp;
22: // propriedade enum
23: property EnumProp: EnumType read FEnumProp write FEnumProp;
24: // propriedade objeto
25: property SomeObj: SomeObject read FSomeObj write FSomeObj;
26: end

u Localize o código no CD: \Code\Chapter 13.


298 Capítulo 13 Desenvolvendo controles WinForms personalizados

Observe que adicionamos um método chamado Init( ) como mostrado na linha 11.
Esse método é chamado dos construtores para realizar uma inicialização adicional – no
nosso caso, criar uma instância de SomeObject, como mostrado aqui:

procedure JustWorthless.Init;
begin
FSomeObj := SomeObject.Create;
end;

Se adicionarmos esse componente na IDE tal como ele está atualmente escrito, a
propriedade SomeObj aparecerá, mas não será editável ou expansível. Isso ocorre porque o
Object Inspector só sabe representar e editar propriedades como strings. Para tipos sim-
ples de propriedade, o Object Inspector já sabe converter de e para strings a fim de mani-
pular as propriedades. Esse não é o caso, porém, para tipos complexos como SomeObject.
A fim de fornecer funcionalidade adequada para editar e expandir propriedades obje-
to aninhadas, você deve implementar um TypeConverter. Como o nome indica, TypeConver-
ters realizam a tarefa de converter um objeto entre uma representação de tipo e outra – por
exemplo, da forma nativa para a forma de string e vice-versa. A classe base para um Type-
Converter é System.ComponentModel.TypeConverter, e o Object Inspector sabe como utilizar um
desses objetos para representar objetos aninhados em um componente.
TypeConverters podem ser associados a uma classe ou propriedade utilizando o atribu-
to TypeConverter desta maneira:

type
[TypeConverter(TypeOf(SomeObjectConverter))]
SomeObject = class(TObject)

System.Component.TypeConverter por si só não fornece um comportamento padrão útil


para propriedades objeto aninhadas, mas tem uma classe ancestral útil chamada Sys-
tem.ComponentModel.ExpandableObjectConverter para esse propósito. Para tornar sua proprie-
dade objeto editável e expansível no Object Inspector, você só precisa utilizar a classe
ExpandableObjectConverter ou uma descendente como o conversor de tipo (type converter).
ExpandableObjectConverter fornece o comportamento expansível, mas a maioria das imple-
mentações descende dessa classe a fim de oferecer a string editável excelente no Object
Inspector. Isso pode ser feito sobrescrevendo três métodos-chave: CanConvertFrom( ), Con-
vertFrom( ) e ConvertTo( ). Esses métodos, respectivamente, indicam se a conversão entre
tipos é possível, convertem em tipo nativo e a partir dele. A Listagem 13.6 mostra a defi-
nição de SomeObject e seu TypeConverter, SomeObjectConverter.

LISTAGEM 13.6 Uma implementação de TypeConverter


1: type
2: SomeObjectConverter = class(ExpandableObjectConverter)
3: public
4: function CanConvertFrom(context: ITypeDescriptorContext;
5: t: System.Type): Boolean; override;
6: function ConvertFrom(context: ITypeDescriptorContext;
Princípios básicos da construção de componentes 299

LISTAGEM 13.6 Continuação


7: info: CultureInfo; value: TObject): TObject; override;
8: function ConvertTo(context: ITypeDescriptorContext;
9: culture: CultureInfo; value: TObject;
10: destType: System.Type): TObject; override;
11: end;
12:
13: [TypeConverter(TypeOf(SomeObjectConverter))]
14: SomeObject = class(TObject)
15: private
16: FProp1: Integer;
17: FProp2: string;
18: public
19: constructor Create;
20: published
21: property Prop1: Integer read FProp1 write FProp1;
22: property Prop2: string read FProp2 write FProp2;
23: end;
24:
25: { SomeObjectConverter }
26:
27: function SomeObjectConverter.CanConvertFrom(context:
28: ITypeDescriptorContext; t: System.Type): Boolean;
29: begin
30: if t = TypeOf(System.String) then
31: Result := True
32: else
33: Result := inherited CanConvertFrom(context, t);
34: end;
35:
36: function SomeObjectConverter.ConvertFrom(context: ITypeDescriptorContext;
37: info: CultureInfo; value: TObject): TObject;
38: const
39: Seps: array[0..0] of char = (',');
40: var
41: S: string;
42: PropStrings: array of string;
43: Obj: SomeObject;
44: begin
45: if value is System.String then
46: begin
47: S := value as System.String;
48: if S = '' then
49: Result := SomeObject.Create
50: else begin
51: // analisa sintaticamente o formato "Prop1, Prop2"
52: PropStrings := S.Split(Seps);
53: if Length(PropStrings) < > 2 then
54: raise ArgumentException.Create(System.String.Format(
300 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.6 Continuação


55: 'Cannot convert "{0}" to type SomeObject', S));
56: Obj := SomeObject.Create;
57: Obj.Prop1 := Convert.ToInt32(PropStrings[0].Trim);
58: Obj.Prop2 := PropStrings[1].Trim;
59: Result := Obj;
60: end;
61: end
62: else
63: Result := inherited ConvertFrom(context, info, value);
64: end;
65:
66: function SomeObjectConverter.ConvertTo(context: ITypeDescriptorContext;
67: culture: CultureInfo; value: TObject; destType: System.Type): TObject;
68: var
69: Obj: SomeObject;
70: begin
71: if (destType = TypeOf(System.String)) and (value is SomeObject) then
72: begin
73: Obj := value as SomeObject;
74: // constrói a string como "Prop1, Prop2"
75: Result := Obj.Prop1.ToString + ', ' + Obj.Prop2;
76: end
77: else
78: Result := inherited ConvertTo(context, culture, value, destType);
79: end;
80:
81: { SomeObject }
82:
83: constructor SomeObject.Create;
84: begin
85: inherited;
86: FProp1 := 1971;
87: FProp2 := 'hello';
88: end

u Localize o código no CD: \Code\Chapter 13.

Como a Listagem 13.6 mostra, o SomeObjectConverter TypeConverter converte um Some-


Object em e para uma representação string na forma de Prop1, Prop2. O link entre a pro-
priedade do componente e o conversor de tipo é a declaração do atributo encontrada na
linha 13 da listagem. A Figura 13.3 mostra a visualização dessa propriedade na IDE.
Observe a representação string do objeto e as subpropriedades expandidas.

Adicionando propriedades array aos componentes Algumas propriedades po-


dem ser acessadas como se fossem arrays, isto é, elas contêm uma lista de itens que podem
ser referenciados com um valor de índice. Os itens reais referenciados podem ser qual-
quer tipo de objeto. TextBox.Lines é um bom exemplo desse tipo de propriedade. Iremos
Princípios básicos da construção de componentes 301

FIGURA 13.3 A propriedade JustWorthless.SomeObj visualizada no Object Inspector.

postergar a discussão sobre o componente JustWorthless por um momento e, em vez dis-


so, examinaremos o componente Planets . Planets contém duas propriedades: PlanetName e
PlanetPosition. PlanetName será uma propriedade array que retorna o nome do planeta com
base no valor de um índice de inteiro. PlanetPosition não utilizará um índice de inteiro,
mas, em vez disso, um índice de string. Se essa string for um dos nomes dos planetas, o
resultado será a posição do planeta no sistema solar.
Por exemplo, a instrução a seguir exibirá a string "Neptune" utilizando a propriedade
Planets.PlanetName:

P := Planets.Create;
ShowMessage(P.PlanetName[8]);

Compare a diferença quando a frase From the sun, Neptune is planet number: 8 for gera-
da na instrução a seguir:

P := Planets.Create;
MessageBox.Show('From the sun, Neptune is planet number: ' +
P.PlanetPosition['Neptune']).ToString);

Antes de mostrar esse componente, listaremos algumas características-chave das


propriedades array que diferem das outras propriedades mencionadas:
— As propriedades array são declaradas com um ou mais parâmetros de índice. Esses
índices podem ser qualquer tipo simples. Por exemplo, o índice pode ser um in-
teiro ou uma string, mas não um registro ou uma classe.
— As duas diretivas de acesso das propriedades read e write devem referenciar méto-
dos. Elas não podem referenciar um dos campos do componente.
302 Capítulo 13 Desenvolvendo controles WinForms personalizados

— Se a propriedade array estiver indexada por múltiplos valores – isto é, se a proprie-


dade representar um array multidimensional – o método de acesso deverá incluir
parâmetros em cada índice na mesma ordem definida pela propriedade.

Observe que, no Object Inspector, as propriedades array não são navegáveis.


Agora, discutiremos o componente real mostrado na Listagem 13.7.

LISTAGEM 13.7 Utilizando planetas para ilustrar as propriedades array


1: unit d8dg.SolarSystem;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel;
7:
8: type
9: Planets = class(System.ComponentModel.Component)
10: private
11: // Métodos de acesso da propriedade array
12: function GetPlanetName(AIndex: Integer): string;
13: function GetPlanetPosition(APlanetName: string): Integer;
14: public
15: constructor Create; overload;
16: constructor Create(Container: System.ComponentModel.IContainer);
17: overload;
18: // Propriedade array indexada por um valor de inteiro. Essa será a
19: // propriedade array default.
20: property PlanetName[AIndex: Integer]: string read GetPlanetName;
21: default;
22: // Propriedade array indexada por um valor de string
23: property PlanetPosition[APlanetName: string]: Integer
24: read GetPlanetPosition;
25: end;
26:
27: implementation
28:
29: uses
30: System.Globalization;
31:
32: const
33: // Declara um array constante contendo os nomes dos planetas
34: PlanetNames: array[1..9] of string =
35: ('Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn',
36: 'Uranus', 'Neptune', 'Pluto');
37:
38: function Planets.GetPlanetName(AIndex: Integer): string;
39: var
40: LowP, HighP: Integer;
Princípios básicos da construção de componentes 303

LISTAGEM 13.7 Continuação


41: begin
42: LowP := Low(PlanetNames);
43: HighP := High(PlanetNames);
44: // Retorna o nome do planeta especificado por AIndex.
45: // Se AIndex estiver fora do intervalo, levanta uma exceção
46: if (AIndex < LowP) or (AIndex > HighP) then
47: raise Exception.Create(System.String.Format(
48: 'Wrong Planet number, enter a number {0}-{1}',
49: TObject(LowP), TObject(HighP)))
50: else
51: Result := PlanetNames[AIndex];
52: end;
53:
54: function Planets.GetPlanetPosition(APlanetName: string): Integer;
55: var
56: I: Integer;
57: begin
58: { Compare APlanetName to each planet name and return the index
59: of the appropriate position where APlanetName appears in the
60: constant array. Otherwise return zero. }
61: Result := 0;
62: for I := Low(PlanetNames) to High(PlanetNames) do
63: begin
64: if System.String.Compare(APlanetName, PlanetNames[I], True) = 0 then
65: begin
66: Result := I;
67: Break;
68: end;
69: end;
70: end;
71:
72: end.

u Localize o código no CD: \Code\Chapter 13.

Esse componente dá uma idéia de como você criaria uma propriedade array com um
inteiro e uma string sendo utilizada como um índice. Observe como o valor retornado da
leitura do valor da propriedade array é baseado no valor de retorno da função GetPlanet-
Name( ) ou GetPlanePosition( ) e não no valor de um campo de armazenamento, como é o
caso das outras propriedades. A implementação desses métodos é encontrada nas linhas
38 e 54, respectivamente. Consulte os comentários do código para explicação adicional
sobre esse componente.

Valores default Você pode fornecer a uma propriedade um valor padrão atribuindo
a ela um valor no construtor do componente. Portanto, se adicionássemos a instrução a
seguir ao construtor do componente JustWorthless, sua propriedade FIntegerProp sempre
assumiria o padrão de 100 quando o componente fosse colocado no formulário pela pri-
meira vez:
304 Capítulo 13 Desenvolvendo controles WinForms personalizados

FIntegerProp := 100;

Esse é provavelmente o melhor lugar para mencionar as diretivas default e nodefault


para declarações de propriedade. A diretiva default, por exemplo, pode ser utilizada para
sinalizar o valor default de uma propriedade que utiliza um código semelhante ao se-
guinte:

property SomeProp: Integer read FSomeProp write FSomeProp default 0;

Não confunda essa instrução com o valor default especificado no construtor do


componente que, na verdade, configura o valor da propriedade. Por exemplo, suponha
que você fosse alterar a declaração da propriedade IntegerProp para que o componente
JustWorthless fosse lido desta maneira:

property IntegerProp: Integer read FIntegerProp write FIntegerProp default 100;

Na verdade, essa instrução não configura o valor da propriedade como 100. A diretiva
default meramente adiciona o atributo .NET DefaultValue à propriedade. O atributo Default-
Value é utilizado pelas IDEs para redefinir uma propriedade de acordo com seu valor default
ou determinar se é necessário gerar código para configurar um valor de propriedade.
Inversamente, a diretiva nodefault é utilizada para remover o atributo DefaultValue
quando utilizado com uma propriedade redeclarada. Por exemplo, você pode redeclarar
seu componente para não especificar um valor default para a propriedade SomeProp:

Sample = class(SomeComponent)
published
property SomeProp nodefault;

Propriedades array default Você pode declarar uma propriedade array de modo
que ela seja a propriedade default para o componente ao qual pertence. Isso permite
ao usuário do componente referenciar a instância de objeto como se fosse uma variável
de array. Por exemplo, utilizando o componente Planets, declaramos a propriedade Pla-
nets.PlanetName com a palavra-chave default. Fazendo isso, o usuário do componente não
precisa utilizar o nome da propriedade, PlanetName, a fim de obter um valor. Deve simples-
mente colocar o índice próximo do identificador de objeto. Portanto, as linhas de código
a seguir produzirão o mesmo resultado:

P := Planets.Create;
MessageBox.Show(P.PlanetName[8]);
MessageBox.Show(P[8]);

Somente uma propriedade array default, de um dado tipo de índice, pode ser decla-
rada para um objeto. Isso significa que poderia haver, por exemplo, um componente
com uma propriedade array default Integer- e outra com string-indexed. Propriedades ar-
ray default também podem ser sobrescritas nos descendentes.

Criando eventos
No Capítulo 5 introduzimos eventos e informamos que estes eram propriedades espe-
ciais vinculadas ao código que sempre é executado quando uma ação particular ocor-
Princípios básicos da construção de componentes 305

re. Nesta seção, discutiremos os eventos em mais detalhes. Mostraremos como são ge-
rados e como você pode definir suas propriedades de evento para componentes perso-
nalizados.

De onde os eventos vêm?


A definição geral de um evento é basicamente qualquer tipo de ocorrência resultante da
interação do usuário, sistema ou lógica de código. O evento é vinculado a algum código
que responde a essa ocorrência. A vinculação do evento ao código que responde a um
evento é chamada propriedade de evento e é fornecida na forma de um ponteiro de mé-
todo. Os métodos para os quais uma propriedade de evento aponta são chamados hand-
lers de evento.
Por exemplo, quando o usuário clica com o botão do mouse em um objeto Button,
uma mensagem é enviada à janela Button, que é passada para o sistema .NET e, conse-
qüentemente, resulta em um método sendo chamado no objeto Button. A resposta de
button a isso é disparar seu evento Click. Quando o evento é disparado, o método do
handler de evento de cada um dos ouvintes (listeners) desse evento é chamado, permi-
tindo que eles realizem as ações em resposta ao clique.
O evento Click é somente uma das propriedades de evento padrão definidas na FCL.
Em geral, Click e outras propriedades de evento têm um event-dispatching method (mé-
todo de despacho de evento) correspondente. Esse método é em geral virtual e protegido
(protected) do componente ao qual pertence e é normalmente identificado como OnNo-
meDoEvento.

NOTA
A convenção de nomes, utilizada pelo .NET, para eventos e seus métodos de despacho é oposta à
utilizada pela VCL. No .NET, um evento chamado OnNomeDoEvento terá um método de despacho
de evento chamado OnNomeDoEvento, enquanto na VCL um evento chamado OnNomeDoEvento terá
um método de despacho de evento chamado OnNomeDoEvento.

O método de despacho de evento costuma ter somente uma ou duas linhas de có-
digo – para verificar se há um ouvinte para o evento e então disparar o evento. Esse mé-
todo realiza a lógica para determinar se a propriedade de evento refere-se a qualquer có-
digo fornecido pelo usuário do componente. Para a propriedade Jump, seria o método
OnJump( ). A propriedade Jump e o método OnJump( ) são mostrados na classe de teste a se-
guir, chamada EventTest:

type
EventTest = class(Component)
private
FJump: System.EventHandler;
protected
procedure OnJump; virtual;
property Jump: System.EventHandler add FJump remove FJump;
end;
306 Capítulo 13 Desenvolvendo controles WinForms personalizados

Eis o método EventTest.OnJump( ):


procedure EventTest.OnJump;
begin
if Assigned(FJump) then
FJump(Self, EventArgs.Empty);
end;

Observe que a propriedade FJump é definida como um System.EventHandler. EventHandler


é definido desta maneira:

EventHandler = procedure(Sender: TObject; e: EventArgs) of object;

Isso informa que EventHandler é um tipo procedural que recebe dois parâmetros – Sen-
der, que é do tipo TObject e e, do tipo EventArgs. A diretiva, of object, é o que faz com que
essa procedure torne-se um método. Isso significa que um parâmetro implícito adicional
que você não vê na lista de parâmetros também é passado para essa procedure. Esse é o
parâmetro Self que refere-se ao objeto ao qual esse método pertence.
Na terminologia .NET, EventHandler é chamado delegate.
Quando o método OnJump( ) de um componente é chamado, ele primeiro verifica se
há ouvintes para FJump e, se houver, chama esse método.

NOTA
Observe que esse exemplo utiliza eventos multicast. Você deve evitar a utilização de eventos sin-
gleton em componentes WinForms, uma vez que o evento não irá se comportar do modo espera-
do em outros ambientes, que não o Delphi for .NET. Os eventos singleton existem principalmente
para retrocompatibilidade com versões anteriores do Delphi.

Como um escritor de componentes, você escreve todo o código que define seu even-
to, sua propriedade de evento e seus métodos de despacho. O usuário do componente
fornecerá o handler de evento ao utilizar seu componente. Seu método de despacho de
evento verificará se há ouvintes para o evento e então irá executá-los se eles existirem.

Definindo propriedades de eventos


Antes de definir uma propriedade de evento, você precisa determinar se é necessário um
tipo especial de evento. Isso ajuda a conhecer as propriedades de eventos comuns exis-
tentes na FCL. Na maioria das vezes, você será capaz de fazer com que seu componente
descenda de um dos componentes existentes e simplesmente utilizar suas propriedades
de evento ou talvez tenha de exibir uma propriedade de evento protegida. Se determinar
que nenhum dos eventos existentes atende às suas necessidades, você poderá definir
seus próprios eventos.
Como um exemplo, considere o cenário a seguir. Suponha que você quer um com-
ponente que sirva como um alarme, disparando o evento de alarme em uma data e hora
de sua escolha. Embora provavelmente não seja difícil simplesmente escrever essa lógica
em uma aplicação, a componentização do comportamento fornece uma divisão de tra-
balho mais clara dentro do código-fonte e um fragmento reutilizável que pode ser apro-
veitado em outras aplicações.
Princípios básicos da construção de componentes 307

O componente AlarmClock mostrado na Listagem 13.8 ilustra como você projetaria


esse componente. Mais importante, a listagem mostra como criar seus próprios eventos e
utilizar eventos de dentro do componente.

LISTAGEM 13.8 AlarmClock – Criação de evento


1: unit d8dg.Alarms;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
7: System.Windows.Forms;
8:
9: type
10: AlarmEventArgs = class(EventArgs)
11: public
12: FTime: DateTime;
13: public
14: property Time: DateTime read FTime write FTime;
15: end;
16:
17: AlarmHandler = procedure (Sender: TObject; e: AlarmEventArgs) of object;
18:
19: AlarmClock = class(System.ComponentModel.Component)
20: private
21: FTimer: Timer; // timer interno
22: FAlarm: AlarmHandler; // evento de alarme
23: FAlarmFired: Boolean;
24: procedure TimerHandler(Sender: TObject; e: EventArgs); // handler de tique-taque
25: function GetCurrentTime: DateTime;
26: procedure SetEnabled(Value: Boolean);
27: function GetEnabled: Boolean;
28: procedure SetAlarmTime(const Value: DateTime);
29: protected
30: procedure OnAlarm(args: AlarmEventArgs); virtual;
31: procedure Init; virtual;
32: public
33: FAlarmTime: DateTime;
34: FEnabled: Boolean;
35: constructor Create; overload;
36: constructor Create(Container: System.ComponentModel.IContainer);
37: overload;
38: property CurrentTime: DateTime read GetCurrentTime;
39: published
40: property AlarmTime: DateTime read FAlarmTime write SetAlarmTime;
41: property Enabled: Boolean read GetEnabled write SetEnabled;
42: property Alarm: AlarmHandler add FAlarm remove FAlarm;
43: end;
308 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.8 Continuação


44:
45: implementation
46:
47: uses
48: System.Globalization;
49:
50: procedure AlarmClock.TimerHandler(Sender: TObject; e: EventArgs);
51: var
52: Now: DateTime;
53: Args: AlarmEventArgs;
54: begin
55: if not DesignMode then // não dispara no modo de projeto
56: begin
57: Now := DateTime.Now;
58: // compara Now à data/hora do alarme, ignorando milissegundos
59: if (not FAlarmFired) and (Now >= FAlarmTime) then
60: begin
61: Args := AlarmEventArgs.Create;
62: Args.Time := Now;
63: OnAlarm(Args);
64: FAlarmFired := True;
65: end;
66: end;
67: end;
68:
69: function AlarmClock.GetEnabled: Boolean;
70: begin
71: Result := FTimer.Enabled;
72: end;
73:
74: procedure AlarmClock.SetEnabled(Value: Boolean);
75: begin
76: FTimer.Enabled := Value;
77: if Value then
78: FAlarmFired := False;
79: end;
80:
81: function AlarmClock.GetCurrentTime: DateTime;
82: begin
83: Result := DateTime.Now;
84: end;
85:
86: procedure AlarmClock.Init;
87: begin
88: FTimer := Timer.Create;
89: Self.Components.Add(FTimer);
90: // ciclo de cada segundo
91: FTimer.Interval := 1000;
Princípios básicos da construção de componentes 309

LISTAGEM 13.8 Continuação


92: // ouve o evento Tick do timer
93: Include(FTimer.Tick, TimerHandler);
94: end;
95:
96: procedure AlarmClock.OnAlarm(args: AlarmEventArgs);
97: begin
98: if Assigned(FAlarm) then
99: FAlarm(Self, args);
100: end;
101:
102: procedure AlarmClock.SetAlarmTime(const Value: DateTime);
103: begin
104: if FAlarmTime < > Value then
105: begin
106: FAlarmTime := Value;
107: FAlarmFired := False;
108: end;
109: end;
110:
111: end.

u Localize o código no CD: \Code\Chapter 13.

ATENÇÃO
Nunca faça uma atribuição a uma propriedade em um método writer da propriedade. Por exem-
plo, examine a declaração da propriedade a seguir:

property SomeProp: integer read FSomeProp write SetSomeProp;


....
procedure SetSomeProp(Value: integer);
begin
SomeProp := Value; // Isso causa recursão infinita
end;
Como acessa a propriedade em si (não o campo interno de armazenamento), você faz com que o
método SetSomeProp( ) seja chamado novamente, o que resulta em um loop infinitamente recur-
sivo. Por fim, a aplicação lançará uma exceção de estouro de pilha. Da mesma forma, o mesmo se
aplica à leitura do valor de uma propriedade no seu método reader. Sempre acesse o campo inter-
no de armazenamento nos métodos reader e writer da propriedade.

Ao criar seus próprios eventos, você deve determinar as informações que quer forne-
cer aos usuários do seu componente como um parâmetro no handler de evento. Por
exemplo, ao criar um handler de evento para um evento KeyPress de um formulário, seu
handler de evento será semelhante ao código a seguir:

procedure TWinForm.TWinForm_KeyPress(sender: System.Object;


e: System.Windows.Forms.KeyPressEventArgs);
310 Capítulo 13 Desenvolvendo controles WinForms personalizados

begin

end;

Você não apenas obtém uma referência ao objeto que causou o evento, mas também
um parâmetro KeyPressEventArgs que especifica informações adicionais, por exemplo, a te-
cla que foi pressionada. Dentro da FCL, esse evento ocorreu como resultado de uma
mensagem WM_CHAR Win32 que contém algumas informações adicionais relacionadas à te-
cla pressionada. A FCL cuida de extrair os dados necessários e torná-los disponíveis aos
usuários do componente como parâmetros do handler de evento. Um dos aspectos inte-
ressantes sobre todo o esquema é que ele permite aos escritores de componentes selecio-
nar informações que talvez sejam um pouco difíceis de entender, e torná-las disponíveis
aos usuários do componente em um formato muito mais compreensível e fácil de usar.
Examinando a Listagem 13.8, você verá que definimos o delegate AlarmHandler como

AlarmHandler = procedure (Sender: TObject; e: AlarmEventArgs) of object;

Esse delegate define o tipo procedural para o handler do evento Alarm. Aqui, decidi-
mos que queremos que o usuário tenha uma referência ao objeto que faz com que o
evento ocorra e o valor AlarmEventArgs de quando o evento ocorreu.

DICA
Talvez você perceba que a maioria dos eventos no .NET segue o mesmo padrão de ter como parâ-
metro um objeto remetente e um objeto referente aos argumentos do evento; você deve se esfor-
çar para fazer o mesmo. O objeto de argumentos de evento é em geral uma instância ou um des-
cendente de System.EventArgs. Se for necessário passar múltiplas informações para o handler de
evento, estas podem ser adicionadas como propriedades dos objetos de argumentos de evento.
A vantagem da utilização de um objeto de argumentos de evento em oposição a parâmetros de
evento separados para cada tipo de informação é a flexibilidade de versão: as propriedades podem
ser adicionadas ao objeto de argumentos de evento sem quebrar a retrocompatibilidade, que seria
violada se os parâmetros fossem adicionados a uma versão mais recente de uma definição de dele-
gate.

O campo de armazenamento FAlarm é a referência ao handler de evento e é exibido


no Object Inspector em tempo de projeto pela propriedade Alarm.
A funcionalidade básica do componente utiliza um objeto Timer para emitir uma si-
nalização a cada segundo, verificando a data/hora atual versus o campo FAlarmTime em
cada sinalização. Quando eles coincidem, o evento FAlarm é disparado.
Depois de instalar esse componente na Component Palette, você pode colocá-lo no
formulário e adicionar ao evento Alarm o handler de evento a seguir:

procedure TWinForm.AlarmClock1_Alarm(Sender: System.Object;


e: d8dg.Alarms.AlarmEventArgs);
begin
MessageBox.Show(Convert.ToString(e.Time));
end;
Princípios básicos da construção de componentes 311

Isso deve ilustrar como seu delegate e evento definidos recentemente tornam-se um
handler de evento.

Criando métodos
Adicionar métodos aos componentes não é diferente de adicionar métodos a outros
objetos. Entretanto, há algumas diretrizes que você sempre deve levar em conta ao proje-
tar componentes.

Nenhuma interdependência!
Um dos principais objetivos por trás da criação de componentes é simplificar o uso para
o usuário final. Portanto, é recomendável evitar o máximo possível quaisquer interde-
pendências de método. Por exemplo, nunca é recomendável forçar o usuário a chamar
um método particular a fim de utilizar o componente, e os métodos não devem ser cha-
mados em nenhuma ordem particular. Além disso, os métodos chamados pelo usuário
não devem colocar o componente em um estado que invalide outros eventos ou méto-
dos. Por fim, é recomendável atribuir a seu método um nome significativo, que siga as
convenções apropriadas de modo que o usuário não tenha de tentar adivinhar o que ele
faz.

Exposição de método
Parte do projeto de um componente é conhecer quais métodos devem ser públicos, pri-
vados ou protegidos (public, private ou protected). Você deve levar em conta não apenas
os usuários do seu componente, mas também aqueles que poderiam utilizá-lo como um
ancestral para um outro componente personalizado. A Tabela 13.2 ajudará a decidir o
que entra no seu componente personalizado.

TABELA 13.2 Private, Protected, Public, Published, meu Deus!


Diretiva O que entra aí?
private Variáveis e métodos de instância os quais você não quer que um tipo
descendente seja capaz de acessar ou modificar. Em geral, você fornecerá
acesso a algumas variáveis de instância privadas por meio de propriedades
com as diretivas read e write configuradas, de tal maneira que ajudem a
impedir que os usuários atirem nos seus próprios pés.
strict private Quando é necessário ocultar dados além do nível privado e quando você
quer impedir acesso a dados ou métodos, até mesmo a elementos dentro da
mesma unit.
protected Variáveis de instância, métodos, métodos virtuais que podem ser sobrescritos
e propriedades as quais você quer que classes descendentes sejam capazes de
acessar e modificar – mas não os usuários da sua classe. Uma prática comum
é colocar propriedades na seção protegida de uma classe base para classes
descendentes, a fim de publicá-las conforme necessário.
strict protected Como ocorre com strict private, membros strict protected são
inacessíveis mesmo para itens dentro da mesma unit.
312 Capítulo 13 Desenvolvendo controles WinForms personalizados

TABELA 13.2 Continuação


Diretiva O que entra aí?
public Os métodos e propriedades as quais você quer tornar acessíveis a qualquer
usuário da sua classe. Se houver propriedades que você quer tornar acessíveis
em tempo de execução, mas não em tempo de projeto, esse é o lugar para
colocá-las.
published Propriedades que você quer que sejam colocadas no Object Inspector em
tempo de projeto. O atributo [Browsable(True)] é aplicado a propriedades
contidas dentro desta seção, fazendo com que elas sejam exibidas no Object
Inspector.

Construtores e destrutores
Ao criar um novo componente, você tem a opção de sobrescrever o construtor do com-
ponente ancestral e definir seu próprio. Tenha em mente algumas precauções ao fazer
isso.

Construtores de componente
Talvez você tenha notado que o assistente de componente WinForm gerou dois constru-
tores sobrecarregados, como mostrado aqui:

type
TComponent = class(System.ComponentModel.Component)
strict private
Components: System.ComponentModel.Container;
procedure InitializeComponent;
strict protected
procedure Dispose(Disposing: Boolean); override;
private
{ Declarações Private }
public
constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer); overload;
end;

A implementação de cada um desses construtores chama o método InitializeCompo-


nent( ), que é gerenciado pelo Form Designer da IDE. Se precisar adicionar ao construtor
a lógica de um componente, em geral a melhor idéia é utilizar o padrão InitializeCompo-
nent( ) e criar um outro método que é chamado pelos construtores, em vez de adicionar
vários códigos semelhantes especiais a cada um dos construtores. Isso é demonstrado no
método Init( ) do componente AlarmClock.

Sobrescrevendo destrutores
O código gerado pela IDE inclui automaticamente uma versão sobrescrita do método
Dispose( ). Esse seria o melhor lugar para adicionar qualquer código de limpeza de re-
cursos necessário. Além disso, como você aprendeu no capítulo 5, o método destructor
Princípios básicos da construção de componentes 313

Destroy( ), em vez de ser um destrutor verdadeiro, implementa efetivamente o método


IDisposable.Dispose( ). Como todos os componentes implementam IDisposable, Des-
troy( ) também é o lugar legítimo para liberar quaisquer recursos necessários. Seja ao
sobrescrever Dispose( ) ou Destroy( ), a diretriz geral a ser seguida é certificar-se de cha-
mar o destrutor herdado somente depois de liberar os recursos alocados pelo seu com-
ponente, não antes.

DICA
Como regra geral, ao sobrescrever construtores normalmente você chama primeiro o construtor
herdado e, ao sobrescrever destrutores, chama por último o destrutor herdado. Isso assegura que
a classe foi configurada antes de você modificá-la e que todos os recursos dependentes foram lim-
pos antes de você desfazer-se de uma classe.
Há exceções a essa regra, mas geralmente você deve se ater a ela, a menos que haja uma boa razão
contrária.

Comportamento em tempo de projeto


Considere o seguinte fragmento de código na Listagem 13.8:

procedure AlarmClock.TimerHandler(Sender: TObject; e: EventArgs);


var
Now: DateTime;
Args: AlarmEventArgs;
begin
if not DesignMode then // não dispara no modo de projeto
begin

Observe o uso da propriedade DesignMode para impedir que o evento Alarm seja dispa-
rado em modo de projeto. DesignMode é uma propriedade booleana de System.ComponentMo-
del.Component que indica se o componente está funcionando em modo de projeto na IDE
ou reside em uma aplicação em execução.

Testando o componente
Embora seja emocionante quando você finalmente escreve um componente e está na
fase de teste, não se entusiasme tentando adicionar seu componente à Component Palet-
te antes de ele ser suficientemente depurado. Você deve fazer todos os testes preliminares
do seu componente criando um projeto que crie e utilize uma instância dinâmica do
componente. A razão disso é que os componentes são muito mais difíceis de depurar
quando executados na IDE do que fora dela.
Tenha em mente que testar o componente em tempo de projeto não significa que
seu componente é à prova de falhas. Algum comportamento em tempo de projeto ainda
pode causar danos à IDE do Delphi.
314 Capítulo 13 Desenvolvendo controles WinForms personalizados

DICA
Às vezes, um bug só se manifesta em tempo de projeto – nesse caso, a depuração dentro da IDE
talvez seja necessária. Uma técnica que você pode empregar para depurar esse cenário é executar
uma instância da IDE dentro do depurador de uma outra instância da IDE. Faça isso carregando
duas instâncias da IDE e então utilizando Run, Attach to Process no menu principal a partir de uma
instância da IDE para anexar à outra como um processo de depuração.

Fornecendo um ícone de componente


Nenhum componente personalizado estaria completo sem seu próprio ícone na Compo-
nent Palette. Para criar um desses ícones, utilize seu editor de bitmaps favorito para criar
um bitmap de 16×16 que será o ícone do componente. Os bitmaps do componente po-
dem então ser colocados em um arquivo de recursos .NET (.resx), utilizando um progra-
ma editor de recursos .NET, que deve ser adicionado ao projeto.

DICA
Lutz Roeder, da renomada .NET Reflector, também produz um excelente editor freeware de arqui-
vo .resx chamado Resourcer. Você também encontrará essa ferramenta em http://www.ais-
to.com/roeder/DotNet/.

O atributo ToolboxBitmap é utilizado para relacionar uma imagem bitmap a um com-


ponente específico. A sintaxe para utilizar esse atributo é

[ToolboxBitmap(typeof(ComponentTypeName), "FileName.bmp")]

Componentes de exemplo
As demais seções deste capítulo fornecem alguns exemplos reais da criação de compo-
nentes. Os componentes criados aqui servem a dois propósitos principais. Primeiro, eles
ilustram as técnicas explicadas na primeira parte deste capítulo. Segundo, você pode uti-
lizá-los nas suas aplicações. Você pode até decidir estender as funcionalidades desses
componentes a fim de atender as suas necessidades.

ExplorerViewer: um exemplo de UserControl


Como mencionado anteriormente, os controles UserControl fornecem um meio de proje-
tar um componente utilizando uma superfície de projeto do tipo formulário. O resultado
final costuma ser um componente composto ou um componente constituído de vários
outros em uma única entidade atômica, que pode ser utilizada de diversas formas.
O ExplorerViewer é um excelente exemplo desse tipo de controle. Visualmente falan-
do, o ExplorerViewer consiste em três controles: TreeView à esquerda, ListView à direita e
Splitter no meio. Esse controle tem um comportamento muito semelhante a uma janela
do Windows Explorer e é mostrado no designer na Figura 13.4.
Componentes de exemplo 315

FIGURA 13.4 ExplorerViewer como visto no designer.

O código-fonte para o ExplorerViewer é mostrado na Listagem 13.9.

LISTAGEM 13.9 Código-fonte do ExplorerViewer


1: unit d8dg.ExplorerCtl;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
7: System.Windows.Forms, System.IO, System.Runtime.InteropServices;
8:
9: type
10: ExplorerViewerEventArgs = class(System.EventArgs)
11: private
12: FActiveFile: string;
13: public
14: /// <resumo>
15: /// Arquivo ativo no momento da invocação do evento.
16: /// </resumo>
17: property ActiveFile: string read FActiveFile write FActiveFile;
18: end;
19:
20: ExplorerViewerEvent = procedure (Sender: System.Object;
21: Args: ExplorerViewerEventArgs) of object;
316 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.9 Continuação


22:
23: ExplorerViewer = class(System.Windows.Forms.UserControl)
24: private
25: FIconList: ArrayList;
26: FAutoActivate: Boolean;
27: FFileActivated: ExplorerViewerEvent;
28: function GetCurrentItem: string;
29: procedure SetCurrentItem(const Value: string);
30: procedure DisposeIcons;
31: function IncludeBackslash(APath: string): string;
32: function ExcludeBackslash(APath: string): string;
33: protected
34: /// <doc><desc>
35: /// Adiciona um nó ao TreeView com um dado pai.
36: /// </desc></doc>
37: procedure AddTreeNode(Parent: TreeNode; NodeName: string); virtual;
38: /// <doc><desc>
39: /// Preenche ListView com arquivos a partir de um dado diretório.
40: /// </desc></doc>
41: procedure FillListView(APath: string); virtual;
42: /// <doc><desc>
43: /// Inicializa unidades lógicas de TreeView.
44: /// </desc></doc>
45: procedure FillTreeView; virtual;
46: /// <doc><desc>
47: /// Obtém uma lista de subdiretórios para um nó e cria subnós.
48: /// </desc></doc>
49: procedure RefreshNode(Node: TreeNode); virtual;
50: /// <doc><desc>
51: /// Dispara o evento FileActivated.
52: /// </desc></doc>
53: procedure OnFileActivated(Args: ExplorerViewerEventArgs); virtual;
54: /// <doc><desc>
55: /// Ativa o arquivo especificado utilizando o shell para executar.
56: /// </desc></doc>
57: procedure ActivateFile(FileName: string); virtual;
58: public
59: constructor Create;
60: published
61: /// <doc><desc>
62: /// Especifica se ActiveFile deve ser chamado quando o arquivo é
63: /// ativado na ListView.
64: /// </desc></doc>
65: property AutoActivate: Boolean read FAutoActivate
66: write FAutoActivate default True;
67: /// <doc><desc>
68: /// Nome de caminho completo do arquivo atualmente selecionado.
69: /// </desc></doc>
Componentes de exemplo 317

LISTAGEM 13.9 Continuação


70: property CurrentItem: string read GetCurrentItem write SetCurrentItem;
71: /// <doc><desc>
72: /// Evento que é disparado quando o item é ativado na ListView.
73: /// </desc></doc>
74: property FileActivated: ExplorerViewerEvent add FFileActivated
75: remove FFileActivated;
76: end;
77:
78: ExplorerViewerError = class(Exception)
79: end;
80:
81: [assembly: RuntimeRequiredAttribute(TypeOf(ExplorerViewer))]
82:
83: function ExtractIcon(FileName: string): System.Drawing.Icon;
84:
85: implementation
86:
87: uses
88: System.Globalization, System.Diagnostics;
89:
90: { Icon extraction support }
91:
92: type
93: [StructLayout(LayoutKind.Sequential)]
94: SHFILEINFO = record
95: hIcon: IntPtr;
96: iIcon: Integer;
97: dwAttributes: Cardinal;
98: [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)]
99: szDisplayName: string;
100: [MarshalAs(UnmanagedType.ByValTStr, SizeConst=80)]
101: szTypeName: string;
102: end;
103:
104: SHGFI = (SmallIcon = $00000001, LargeIcon = $00000000,
105: Icon = $00000100, DisplayName = $00000200, Typename = $00000400,
106: SysIconIndex = $00004000, UseFileAttributes = $00000010);
107:
108: [DllImport('Shell32.dll')]
109: function SHGetFileInfo(pszPath: string; dwFileAttributes: Cardinal;
110: out psfi: SHFILEINFO; cbfileInfo: Cardinal; uFlags: SHGFI): Integer;
111: external;
112:
113: function ExtractIcon(FileName: string): System.Drawing.Icon;
114: var
115: SHFI: SHFILEINFO;
116: begin
117: if SHGetFileInfo(FileName, 0, SHFI, Marshal.SizeOf(SHFI),
318 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.9 Continuação


118: SHGFI.Icon or SHGFI.LargeIcon) < > 0 then
119: Result := System.Drawing.Icon.FromHandle(SHFI.hIcon)
120: else
121: Result := nil;
122: end;
123:
124: { Explorerview }
125:
126: constructor ExplorerViewer.Create;
127: begin
128: inherited Create;
129: //
130: // Necessário para suporte ao Windows Form Designer
131: //
132: InitializeComponent;
133: FAutoActivate := True;
134: ListView.Dock := DockStyle.Right;
135: Splitter.Dock := DockStyle.Right;
136: TreeView.Dock := DockStyle.Fill;
137: FIconList := ArrayList.Create;
138: FillTreeView;
139: end;
140:
141: procedure ExplorerViewer.ListView_ItemActivate(sender: System.Object;
142: e: System.EventArgs);
143: var
144: I: Integer;
145: FileName: string;
146: Args: ExplorerViewerEventArgs;
147: begin
148: for I := 0 to ListView.SelectedItems.Count - 1 do
149: begin
150: if TreeView.SelectedNode < > nil then
151: FileName := IncludeBackslash(TreeView.SelectedNode.FullPath)
152: else
153: FileName := '';
154: FileName := FileName + ListView.SelectedItems[I].Text;
155: if FAutoActivate then
156: ActivateFile(FileName);
157: Args := ExplorerViewerEventArgs.Create;
158: Args.ActiveFile := FileName;
159: OnFileActivated(Args);
160: end;
161: end;
162:
163: procedure ExplorerViewer.TreeView_BeforeExpand(sender: System.Object;
164: e: System.Windows.Forms.TreeViewCancelEventArgs);
165: begin
Componentes de exemplo 319

LISTAGEM 13.9 Continuação


166: if e.Node.Tag = nil then
167: RefreshNode(e.Node);
168: end;
169:
170: procedure ExplorerViewer.TreeView_AfterSelect(sender: System.Object;
171: e: System.Windows.Forms.TreeViewEventArgs);
172: begin
173: FillListView(e.Node.FullPath);
174: end;
175:
176: procedure ExplorerViewer.Dispose(Disposing: Boolean);
177: begin
178: if Disposing then
179: begin
180: DisposeIcons;
181: if Components < > nil then
182: Components.Dispose( );
183: end;
184: inherited Dispose(Disposing);
185: end;
186:
187: function ExplorerViewer.GetCurrentItem: string;
188: var
189: Selected: TreeNode;
190: begin
191: Selected := TreeView.SelectedNode;
192: if Selected < > nil then
193: Result := Selected.FullPath
194: else
195: Result := '';
196: end;
197:
198: procedure ExplorerViewer.SetCurrentItem(const Value: string);
199: var
200: I, J: Integer;
201: Directories: array of string;
202: Node: TreeNode;
203: Nodes: TreeNodeCollection;
204: begin
205: if Value < > '' then
206: begin
207: Node := nil;
208: Nodes := TreeView.Nodes;
209: // Divide a string de caminho em barras normais para obter todos os subdiretórios
210: Directories := Value.Split(['\']);
211: // Atravessa a árvore, selecionando um subdiretório por vez
212: for I := 0 to Length(Directories) - 1 do
213: begin
320 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.9 Continuação


214: for J := 0 to Nodes.Count - 1 do
215: begin
216: if System.String.Compare(Nodes[J].Text, Directories[I], True) = 0
217: then
218: begin
219: Node := Nodes[J];
220: Nodes := Node.Nodes;
221: Break;
222: end;
223: end;
224: end;
225: // seleciona o nó interno encontrado
226: if Node < > nil then
227: begin
228: TreeView.SelectedNode := Node;
229: Node.Expand;
230: end;
231: end;
232: end;
233:
234: procedure ExplorerViewer.FillListView(APath: string);
235: var
236: DI: DirectoryInfo;
237: FI: array of FileInfo;
238: I: Integer;
239: FileIcon: System.Drawing.Icon;
240: Item: ListViewItem;
241: begin
242: ListView.Items.Clear;
243: ImageList.Images.Clear;
244: DisposeIcons;
245: DI := DirectoryInfo.Create(IncludeBackslash(APath));
246: // Obtém o array que representa os arquivos no diretório atual.
247: FI := DI.GetFiles;
248: // adiciona cada arquivo a ListView
249: for I := 0 to Length(FI) - 1 do
250: begin
251: Item := ListView.Items.Add(FI[I].Name);
252: FileIcon := ExtractIcon(FI[I].FullName);
253: if FileIcon < > nil then
254: begin
255: // Adiciona o ícone à lista de imagens e salva em ArrayList de modo que ele
possa ser disposto
256: FIconList.Add(FileIcon);
257: ImageList.Images.Add(FileIcon);
258: Item.ImageIndex := ImageList.Images.Count - 1;
259: end;
260: end;
Componentes de exemplo 321

LISTAGEM 13.9 Continuação


261: end;
262:
263: procedure ExplorerViewer.FillTreeView;
264: var
265: LogDrives: array of string;
266: I: Integer;
267: begin
268: // Todos os nós-raiz da árvore são unidades lógicas
269: TreeView.Nodes.Clear;
270: DisposeIcons;
271: LogDrives := Directory.GetLogicalDrives;
272: for I := 0 to Length(LogDrives) - 1 do
273: AddTreeNode(nil, LogDrives[I]);
274: end;
275:
276: procedure ExplorerViewer.RefreshNode(Node: TreeNode);
277: var
278: SubDirs: array of string;
279: I: Integer;
280: begin
281: // Tag no nó indica que o diretório foi processado
282: Node.Tag := TObject(True);
283: // Obtém lista de subdiretórios para esse nó
284: SubDirs := Directory.GetDirectories(IncludeBackslash(Node.FullPath));
285: // Apara a árvore nesse ponto e adiciona filhos diretos
286: Node.Nodes.Clear;
287: // Adiciona cada subdiretório à árvore
288: for I := 0 to Length(SubDirs) - 1 do
289: AddTreeNode(Node, Path.GetFileName(SubDirs[I]));
290: end;
291:
292: procedure ExplorerViewer.AddTreeNode(Parent: TreeNode; NodeName: string);
293: var
294: Nodes: TreeNodeCollection;
295: NewNode: TreeNode;
296: begin
297: if Parent = nil then
298: Nodes := TreeView.Nodes
299: else
300: Nodes := Parent.Nodes;
301: NewNode := Nodes.Add(ExcludeBackslash(NodeName));
302: NewNode.Nodes.Add(''); // adiciona nó "falso" para fazer com que o sinal de adição
apareça
303: end;
304:
305: function ExplorerViewer.ExcludeBackslash(APath: string): string;
306: begin
307: if APath.EndsWith('\') then
322 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.9 Continuação


308: Result := APath.SubString(0, APath.Length - 1)
309: else
310: Result := APath;
311: end;
312:
313: function ExplorerViewer.IncludeBackslash(APath: string): string;
314: begin
315: if not APath.EndsWith('\') then
316: Result := APath + '\'
317: else
318: Result := APath;
319: end;
320:
321: procedure ExplorerViewer.DisposeIcons;
322: var
323: I: Integer;
324: begin
325: for I := 0 to FIconList.Count - 1 do
326: begin
327: if FIconlist[I] < > nil then
328: (FIconList[I] as System.Drawing.Icon).Dispose;
329: end;
330: FIconList.Clear;
331: end;
332:
333: procedure ExplorerViewer.OnFileActivated(Args: ExplorerViewerEventArgs);
334: begin
335: if Assigned(FFileActivated) then
336: FFileActivated(Self, Args);
337: end;
338:
339: procedure ExplorerViewer.ActivateFile(FileName: string);
340: var
341: Proc: System.Diagnostics.Process;
342: begin
343: Proc := System.Diagnostics.Process.Create;
344: try
345: Proc.StartInfo.FileName := FileName;
346: Proc.StartInfo.UseShellExecute := True;
347: Proc.StartInfo.ErrorDialog := True;
348: Proc.Start;
349: finally
350: Proc.Dispose;
351: end;
352: end;
353:
354: end.

u Localize o código no CD: \Code\Chapter 13.


Componentes de exemplo 323

A lógica básica desse componente é que o painel esquerdo é preenchido com uma vi-
sualização de diretório, enquanto o painel direito contém arquivos dentro desse diretó-
rio. Como seria muito demorado atravessar o disco rígido inteiro a fim de analisar a es-
trutura de diretórios no painel esquerdo, a árvore só é construída com um subdiretório
por vez, à medida que o usuário clica em cada nó sucessivo. O preenchimento inicial de
TreeView é realizado no método FillTreeView( ), mostrado nas linhas 263–274. A chave
para esse método é a chamada para o método System.IO.Directory.GetLogicalDrives( ) da
FCL, que retorna um array de strings – cada um representando uma unidade lógica no
sistema. Depois que o array é obtido, o código itera por esse array, adicionando um ele-
mento por vez como um nó-raiz da árvore.
Quando ocorre uma tentativa de expandir um nó em TreeView, seu evento BeforeEx-
pand é disparado, executando o ouvinte localizado nas linhas 163–168 da Listagem 13.9.
Esse código faz verificações para certificar-se de que o diretório ainda não foi processado
e chama o método RefreshNode( ) para processar o subdiretório, mostrado nas linhas
276–290.
Outro método útil de System.IO.Directory, GetDirectories( ), é utilizado em RefreshNo-
de( ) para obter uma lista de subdiretórios no diretório identificado pelo nó atual na ár-
vore. Uma vez que o array é obtido, ele é atravessado para preencher subnós no TreeView.
Depois que TreeView foi ajustado, selecionar um nó também faz com que o método
FillListView( ) (linhas 234–261) seja chamado, preenchendo ListView com os arquivos do
diretório selecionado.
Observe que o método FillListView( ) utiliza a função ExtractIcon( ) local que empa-
cota a função SHGetFileInfo( ) da Win32 API a fim de extrair um ícone a partir de um ar-
quivo ou do shell. É necessário chamar essa função da Win32 API, pois não há nenhum
invólucro disponível para essa funcionalidade na FCL. Essa função e seu código de su-
porte são mostrados nas linhas 92–122 da Listagem 13.9.
A função ExtractIcon( ) extrai um handle de ícone Win32 e o converte em um objeto
Icon .NET, utilizado na ListView para representar o arquivo. Você observará que a função
SHGetFileInfo( ) é importada diretamente da Win32 API. Para utilizar essa função, a estru-
tura SHFILEINFO e o tipo enumerado SHGFI também são adicionados porque esses tipos são
utilizados pela função. A mágica ocorre com o atributo DllImport, que é vinculado direta-
mente à função da Win32 API não-gerenciada.
Para completar o controle, permitimos que o usuário execute o arquivo dando um
clique duplo em um arquivo na ListView. Isso faz com que o método ActivateFile( ) seja
chamado, o qual utiliza o objeto System.Diagnosis.Process para carregar um novo processo
no arquivo:
procedure ExplorerViewer.ActivateFile(FileName: string);
var
Proc: System.Diagnostics.Process;
begin
Proc := System.Diagnostics.Process.Create;
try
Proc.StartInfo.FileName := FileName;
Proc.StartInfo.UseShellExecute := True;
Proc.StartInfo.ErrorDialog := True;
324 Capítulo 13 Desenvolvendo controles WinForms personalizados

Proc.Start;
finally
Proc.Dispose;
end;
end;

A Figura 13.5 mostra o controle ExplorerViewer em ação em tempo de execução.

FIGURA 13.5 O ExplorerViewer visto em tempo de execução.

SimpleStatusBars: utilização de Extender Providers


Um recurso particularmente útil e “legal” dos componentes WinForms são os extender
providers (provedores de extensão). Os extender providers permitem que um compo-
nente apareça para adicionar propriedades a outros componentes. Por exemplo, consi-
dere o componente System.Windows.Forms.ToolTip. Ao colocar esse componente em um for-
mulário, ele parece adicionar uma propriedade de string ToolTip a todos os controles no
formulário. Na verdade, a propriedade ToolTip não é adicionada a outros formulários, en-
tretanto, em vez disso, o componente ToolTip gerencia uma lista de componentes com
uma propriedade ToolTip e o valor da propriedade correspondente.
Com apenas um pouco de trabalho, é possível implementar esse tipo de recurso nos
seus próprios componentes. A chave para fazer isso são duas coisas: o atributo ProvidePro-
perty e a interface System.ComponentModel.IExtenderProvider.

ProvidePropertyAttribute
Você deve utilizar o atributo ProvideProperty na classe na qual deseja estender outros com-
ponentes adicionando propriedades. A classe ProvidePropertyAttribute tem dois constru-
tores:

constructor Create(PropertyName, ReceiverTypeName: string); overload;


constructor Create(PropertyName: string; ReceiverType: System.Type); overload;
Componentes de exemplo 325

Os dois construtores aceitam um nome de propriedade como o primeiro parâmetro.


O segundo parâmetro permite uma string que descreve um nome de tipo, no primeiro
caso, ou uma instância de tipo real, no segundo. Esse parâmetro identifica as classes que
devem ser estendidas com a propriedade.

ATENÇÃO
Nas versões anteriores do Delphi 8, não é possível utilizar o construtor que aceita um System.Type
por causa de um bug no compilador. Utilize o construtor de string se precisar manter compatibili-
dade com todas as versões do compilador Delphi 8. Esse bug foi corrigido no Delphi 8 Update 2.

O código de atributo para adicionar uma propriedade chamada StatusText a todos os


outros componentes se pareceria a algo assim:

[ProvideProperty('StatusText', 'System.Object')]

A combinação secreta que conecta a string do nome de propriedade utilizada no cons-


trutor de atributo com o código real em sua classe é uma convenção simples: crie métodos
na sua classe chamados GetNomeDaPropriedade( ) e SetNomeDaPropriedade( ). Estes serão localiza-
dos pela FCL por meio de Reflection e utilizados para obter e configurar a propriedade.

DICA
Observe que a string 'System.Object' é passada como a classe a ser estendida no exemplo anterior.
Isso é um artefato do bug de compilador supracitado, que impede o uso do construtor que recebe
um Type. Classes diferentes de Object exigem o uso da string contendo o nome completamente
qualificado, o que é inconveniente e incompatível com o controle de versão. O único efeito nega-
tivo de passar Object é que IExtenderProvider.CanExtend( ) (descrito a seguir) será chamado
um pouco mais freqüentemente.

IExtenderProvider
IExtenderProvider é definida como

type
IExtenderProvider = interface
function CanExtend(extendee: TObject): Boolean;
end;

Você também precisa implementar essa interface simples no componente que dese-
ja converter em o extender provider. Se o atributo ProvideProperty estiver presente, o fra-
mework da FCL irá procurar IExtenderProvider e chamará CanExtend( ) em todas as classes
que atendem à especificação de classe fornecida no segundo parâmetro para o construtor
ProvidePropertyAttribute. Por exemplo, para o controle SimpleStatusBar, que será descrito
em breve, a implementação de CanExtend( ) se parece a:
function SimpleStatusBar.CanExtend(extendee: TObject): Boolean;
begin
Result := (extendee is Control) and (not (extendee is SimpleStatusBar));
end;
326 Capítulo 13 Desenvolvendo controles WinForms personalizados

Essa implementação de CanExtend( ) retorna True para qualquer Control que não for
um SimpleStatusBar.

O controle SimpleStatusBar
SimpleStatusBar é um Control que descende de System.Windows.Forms.StatusBar, com uma varia-
ção: ele adiciona uma propriedade StatusText a outros controles no formulário e monitora
a alteração de foco destes, configurando seu próprio texto para ser igual à propriedade
StatusText do controle focalizado.
O código-fonte para SimpleStatusBar é mostrado na Listagem 13.10.

LISTAGEM 13.10 O código-fonte de SimpleStatusBar


1: unit d8dg.SimpleStatus;
2:
3: interface
4:
5: uses System.Windows.Forms, System.ComponentModel, System.Collections,
6: System.Windows.Forms.Design, System.ComponentModel.Design;
7:
8: type
9: [ProvideProperty('StatusText', 'System.Object')]
10: SimpleStatusBar = class(System.Windows.Forms.StatusBar,
11: IExtenderProvider)
12: private
13: FStatusTexts: HashTable;
14: FActiveControl: Control;
15: procedure HandleControlEnter(Sender: TObject; e: EventArgs);
16: procedure HandleControlLeave(Sender: TObject; e: EventArgs);
17: strict protected
18: procedure Dispose(disposing: Boolean); override;
19: public
20: constructor Create;
21: function CanExtend(extendee: TObject): Boolean;
22: function GetStatusText(Ctl: TObject): string;
23: procedure SetStatusText(Ctl: TObject; Value: string);
24: end;
25:
26: implementation
27:
28: { SimpleStatusBar }
29:
30: function SimpleStatusBar.CanExtend(extendee: TObject): Boolean;
31: begin
32: Result := (extendee is Control) and (not (extendee is SimpleStatusBar));
33: end;
34:
35: constructor SimpleStatusBar.Create;
36: begin
Componentes de exemplo 327

LISTAGEM 13.10 Continuação


37: inherited;
38: FStatusTexts := HashTable.Create;
39: end;
40:
41: procedure SimpleStatusBar.Dispose(disposing: Boolean);
42: var
43: I, Size: Integer;
44: A: array of TObject;
45: begin
46: if disposing then
47: begin
48: // certifique-se de que todos os handlers de evento estão desconectados
49: Size := FStatusTexts.Count;
50: SetLength(A, Size);
51: FStatusTexts.Keys.CopyTo(A, 0);
52: for I := 0 to Size - 1 do
53: SetStatusText(A[I] as Control, '');
54: end;
55: inherited;
56: end;
57:
58: function SimpleStatusBar.GetStatusText(Ctl: TObject): string;
59: begin
60: Result := string(FStatusTexts[Ctl as System.Windows.Forms.Control]);
61: if Result = nil then
62: Result := '';
63: end;
64:
65: procedure SimpleStatusBar.HandleControlEnter(Sender: TObject;
66: e: EventArgs);
67: begin
68: FActiveControl := Sender as Control;
69: Self.Text := GetStatusText(FActiveControl);
70: end;
71:
72: procedure SimpleStatusBar.HandleControlLeave(Sender: TObject;
73: e: EventArgs);
74: begin
75: if Sender = FActiveControl then
76: begin
77: FActiveControl := nil;
78: Self.Text := '';
79: end;
80: end;
81:
82: procedure SimpleStatusBar.SetStatusText(Ctl: TObject; Value: string);
83: var
84: C: Control;
328 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.10 Continuação


85: begin
86: C := Ctl as System.Windows.Forms.Control;
87: if Value = nil then
88: Value := '';
89: if Value.Length = 0 then
90: begin
91: FStatusTexts.Remove(C);
92: Exclude(C.Enter, HandleControlEnter);
93: Exclude(C.Leave, HandleControlLeave);
94: end
95: else begin
96: FStatusTexts[Ctl] := Value;
97: Include(C.Enter, HandleControlEnter);
98: Include(C.Leave, HandleControlLeave);
99: end;
100: if C = FActiveControl then
101: Text := Value;
102: end;
103:
104: end.

u Localize o código no CD: \Code\Chapter 13.

SimpleStatusBar utiliza uma HashTable para armazenar os valores de StatusText do com-


ponente individual e as tarefas mais complexas para toda a operação ocorrem no método
SetStatusText( ) mostrado nas linhas 82–102. Esse método é responsável por adicionar e
remover pares classe/valor da tabela de hash e também ouvintes para os eventos Enter e
Leave do controle. SimpleStatusBar utiliza esses eventos para detectar o momento em que o
foco muda de um controle para outro. Quando muda, ele tenta pesquisar o controle na
tabela de hash, e, se encontrado, configura a propriedade Text apropriadamente.
A Figura 13.6 mostra o controle SimpleStatusBar em ação em tempo de projeto, com a
propriedade StatusText adicionada a um botão. A Figura 13.7 mostra o SimpleStatusBar em
operação em tempo de execução.

Pintura de usuário: o controle PlayingCard


Discutimos vários controles que derivam sua aparência e comportamento dos compo-
nentes existentes. Entretanto, e se um controle existente não for adequado às necessida-
des da sua interface com o usuário? É aí que entram os controles de usuário pintados
(painted). Os controles de usuário pintados permitem ter controle total sobre como eles
são renderizados na tela.
Para demonstrar isso, iremos investigar a implementação de um controle WinForm
de jogos de cartas. A carta pintará o naipe e valor na face da carta e uma cor e imagem que
o usuário escolher no verso.
Pintura de usuário: o controle PlayingCard 329

FIGURA 13.6 SimpleStatusBar realiza seu bom trabalho no designer.

FIGURA 13.7 SimpleStatusBar realizando seu trabalho em tempo de execução.

Para iniciar, definiremos alguns enums para representar naipes e valores:


type
CardSuit = (CSClub, CSDiamond, CSHeart, CSSpade);
CardValue = (CVAce, CVTwo, CVThree, CVFour, CVFive, CVSix, CVSeven,
CVEight, CVNine, CVTen, CVJack, CVQueen, CVKing);

Vamos agora examinar a declaração de classe:


PlayingCard = class(System.Windows.Forms.Control)
private
class var
FDrawBrush: SolidBrush;
FBrushRef: Integer;
private
FBackPen: Pen;
FBorderPen: Pen;
FSymbolFont: Font;
FSansSerifFont: Font;
330 Capítulo 13 Desenvolvendo controles WinForms personalizados

FCardBackBrush: SolidBrush;
FSuit: CardSuit;
FValue: CardValue;
FFaceUp: Boolean;
FBorderWidth: Integer;
procedure SetSuit(Value: CardSuit);
procedure SetValue(Value: CardValue);
procedure SetFaceUp(Value: Boolean);
procedure SetBorderWidth(Value: Integer);
strict protected
procedure InitComp;
procedure OnPaint(e: PaintEventArgs); override;
function get_DefaultSize: Size; override;
property BorderWidth: Integer read FBorderWidth write SetBorderWidth;
public
constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer); overload;
published
[Description('The suit of the card'), Category('Appearance')]
property Suit: CardSuit read FSuit write SetSuit;
[Description('The face value of the card'), Category('Appearance')]
property Value: CardValue read FValue write SetValue;
[Description('Whether the card is face up or down'), Category('Appearance')]
property FaceUp: Boolean read FFaceUp write SetFaceUp;
end;

Essa declaração mostra as três propriedades publicadas do controle – Suit, Value e


FaceUp – que controlam o naipe, o valor e se a carta do baralho está virada para cima ou
para baixo.

NOTA
O atributo Description, utilizado por cada propriedade anterior, permite especificar uma descri-
ção que será exibida no Object Inspector quando a propriedade for selecionada.
O atributo Category permite especificar a qual categoria no Object Inspector a propriedade per-
tence.

Também de interesse são os métodos strict protected InitComp( )e OnPaint( ). Init-


Comp( ) é chamado pelos construtores para realizar a inicialização do controle. OnPaint( )
realiza toda a pintura do controle.

DICA
Sobrescreva o método get_DefaultSize( ) de um controle para alterar o tamanho inicial do con-
trole da maneira como foi inicialmente criado ou colocado em um formulário no designer.
Pintura de usuário: o controle PlayingCard 331

A seção privada contém alguns objetos GDI+ utilizados na renderização, bem como
acessores de propriedade e campos privados para armazenar os valores da propriedade.
O código-fonte de PlayingCard é mostrado na Listagem 13.11.

LISTAGEM 13.11 O código-fonte de PlayingCard


1: unit d8dg.Cards;
2:
3: interface
4:
5: uses
6: System.Drawing, System.Collections, System.ComponentModel,
7: System.Windows.Forms, System.Threading;
8:
9: type
10: CardSuit = (CSClub, CSDiamond, CSHeart, CSSpade);
11:
12: CardValue = (CVAce, CVTwo, CVThree, CVFour, CVFive, CVSix, CVSeven,
13: CVEight, CVNine, CVTen, CVJack, CVQueen, CVKing);
14:
15: /// <resumo>
16: /// Representa uma única carta, com o naipe e valor.
17: /// </resumo>
18: [Serializable]
19: Card = record
20: cSuit: CardSuit;
21: cValue: CardValue;
22: end;
23:
24: /// <resumo>
25: /// Classe de exceção Card
26: /// </resumo>
27: [Serializable]
28: ECardException = class(Exception)
29: end;
30:
31: [ToolboxItem(True)]
32: PlayingCard = class(System.Windows.Forms.Control)
33: strict protected
34: /// <resumo>
35: /// Limpa quaisquer recursos sendo utilizados.
36: /// </resumo>
37: procedure Dispose(Disposing: Boolean); override;
38: private
39: class var
40: FDrawBrush: SolidBrush;
41: FBrushRef: Integer;
42: private
43: FBackPen: Pen;
332 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.11 Continuação


44: FBorderPen: Pen;
45: FSymbolFont: Font;
46: FSansSerifFont: Font;
47: FCardBackBrush: SolidBrush;
48: FSuit: CardSuit;
49: FValue: CardValue;
50: FFaceUp: Boolean;
51: FBorderWidth: Integer;
52: procedure SetSuit(Value: CardSuit);
53: procedure SetValue(Value: CardValue);
54: procedure SetFaceUp(Value: Boolean);
55: procedure SetBorderWidth(Value: Integer);
56: strict protected
57: procedure InitComp;
58: procedure OnPaint(e: PaintEventArgs); override;
59: function get_DefaultSize: Size; override;
60: property BorderWidth: Integer read FBorderWidth write SetBorderWidth;
61: public
62: constructor Create; overload;
63: constructor Create(Container: System.ComponentModel.IContainer);
64: overload;
65: published
66: [Description('The suit of the card'), Category('Appearance')]
67: property Suit: CardSuit read FSuit write SetSuit;
68: [Description('The face value of the card'), Category('Appearance')]
69: property Value: CardValue read FValue write SetValue;
70: [Description('Whether the card is face up or down'),
71: Category('Appearance')]
72: property FaceUp: Boolean read FFaceUp write SetFaceUp;
73: end;
74:
75: implementation
76:
77: uses
78: System.Globalization;
79:
80: const
81: SuitArray: array[0..3] of string = (Chr(167), Chr(168), Chr(169),
82: Chr(170));
83: ValueArray: array[0..12] of string = ('A', '2', '3', '4', '5', '6',
84: '7', '8', '9', '10', 'J', 'Q', 'K');
85:
86: procedure PlayingCard.Dispose(Disposing: Boolean);
87: begin
88: if Disposing then
89: begin
90: FBackPen.Dispose;
91: FBorderPen.Dispose;
Pintura de usuário: o controle PlayingCard 333

LISTAGEM 13.11 Continuação


92: FCardBackBrush.Dispose;
93: FSymbolFont.Dispose;
94: FSansSerifFont.Dispose;
95: if System.Threading.Interlocked.Decrement(FBrushRef) = 0 then
96: begin
97: FDrawBrush.Dispose( );
98: FDrawBrush := nil;
99: end;
100: if Components < > nil then
101: Components.Dispose( );
102: end;
103: inherited Dispose(Disposing);
104: end;
105:
106: procedure PlayingCard.InitComp;
107: begin
108: FBorderWidth := 4;
109: FFaceUp := True;
110: SetStyle(ControlStyles.UserPaint, True);
111: SetStyle(ControlStyles.AllPaintingInWmPaint, True);
112: SetStyle(ControlStyles.DoubleBuffer, True);
113: SetStyle(ControlStyles.ResizeRedraw, True);
114: SetStyle(ControlStyles.StandardClick, True);
115: SetStyle(ControlStyles.StandardDoubleClick, True);
116: SetStyle(ControlStyles.Opaque, True);
117: FBackPen := Pen.Create(BackColor, FBorderWidth);
118: FBorderPen := Pen.Create(Color.FromKnownColor(KnownColor.Black), 1);
119: FCardBackBrush := SolidBrush.Create(ForeColor);
120: FSymbolFont := System.Drawing.Font.Create('Symbol', 36);
121: FSansSerifFont := System.Drawing.Font.Create('Arial', 24);
122: if System.Threading.Interlocked.Increment(FBrushRef) = 1 then
123: FDrawBrush := SolidBrush.Create(Color.FromKnownColor(KnownColor.Red));
124: System.Threading.Interlocked.Increment(FBrushRef);
125: ForeColor := Color.FromKnownColor(KnownColor.White);
126: BackColor := Color.FromKnownColor(KnownColor.Gray);
127: end;
128:
129: procedure PlayingCard.OnPaint(e: PaintEventArgs);
130: var
131: penWidth: Double;
132: rect: Rectangle;
133: fmt: StringFormat;
134: begin
135: rect := ClientRectangle;
136: FDrawBrush.Color := Color.FromKnownColor(KnownColor.White);
137: e.Graphics.FillRectangle(FDrawBrush, rect);
138: rect.Inflate(FBorderWidth * -1, FBorderWidth * -1);
139: if FFaceUp then // carta virada para cima
334 Capítulo 13 Desenvolvendo controles WinForms personalizados

LISTAGEM 13.11 Continuação


140: begin
141: // obtém a cor a partir do naipe
142: if (FSuit = CardSuit.CSDiamond) or (FSuit = CardSuit.CSHeart) then
143: FDrawBrush.Color := Color.FromKnownColor(KnownColor.Red)
144: else
145: FDrawBrush.Color := Color.FromKnownColor(KnownColor.Black);
146: fmt := StringFormat.Create;
147: // Desenha um caractere do naipe
148: fmt.Alignment := StringAlignment.Center;
149: fmt.LineAlignment := StringAlignment.Center;
150: e.Graphics.DrawString(SuitArray[Ord(FSuit)], FSymbolFont,
151: FDrawBrush, rect, fmt);
152: // Desenha o valor superior/esquerdo
153: fmt.Alignment := StringAlignment.Near;
154: fmt.LineAlignment := StringAlignment.Near;
155: e.Graphics.DrawString(ValueArray[Ord(FValue)], FSansSerifFont,
156: FDrawBrush, rect, fmt);
157: // Desenha o valor inferior/direito
158: fmt.Alignment := StringAlignment.Far;
159: fmt.LineAlignment := StringAlignment.Far;
160: e.Graphics.DrawString(ValueArray[Ord(FValue)], FSansSerifFont,
161: FDrawBrush, rect, fmt);
162: end
163: else begin // carta virada para baixo
164: // desenha a imagem de fundo
165: if BackgroundImage < > nil then
166: e.Graphics.DrawImage(BackgroundImage,
167: (Width - BackgroundImage.Width) div 2,
168: (Height - BackgroundImage.Height) div 2);
169: // desenha a borda inset
170: FBackPen.Color := BackColor;
171: e.Graphics.DrawRectangle(FBackPen, rect);
172: end;
173: // desenha a borda externa
174: penWidth := FBorderPen.Width;
175: e.Graphics.DrawRectangle(FBorderPen, 0, 0, Width - penWidth,
176: Height - penWidth);
177: end;
178:
179: function PlayingCard.get_DefaultSize: Size;
180: begin
181: Result := System.Drawing.Size.Create(90, 120);
182: end;
183:
184: procedure PlayingCard.SetBorderWidth(Value: Integer);
185: begin
186: if FBorderWidth < > Value then
187: begin
Pintura de usuário: o controle PlayingCard 335

LISTAGEM 13.11 Continuação


188: FBorderWidth := Value;
189: Invalidate( );
190: end;
191: end;
192:
193: procedure PlayingCard.SetSuit(Value: CardSuit);
194: begin
195: if not Enum.IsDefined(TypeOf(CardSuit), Suit) then
196: raise ArgumentOutOfRangeException.Create;
197: if FSuit < > Value then
198: begin
199: FSuit := Value;
200: Invalidate;
201: end;
202: end;
203:
204: procedure PlayingCard.SetValue(Value: CardValue);
205: begin
206: if not Enum.IsDefined(TypeOf(CardValue), Value) then
207: raise ArgumentOutOfRangeException.Create;
208: if FValue < > Value then
209: begin
210: FValue := Value;
211: Invalidate;
212: end;
213: end;
214:
215: procedure PlayingCard.SetFaceUp(Value: Boolean);
216: begin
217: if FFaceUp < > Value then
218: begin
219: FFaceUp := Value;
220: Invalidate;
221: end;
222: end;
223:
224: end.

u Localize o código no CD: \Code\Chapter 13.

InitComp( ) começa na linha 106 da Listagem 13.11. Esse método é chamado pelos
construtores e trata a inicialização apropriada do componente. Em particular, ele utiliza
o método SetStyle( ) herdado de Control para configurar diversos estilos de controle. Esse
componente configura os estilos necessários para realizar a pintura otimizada de usuário
e trata de cliques do mouse. A Tabela 13.3 detalha todos os valores do tipo enumerado
ControlStyles que podem ser utilizados com SetStyle( ).
336 Capítulo 13 Desenvolvendo controles WinForms personalizados

TABELA 13.3 Valores do tipo enumerado ControlStyles


Nome Significado
AllPaintingInWmPaint O controle irá ignorar a mensagem do Windows WM_ERASEBKGND, o
que talvez reduza a oscilação. Esse estilo só pode ser utilizado em
combinação com o estilo UserPaint.
CacheText Para eficiência, o controle mantém sua própria cópia do valor da sua
propriedade Text em vez de obtê-la do handle da janela subjacente
toda vez que é necessária. Você precisará ter cuidado e manter o
valor da propriedade sincronizado com o controle subjacente ao
utilizar esse estilo.
ContainerControl O controle é do tipo contêiner capaz de conter os controles filhos.
DoubleBuffer Para impedir oscilação, o desenho é realizado em um buffer na
memória e a saída na tela é gerada em volume depois de completar.
Para completamente permitir um buffer duplo, você também deve
incluir o estilo UserPaint e AllPaintingInWmPaint.
EnableNotifyMessage O método OnNotifyMessage será chamado para cada mensagem
enviada para a procedure na janela do controle.
FixedHeight O controle tem uma largura fixa quando auto-dimensionável. Por
exemplo, se uma operação de layout tentar redimensionar o controle
a fim de acomodar uma nova fonte, a largura do controle
permanecerá inalterada.
FixedHeight O controle tem uma altura fixa quando auto-dimensionável. Por
exemplo, se uma operação de layout tentar redimensionar o controle
a fim de acomodar uma nova fonte, a altura do controle
permanecerá inalterada.
Opaque O fundo do controle não é pintado, tornando-o opaco.
ResizeRedraw O controle será redesenhado quando for redimensionado.
Selectable O controle pode receber foco.
StandardClick O controle implementa o comportamento padrão de clique do
mouse.
StandardDoubleClick O controle implementa o comportamento padrão de clique duplo
do mouse. Esse estilo deve ser utilizado em conjunção com o estilo
StandardClick.
SupportsTransparentBackColor O controle aceita um BackColor com um componente alfa menor
que 255 para simular transparência. A transparência será simulada
somente se o estilo UserPaint estiver incluído e o controle pai
descender de System.Windows.Forms.Control.
UserMouse O controle faz seu próprio processamento de mouse e os eventos de
mouse não serão tratados pelo sistema operacional.
UserPaint O controle pinta a si próprio em vez de esperar que o sistema
operacional faça isso.

Canetas, pincéis e fontes GDI+ também são criados no método InitComp( ). Criando
esses objetos antecipadamente, o componente não sofre nenhum impacto de eficiência
que poderia sofrer se criasse cada um desses objetos todas as vezes que uma pintura fosse
necessária. Observe especialmente a proteção contra acesso simultâneo em threads em
torno da criação de FDrawBrush. Como FDrawBrush é uma variável class var, ela é comparti-
Pintura de usuário: o controle PlayingCard 337

lhada entre todas as instâncias dessa classe; assim, referenciamos a contagem desse pin-
cel utilizando FBrushRef e cuidadosamente protegemos a criação e destruição contra aces-
so simultâneo em threads.
O método OnPaint( ) é localizado iniciando na linha 129 da Listagem 13.11. O losan-
go da carta é primeiro pintado em branco. Se a carta estiver virada para cima, o caractere
e o valor do naipe serão desenhados nos locais apropriados na carta do baralho. Se a carta
estiver virada para baixo, a imagem fornecida pela propriedade BackgroundImage será dese-
nhada na carta.

DICA
Em vez de utilizar uma imagem personalizada, os símbolos de naipe são desenhados utilizando a
fonte de símbolos do Windows. Como a fonte de símbolos está disponível em todas as versões do
Windows, suas muitas imagens podem oferecer atalhos úteis ao pintar o controle personalizado.

A Figura 13.8 mostra uma aplicação contendo cartas de baralho viradas para cima e
para baixo.

FIGURA 13.8 Uma excelente combinação de sete cartas .


NESTE CAPÍTULO
— Processos CAPÍTULO 14
— Threads

— Threads no estilo .NET Threading no Delphi


— AppDomain

— O namespace
for .NET
System.Threading
por Rick Ross
— Escrevendo código
thread-safe no estilo .NET
— Questões da interface com o
A plicações que parecem não responder à interação do
usuário são vistas como mal escritas. Seja ao esperar pela
usuário
conclusão de um relatório extenso ou ao imprimir um
— Exceções em threads documento de 100 páginas, as aplicações devem
— Garbage Collection e responder à entrada de usuário. Felizmente, escrever
threading aplicações responsivas não é uma tarefa difícil contanto
que certos princípios sejam entendidos.
Este capítulo fornece os blocos de construção para
escrever aplicações que respondem à entrada de usuário
durante tarefas demoradas. Além disso, esses mesmos
conceitos são aplicáveis a outras aplicações como NT
Services, Application Servers e aplicações Internet.

Processos
Um processo é criado quando uma aplicação é iniciada.
Esse processo contém um ponteiro de instruções que
monitora o local atualmente executado. Além do código
executável, um processo contém espaço de endereço
virtual, espaço de memória e numerosos registradores
de CPU.
O espaço de endereço virtual contém um conjunto
lógico de endereços válidos em um processo. O espaço
de memória contém os dados do processo global – a
pilha em que as variáveis locais são armazenadas, o heap
em que a memória é dinamicamente alocada e o
conjunto de páginas utilizado para mapear endereços
virtuais para a memória física.
Esses processos têm três estados únicos: em
execução, parados ou bloqueados. Processos parados são
aqueles em depuração enquanto processos bloqueados
esperam que o sistema operacional execute-os. Cada
processo é tratado como uma entidade isolada agendada
pelo sistema operacional.
Threads 339

Como o sistema operacional impede que os processos afetem diretamente um ao ou-


tro, a comunicação entre dois ou mais processos precisa de um protocolo predetermina-
do. Coletivamente, os protocolos utilizados para comunicação entre múltiplos processos
são chamados Interprocess Communications (IPC). A Figura 14.1 ilustra múltiplos pro-
cessos que se comunicam entre si.

Processo Mecanismo Processo


“A” de IPC “B”

Mecanismo Mecanismo
de IPC de IPC

Processo Processo
“C” “D”

FIGURA 14.1 Comunicação entre múltiplos processos.

O sistema operacional Windows (NT e superiores) apresenta várias opções de meca-


nismos IPC. Estes incluem
— Pipes identificados
— Memória compartilhada

— Mutexes

— Eventos
— Semáforos

— Soquetes TCP/IP

Pesado (Heavyweight) é um termo associado aos processos porque eles utilizam re-
cursos intensamente. Iniciar e parar um processo é relativamente mais lento do que ou-
tras alternativas, discutidas na próxima seção. Apesar da necessidade de um maior nú-
mero de recursos, o nível de proteção oferecido pelo sistema operacional para processos é
inigualável.

Threads
O threading supera muitas desvantagens da utilização de processos para realizar proces-
samento em segundo plano. Ele permite que múltiplos caminhos independentes dentro
de um processo sejam executados simultaneamente. Cada caminho de execução é cha-
mado thread.
340 Capítulo 14 Threading no Delphi for .NET

NOTA
Tecnicamente, esses caminhos independentes só podem executar simultaneamente em uma má-
quina com múltiplos processadores. Máquinas de um único processador alternam entre esses ca-
minhos independentes rapidamente, dando a impressão de uma execução simultânea.

A maioria dos processos só tem um único caminho de execução – um processo de


uma única thread. Os processos que contêm múltiplos caminhos são chamados de mul-
tithreads. A Figura 14.2 ilustra processos de uma única thread e de múltiplas threads.
Cada thread em um processo tem seu próprio ponteiro de instruções e registradores
de CPU. Todas as threads compartilham o mesmo espaço de endereço virtual que é possuí-
do pelo processo que os contém. Cada thread, porém, recebe seu próprio espaço na pilha.
Como todas as threads dentro de um processo compartilham o mesmo espaço de
endereçamento, a comunicação entre threads é trivial. Embora facilmente realizável,
compartilhar espaço de endereçamento requer um projeto diligente. Este tópico será dis-
cutido mais adiante neste capítulo.
O sistema operacional dá uma fração de tempo do processador para cada thread. Uti-
lizando um algoritmo complexo, o sistema operacional examina a prioridade da thread e
se ela está esperando que algo ocorra. Depois que uma thread bloqueou ou utilizou sua
fração de tempo, o scheduler procura uma outra thread a executar.
Leve (Lightweight ) é um termo associado a uma thread porque requer menos recur-
sos do que os processos. As threads iniciam e param muito mais rapidamente que os pro-
cessos.

Threads no estilo .NET


Como esperado, o .NET Framework também tem o conceito de threading bem posicio-
nado dentro do conceito orientado a objetos e da neutralidade em relação a plataformas.
Encapsulando e abstraindo threads, o framework expõe uma thread lógica. Essas threads
lógicas são gerenciadas pelo framework e fornecem benefícios adicionais não encontra-
dos em uma thread Win32 física. Utilizar threads .NET deve tornar o código muito mais
portável para outras plataformas CLI como WinCE, Win64 e Mono. No momento em
que escrevíamos este livro, uma thread lógica mapeava para uma thread física, mas isso
talvez mude em versões futuras do .NET.

Processo “A” Processo “B”


Thread única de execução Múltiplas threads de execução

FIGURA 14.2 Processos de thread única e de múltiplas threads.


AppDomain 341

Uma thread lógica é capaz de fazer coisas que uma thread nativa não pode. Por
exemplo, não há nenhum método simples para que uma thread nativa levante uma ex-
ceção em uma outra thread. Threads lógicas, porém, podem levantar uma exceção em
uma outra thread chamando o método Thread.Abort( ). As exceções são discutidas na se-
ção “Exceções de threads”.
Threads lógicas no .NET são agendadas pelo CLR e executam no contexto de um
AppDomain.

AppDomain
Semelhante a um processo, um AppDomain fornece uma sandbox (caixa de areia) segura
para executáveis e assemblies .NET. Da mesma maneira como os processos são protegi-
dos, múltiplos assemblies .NET são protegidos se residirem em AppDomains separados.
Uma distinção importante é que AppDomains não necessariamente precisam residir em
processos separados. Múltiplos AppDomains podem ocupar, e freqüentemente ocupam,
o mesmo processo Win32.
Quando uma aplicação .NET é carregada, o CLR cria um processo. Por sua vez, esse
processo cria o primeiro AppDomain, que é chamado o AppDomain default. O AppDo-
main default não pode ser descarregado e é destruído quando o processo termina.
AppDomains adicionais podem ser criados dinamicamente em tempo de execução. O re-
lacionamento entre processos e AppDomains é ilustrado na Figura 14.3

Processo D4DN.exe Processo D4DN2.exe

Assemblies de domínio neutro

Assemblies de domínio neutro

FIGURA 14.3 O relacionamento entre processos e Appdomains.


342 Capítulo 14 Threading no Delphi for .NET

Como ocorre com múltiplos processos, se um assembly precisar se comunicar com


um outro assembly localizado em um AppDomain diferente, será necessário algum me-
canismo IPC como o .NET Remoting. Diferentemente de múltiplos processos, a distin-
ção importante entre AppDomains e processos Win32 está no fato de que atualmente
AppDomains não podem compartilhar um segmento comum de memória, como as apli-
cações Win32.
Os AppDomains fornecem a capacidade de descarregar assemblies, contanto que
eles não estejam localizados no AppDomain default. Todos os assemblies contidos em
um AppDomain são descarregados quando o AppDomain é descarregado.
Diretivas de configuração e de segurança podem ser aplicadas a um AppDomain
para formar um ambiente mais restrito ou mais relaxado.

O namespace System.Threading
O .NET Framework tem uma rica coleção de classes e tipos enumerados necessários para
escrever aplicações multithread, localizadas no namespace System.Threading.

A classe System.Threading.Thread
Herdando diretamente da classe System.Object, a classe Thread fornece os métodos necessá-
rios para criar, abortar, suspender e retomar threads. Além disso, há várias propriedades
para controlar a prioridade e determinar outras informações úteis. A Listagem 14.1 con-
tém uma definição parcial da classe Thread.

LISTAGEM 14.1 Declaração da classe System.Threading.Thread


System.Threading.Thread = class(System.Object)
public
constructor Create(start: ThreadStart);
procedure Start;
// termina um thread
procedure Abort(stateInfo: System.Object); overload;
procedure Abort; overload;
// cancela uma solicitação de abort
class procedure ResetAbort; static;
procedure Suspend;
procedure Resume;

// desperta uma thread adormecida


procedure Interrupt;

// espera uma thread terminar


procedure Join; overload;
function Join(millisecondsTimeout: integer) : Boolean; overload;
function Join(timeout: TimeSpan) : Boolean; overload;

class procedure Sleep(millisecondsTimeout: Integer); overload; static;


class procedure Sleep(timeout: TimeSpan); overload; static;
O namespace System.Threading 343

LISTAGEM 14.1 Continuação


// força a thread a girar em um loop de acordo com um número dado de iterações
class procedure SpinWait(iterations: Integer); static;

// thread local storage


class function AllocateDataSlot: LocalDataStoreSlot; static;
class function AllocateNamedDataSlot(name: String) : LocalDataStoreSlot;
➥static;
class function GetNamedDataSlot(name: String) : LocalDataStoreSlot; static;
class procedure FreeNamedDataSlot(name: String); static;
class function GetData(slot: LocalDataStoreSlot) : System.Object; static;
class procedure SetData(slot: LocalDataStoreSlot; data: System.Object); static;

class function GetDomain: AppDomain; static;


class function GetDomainID: integer; static;

property Priority: System.Threading.ThreadPriority read; write;


property IsAlive: Boolean read;
property IsThreadPoolThread: Boolean read;
class property CurrentThread: System.Threading.Thread read;
property IsBackground: Boolean read; write;
property ThreadState: System.Threading.ThreadState read;
property ApartmentState: System.Threading.ApartmentState read; write;

// informações de cultura
property CurrentUICulture: System.Globalization.CultureInfo read; write;
property CurrentCulture: System.Globalization.CultureInfo read; write;

// utilizadas com segurança baseada em papéis


property CurrentPrincipal: System.Security.Principal.IPrincipal read; write;

property Name: System.String read; write;


end;

Em particular, uma propriedade que vale ser mencionada é a propriedade Name. Ela só
pode ser gravada uma vez. Qualquer tentativa de gravar na propriedade Name mais de uma
vez resulta em uma exceção.

NOTA
Observe que a classe Thread contém métodos para Suspend( ) (suspender) e Resume( ) (reto-
mar) threads. Suspender threads aleatoriamente não é uma boa idéia porque seria muito fácil
pausar uma thread durante um momento inadequado. Imagine os resultados de suspender uma
thread quando um bloqueio está sendo mantido ou no meio de alguma operação de arquivo. O
ponto principal é que somente a própria thread deve chamar Suspend( ) porque ele conhece os
melhores lugares dentro do código para pausar a thread. Chamar Resume( ) em uma thread
suspensa deve ser feito a partir de uma outra thread.
344 Capítulo 14 Threading no Delphi for .NET

Criar uma thread com a classe Thread é realizado de duas maneiras. A mais freqüente
é utilizar um método de instância de uma classe. Uma outra alternativa é utilizar um mé-
todo estático de classe. Esses dois métodos de criar threads são chamados threads criadas
manualmente.
Independentemente do método utilizado para threads criadas manualmente, am-
bos utilizam delegates. Um delegate é um termo .NET para um método orientado a obje-
tos de retorno que é tipificado (type-safe). Como as handlers de evento no Delphi, os de-
legates fornecem um mecanismo para que múltiplos objetos sejam notificados quando
chamados. As Listagens 14.2 e 14.3 demonstram como os delegates são utilizados no
Delphi ao criar uma thread.

LISTAGEM 14.2 Criando threads com métodos de instância


1: program instancethreads;
2: {$APPTYPE CONSOLE}
3:
4: //
5: // Esse exemplo demonstra como utilizar métodos .NET nativos para criar uma
6: // thread em uma classe com um método de instância.
7: //
8:
9: uses
10: System.Threading;
11:
12: type
13: D4DNInstanceThread = class
14: private
15: FStartNumber : integer;
16: public
17: // ThreadMePlease será executado em um thread diferente
18: procedure ThreadMePlease;
19: property StartNumber : integer write FStartNumber;
20: end;
21:
22: procedure D4DNInstanceThread.ThreadMePlease;
23: var
24: stop : integer;
25: curNum : integer;
26: begin
27: curNum := FStartNumber;
28: stop := FStartNumber + 10;
29: while curNum < stop do
30: begin
31: writeln('Thread ', System.Threading.Thread.CurrentThread.Name,
32: ' current value is ',curNum);
33: inc(curNum);
34: Thread.Sleep(3);
35: end;
O namespace System.Threading 345

LISTAGEM 14.2 Continuação


36: end;
37:
38: var
39: ThreadWork1 : D4DNInstanceThread;
40: ThreadWork2 : D4DNInstanceThread;
41: Thread1 : Thread;
42: Thread2 : Thread;
43: begin
44: writeln('Starting threading instance method example...');
45:
46: // Cria a instância D4DNInstanceThread
47: ThreadWork1 := D4DNInstanceThread.Create;
48: ThreadWork1.StartNumber := 10;
49:
50: // Cria a thread, especificando o método de instância a executar
51: Thread1 := Thread.Create(@ThreadWork1.ThreadMePlease);
52: Thread1.Name := 'one';
53:
54: // Cria uma outra instância de D4DNInstanceThread
55: ThreadWork2 := D4DNInstanceThread.Create;
56: ThreadWork2.StartNumber := 100;
57:
58: // Cria a segunda thread, especificando o método de instância a executar
59: Thread2 := Thread.Create(@ThreadWork2.ThreadMePlease);
60: Thread2.Name := 'two';
61:
62: // Por fim, inicia a thread
63: Thread1.Start;
64: Thread2.Start;
65:
66: // Espera que as duas threads terminem
67: Thread1.Join;
68: Thread2.Join;
69:
70: // Espera que o usuário veja os resultados
71: writeln('Done');
72: readln;
73: end.

u Localize o código no CD: \Code\Chapter 14\Ex01\.

A Listagem 14.2 demonstra como criar uma thread com um método de instância.
Qualquer método de instância sem nenhum parâmetro pode ser executado em uma
thread. Examine a chamada do construtor Thread.Create( ) (mostrada na Listagem 14.2,
linha 51). O parâmetro para o construtor Thread.Create é o endereço do método ThreadMe-
Please. Nos bastidores, o compilador Delphi cria um delegate ThreadStart. Outras lingua-
gens, como C#, exigem mais algumas linhas de código para realizar a mesma tarefa.
346 Capítulo 14 Threading no Delphi for .NET

NOTA
Embora o delegate ThreadStart não permita a passagem de parâmetros, utilizar o método de ins-
tância fornece a oportunidade de utilizar o construtor ou propriedades para passar informações adi-
cionais necessárias para a thread. A Listagem 14.2 demonstra isso configurando o StartNumber.

LISTAGEM 14.3 Criando threads com métodos estáticos


1: program staticthreads;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Threading;
5:
6: type
7: D4DNStaticThread = class
8: public
9: class procedure ThreadMePlease; static;
10: end;
11:
12: class procedure D4DNStaticThread.ThreadMePlease;
13: var
14: stop : integer;
15: curNum : integer;
16: rnd : System.Random;
17: begin
18: rnd := System.Random.Create;
19: curNum := rnd.Next(1000);
20: stop := curNum + 10;
21: while curNum < stop do
22: begin
23: writeln('Thread ', System.Threading.Thread.CurrentThread.Name,
24: 'current value is ', curNum);
25: inc(curNum);
26: // Aleatoriamente fornece fração de tempo para outra thread
27: if rnd.Next(100) < 50 then
28: Thread.Sleep(0);
29: end;
30: end;
31:
32: var
33: thrd1 : Thread;
34: thrd2 : Thread;
35: begin
36: Console.Writeline('Starting static method threading example...');
37:
38: // cria a thread passando o método estático
39: thrd1 := Thread.Create(@D4DNStaticThread.ThreadMePlease);
40: thrd1.Name := 'one';
41:
42: // cria outra thread idêntica
O namespace System.Threading 347

LISTAGEM 14.3 Continuação


43: thrd2 := Thread.create(@D4DNStaticThread.ThreadMePlease);
44: thrd2.Name := 'two';
45:
46: // inicia as duas threads
47: thrd1.Start;
48: thrd2.Start;
49:
50: // espera até as duas threads terminarem
51: thrd1.Join;
52: thrd2.Join;
53:
54: Console.Writeline('Done');
55: readln;
56: end.

u Localize o código no CD: \Code\Chapter 14\Ex02\.

Na Listagem 14.3, uma thread é criada em um método estático de classe. Semelhante


a executar métodos de instância em threads, qualquer método estático sem parâmetros
de classe pode ser executado em uma thread.
As Listagens 14.2 e 14.3 contêm um bug sutil. A saída gravada no console é realizada
de maneira dessincronizada. É bem provável que a saída entre as duas threads seja com-
binada. Isso é causado pela implementação da procedure writeln pelo compilador Delphi
e pela biblioteca de runtime. Cada parâmetro passado para a procedure writeln resulta em
uma chamada separada ao método Console.Write ou Console.WriteLine. Alterar a writeln (li-
nha 31 na Listagem 14.2 e linha 23 na Listagem 14.3) para utilizar Console.WriteLine ou
passar somente um parâmetro para a procedure writeln (por exemplo, utilizar wri-
teln(Format(..));) produzirá o comportamento adequado.

System.Threading.ThreadPriority
Todas as threads criadas manualmente têm uma prioridade associada. Essa prioridade
permite aumentar ou diminuir o agendamento da thread. A Tabela 14.1 lista os valores
possíveis do tipo enumerado ThreadPriority, do mais alto ao mais baixo.

TABELA 14.1 Valores do tipo enumerado System.Threading.ThreadPriority


Valor Descrição
Highest A thread é agendada antes de qualquer outro nível de prioridade de thread.
AboveNormal Agendado antes de qualquer thread com prioridade normal.
Normal Configuração padrão para threads. Agendado antes de threads com prioridade
abaixo do normal.
BelowNormal Agendada antes de quaisquer threads com a prioridade mais baixa.
Lowest A thread é agendada depois de todas as outras threads de prioridade mais alta.
348 Capítulo 14 Threading no Delphi for .NET

A prioridade de uma thread é configurada usando sua propriedade Priority. Tenha


cuidado ao mudar prioridades arbitrariamente porque isso pode tornar difícil depurar
condições como inanição de threads, condições de concorrência e impasses. Nunca utili-
ze uma prioridade mais alta se uma prioridade mais baixa funcionar.
Para aplicações GUIs, a maioria das threads em segundo plano deve ser configurada
como BelowNormal ou Lowest para assegurar uma interface com o usuário responsiva inde-
pendentemente de quanto trabalho as threads em segundo plano realizam.

System.Threading.ThreadState
Depois de criada, uma thread tem um estado que indica o que ela está fazendo. A Tabela
14.2 contém os valores do tipo enumerado ThreadState.

TABELA 14.2 Tipo enumerado System.Threading.ThreadState


Valor Descrição
Running O método start da thread foi chamado.
StopRequested Utilizado apenas internamente, indica que foi solicitado que a thread pare de
executar.
SuspendRequested Foi solicitado que a thread suspenda a si própria
Background Controlado pela propriedade Thread.IsBackground, esse valor indica que a
thread está executando em segundo plano. Threads em segundo plano são
automaticamente abortadas quando a thread principal termina. Somente as
threads em primeiro plano impedirão que uma aplicação feche, desde que
estejam em execução.
Unstarted Uma thread foi criada, mas ainda não foi iniciada.
Stopped A thread não está mais em execução.
WaitSleepJoin A thread não está em execução, pois está bloqueada, repousando, ou
unindo-se (esperando que uma outra thread termine de executar).
Suspended A thread está suspensa.
AbortRequested Foi solicitado que a thread aborte, mas ela ainda não recebeu a
ThreadAbortException.
Aborted Indica que a thread foi terminada por causa de Thread.Abort.

Como uma thread pode estar em múltiplos estados, o tipo enumerado ThreadState
é um conjunto de flags de bits. Porém, nem todas as combinações são válidas. Uma
das combinações válidas é uma thread em um estado WaitSleepJoin e no estado AbortRe-
quested.

O tipo enumerado System.Threading.ApartmentState


O.NET Framework cria um apartment (apartamento) quando interage com objetos
COM. Esse apartment é especificado de duas maneiras. Para criar threads manualmente,
a propriedade ApartmentState pode ser configurada com o tipo enumerado ApartmentState
apropriado. Esses valores estão listados na Tabela 14.3.
O namespace System.Threading 349

TABELA 14.3 Valores do tipo enumerado System.Threading.ApartmentState


Valor Descrição
STA Single-threaded apartment
MTA Multi-threaded apartment
Unknown Atualmente mapeado para MTA

Depois de a propriedade ApartmentState ser configurada, ela não pode ser alterada no-
vamente. Nenhum erro ou exceção resultará da tentativa de configurar essa propriedade
mais de uma vez.
Um método alternativo é utilizar o atributo [STAThread] ou [MTAThread] antes da pri-
meira linha do código no arquivo dpr do projeto. Embora seja possível utilizar o código a
seguir para configurar o modelo de apartment,

Thread.CurrentThread.ApartmentState := ApartmentState.STA;

utilizando as garantias apropriadas de atributo com as quais o estado do apartment será


configurado antes de qualquer código de inicialização ser executado.

A classe System.Threading.ThreadPool
Um dos recursos mais interessantes da manipulação de threads no .NET Framework é a
adição de uma classe de pool de threads. Poupando milhares de linhas de código, essa
classe fornece o meio necessário de utilizar threads tão facilmente quanto chamar um
único método.
Ideal para tarefas de curta duração, a classe ThreadPool oculta a classe Thread interna,
removendo a flexibilidade e controle que a classe Thread fornece.
Muitos recursos da plataforma .NET utilizam threads vindas de um pool de threads.
Exemplos da utilização do pool de threads incluem E/S assíncrona de arquivo, timers, co-
nexões soquetes e execução assíncrona de delegates.
Todos os AppDomains localizados dentro do mesmo processo compartilham threads
do mesmo pool de threads. A Figura 14.3 ilustra esse relacionamento.
Aplicações solicitam uma thread utilizando um dos métodos QueueUserWorkItem( ) da
classe ThreadPool. Esses métodos recebem um delegate WaitCallback que especifica o méto-
do a executar em uma thread do pool de threads. Esse método é adicionado a uma fila in-
terna e é executado quando uma thread está disponível.
Tenha cuidado ao utilizar um dos métodos que começa com Unsafe, pois esses méto-
dos driblam a verificação de segurança a fim de aumentar o desempenho.
Um exemplo da utilização de threads em um ThreadPool é mostrado na Listagem 14.4.

LISTAGEM 14.4 Utilizando threads de um ThreadPool


1: program threadpool;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Threading;
350 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.4 Continuação


5:
6: type
7: TThreadPoolMe = class
8: public
9: // esse método será executado em uma thread de threadpool
10: procedure ThreadMePlease(state : System.Object);
11: end;
12:
13: procedure TThreadPoolMe.ThreadMePlease(state : System.Object);
14: var
15: id : string;
16: begin
17: id := 'n/a';
18: if assigned(state) then
19: id := state.ToString;
20:
21: writeln(id,') Hello from the thread pool. Thread ID is ',
22: AppDomain.GetCurrentThreadID,' IsThreadPool = ',
23: Thread.CurrentThread.IsThreadPoolThread);
24:
25: Thread.Sleep(2000);
26: end;
27:
28: const
29: MAX_THREADS = 10;
30: var
31: i : integer;
32: thrd : array[1..MAX_THREADS] of TThreadPoolMe;
33: begin
34: writeln('The main thread''s id is ', AppDomain.GetCurrentThreadID);
35: // enfileira alguns threads para utilizar o pool de threads
36: for i:=1 to MAX_THREADS do
37: begin
38: // cria um outra instância de nossa classe
39: thrd[i] := TThreadPoolMe.Create;
40: writeln('Queueing thread ', i);
41: // agora, enfileira o método ThreadMePlease, passando i para o estado
42: System.Threading.ThreadPool.QueueUserWorkItem(
43: @thrd[i].ThreadMePlease, System.Object(i));
44: end;
45: // observa a reutilização do id de thread quando essa aplicação executa
46: readln;
47: writeln('Done');
48: end.

u Localize o código no CD: \Code\Chapter 14\Ex03\.


O namespace System.Threading 351

A Listagem 14.4 enfileira dez threads no pool de threads para execução. Executar
esse exemplo revelará como o CLR reutiliza as threads existentes examinando a reutiliza-
ção de IDs de threads.

NOTA
Além das threads criadas manualmente e threads dentro de pool de threads, o .NET Framework
também deve monitorar outros threads. Estas threads incluem a thread principal e a thread finali-
zadora (ou o garbage collector). Threads não-gerenciadas também podem ser executadas a partir
de PInvoke ou também do COM Interop.

A classe System.Threading.Timer
O .NET Framework tem três diferentes classes Timer que são utilizadas para diferentes pro-
pósitos. System.Threading.Timer utiliza threads do pool de threads e, em vez de disparar um
evento, utiliza o método de retorno especificado cuja definição é mostrada a seguir:

TimerCallback = procedure (state: System.Object) of object;

Há quatro construtores sobrecarregados da classe Timer. Todos eles são idênticos, ex-
ceto pelos dois últimos parâmetros. O parâmetro de retorno (callback) é o método a ser
chamado quando o timer é disparado. Utilize o parâmetro de estado para passar informa-
ções adicionais em um parâmetro System.Object para o método de retorno. dueTime especi-
fica quanto tempo esperar antes de o método de retorno ser invocado. Esse valor é espe-
cificado em milissegundos. Por fim, período, também especificado em milissegundos,
indica quanto tempo esperar entre sucessivas chamadas ao método de retorno. Utilize
um dos métodos Change( ) para alterar dueTime ou o período depois de criar a instância do
Timer. Selecionada a partir de timerthread.dpr, a Listagem 14.5 demonstra como utilizar
a classe Timer.

LISTAGEM 14.5 Declaração de exemplo da classe System.Threading.Timer


1: program timerthread;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Threading;
5:
6: type
7: TD4DNTimerClass = class
8: public
9: // Alarm será chamado quando o Timer for disparado
10: procedure Alarm(state : System.Object);
11: end;
12:
13: procedure TD4DNTimerClass.Alarm(state : System.Object);
14: begin
15: writeln(AppDomain.GetCurrentThreadID,' Bzzzz. Time to wake up!');
16: if assigned(state) then
17: writeln('You passed me: ', state.ToString);
352 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.5 Continuação


18: end;
19:
20: var
21: tc : TD4DNTimerClass;
22: t : Timer;
23: begin
24: // cria uma instância de nossa classe
25: tc := TD4DNTimerClass.Create;
26: // cria o timer para chamar Alarm somente uma vez, depois de um retardo de 1 segundo
➥(1000 ms)
27: t := Timer.Create(tc.Alarm, System.Object('Some additional info'),
➥1000 , 0);
28: writeln(AppDomain.GetCurrentThreadID,' Waiting for the timer to fire.');
29: // dá uma chance para que o timer dispare
30: Thread.Sleep(2000);
31: writeln(AppDomain.GetCurrentThreadID,' Done!');
32: end.

u Localize o código no CD: \Code\Chapter 14\Ex04\.

Delegates
Um delegate é um mecanismo de retorno tipificado herdado de System.Delegate. Os dele-
gates requerem que um método seja chamado na data/hora apropriada. Os delegates que
descendem de System.MulticastDelegate são capazes de tratar múltiplos métodos. Embora
os delegates normalmente sejam chamados de uma maneira síncrona utilizando o méto-
do Invoke( ), o método BeginInvoke( ) permite chamar métodos delegate assincronamen-
te. Somente os delegates que têm um método a chamar são capazes de utilizar o método
BeginInvoke( ). Infelizmente, o compilador Delphi 8 não reconhece um ponteiro de mé-
todo como uma instância de classe delegate. Felizmente, BeginInvoke( ) ainda pode ser
chamado utilizando Reflection. A Listagem 14.6 demonstra como executar um delegate
assincronamente.

LISTAGEM 14.6 Executando delegates assincronamente


1: program AsyncDelegate;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Reflection,
5: System.Threading;
6:
7: type
8: TMyDelegate = procedure of object;
9:
10: TMyClass = class
O namespace System.Threading 353

LISTAGEM 14.6 Continuação


11: private
12: FOnDoSomething : TMyDelegate;
13: public
14: procedure CallDelegate;
15: property OnDoSomething : TMyDelegate read FOnDoSomething write
➥FOnDoSomething;
16: end;
17:
18: TAnotherClass = class
19: public
20: procedure DoFoo;
21: end;
22:
23: procedure TMyClass.CallDelegate;
24: var
25: obj : System.Object;
26: t : System.Type;
27: m : MethodInfo;
28: parms : array [0..1] of System.Object;
29: begin
30: writeln('CallDelegate');
31: if Assigned(FOnDoSomething) then
32: begin
33: // writeln ('chama o delegate ');
34: // Normalmente isso seria semelhante a
35: // @FOnDoSomething.BeginInvoke (nil, nil);
36: // mas o compilador não suporta BeginInvoke, uma vez que
37: // ele pensa que é apenas um ponteiro para um método.
38: //
39: // a correção utiliza reflexão para invocar o método
40: // primeiro, converte o "ponteiro de método" em um objeto
41: obj := System.Object(@FOnDoSomething);
42: // agora obtém o tipo do FonDoSomething
43: t := obj.GetType;
44: // agora precisamos procurar um método identificado como BeginInvoke
45: m := t.GetMethod('BeginInvoke');
46: // constrói a lista de parâmetros
47: parms[0] := nil;
48: parms[1] := nil;
49: // agora podemos chamar BeginInvoke com os paramêtros
50: m.Invoke(obj, parms);
51: end;
52: end;
53:
54: procedure TAnotherClass.DoFoo;
55: begin
56: writeln(AppDomain.GetCurrentThreadID,' DoFoo');
67: end;
354 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.6 Continuação


58:
59: var
60: c : TMyClass;
61: ac : TAnotherClass;
62:
63: begin
64: c := TMyClass.Create;
65: ac := TAnotherClass.Create;
66: writeln(AppDomain.GetCurrentThreadID,' assigning the delegate');
67: c.OnDoSomething := @ac.DoFoo;
68: writeln(AppDomain.GetCurrentThreadID,' calling the delegate');
69: c.CallDelegate;
70: // espera um pouco para que o delegate chame...
71: Thread.Sleep(2000);
72: writeln(AppDomain.GetCurrentThreadID,' Done');
73: end.

u Localize o código no CD: \Code\Chapter 14\Ex05\.

Escrevendo código thread-safe no estilo .NET


Escrever uma aplicação multithread não faz sentido se múltiplas threads não puderem
interagir de maneira previsível e livre de bugs. Diz-se que um corpo de código é thread-
safe se múltiplas threads puderem executá-lo de maneira segura sem nenhum efeito
colateral. Uma das maneiras de criar um método ou função segura para thread, ou thread-
safe, é serializar o acesso a ele, permitindo assim que somente uma thread execute o código
em determinado momento.
Código thread-safe pode ser alcançado seguindo algumas diretrizes:
— Evitar variáveis e objetos compartilhados entre threads. Se isso não for praticável,
utilize um mecanismo de bloqueio para serializar o acesso.
— Utilizar variáveis declaradas na stack (por exemplo, variáveis locais).

— Utilizar rotinas sem estado (stateless) passando todos os parâmetros necessários


para seu trabalho.
— Utilizar métodos sem estado, que realizam o trabalho nos dados internos do obje-
to. (Isso supõe que cada instância de objeto só seja acessível a partir de uma thread.)
Quaisquer dados adicionais devem ser passados como parâmetros. Certifique-se
de que parâmetros ou campos não se referem a outros dados globais ou objetos
desprotegidos.
— Utilizar Thread Local Storage (threadvar), explicado em uma seção posterior deste
capítulo.

Felizmente, o .NET Framework fornece uma rica coleção de classes que ajuda a escre-
ver código thread-safe. Essas classes são generalizadas como mecanismos de eventos e
Escrevendo código thread-safe no estilo .NET 355

bloqueadores (locking). Um mecanismo bloqueador realiza a serialização, enquanto um


mecanismo de evento é utilizado na comunicação entre threads.

Mecanismos bloqueadores
Serializar o acesso a um recurso é realizado com um mecanismo bloqueador. Permitindo
que apenas uma thread entre em uma região protegida, outras threads serão bloqueadas.
Quando uma thread sair de uma região protegida do código, uma outra thread tem então
permissão de entrar.
Há três mecanismos bloqueadores no .NET Framework: mutexes, monitores e blo-
queios de leitura e gravação. Além disso, a classe Interlocked fornece um conjunto básico
de operações atômicas. As operações atômicas são aquelas que têm garantias de não se-
rem interrompidas depois de iniciarem.

A classe System.Threading.WaitHandle
Antes de discutirmos mutexes e monitores, é necessário começar com a classe WaitHandle,
uma vez que esses dois mecanismos bloqueadores herdam funcionalidade comum dela.
A Listagem 14.7 contém a definição da classe WaitHandle.

LISTAGEM 14.7 Declaração da classe System.Threading.WaitHandle


System.Threading.WaitHandle = class (System.MarshalByRefObject, IDisposable)
public
constructor Create;
procedure Close; virtual;
function WaitOne: Boolean; overload; virtual;
function WaitOne(timeout: TimeSpan;
exitContext: Boolean) : Boolean; overload; virtual;
function WaitOne(millisecondsTimeout: Integer;
exitContext: Boolean) : Boolean; overload; virtual;
class function WaitAll(waitHandles: array of WaitHandle;
millisecondsTimeout: Integer;
exitContext: Boolean) : Boolean; overload; static;
class function WaitAll(waitHandles: array of WaitHandle;
timeout: TimeSpan;
exitContext: Boolean) : Boolean; overload; static;
class function WaitAll(waitHandles: array of WaitHandle) : Boolean;
overload; static;
class function WaitAny(waitHandles: array of WaitHandle;
millisecondsTimeout: Integer;
exitContext: Boolean) : Integer; overload; static;
class function WaitAny(waitHandles: array of WaitHandle;
timeout: TimeSpan;
exitContext: Boolean) : Integer; overload; static;
class function WaitAny(waitHandles: array of WaitHandle) : Integer;
overload; static;
property Handle: System.IntPtr read; write;
end;
356 Capítulo 14 Threading no Delphi for .NET

Semelhante ao WaitForMultipleObjects( ), da Win32 API, o método WaitAll( ) sobre-


carregado aceita múltiplos objetos WaitHandle e só irá retornar se todos os handles estive-
rem sinalizados. O método WaitAny( ) retorna se qualquer um dos múltiplos objetos Wait-
Handle estiverem sinalizados. Os dois métodos têm parâmetros opcionais de tempo limite
que permitem que eles saiam da espera prematuramente e evitem deadlocks.

Classe System.Threading.Mutex
Um mutex é um objeto mutuamente exclusivo que age de maneira semelhante a um blo-
queio. O bloqueio do mutex é realizado chamando um dos métodos WaitOne( ) sobrecar-
regados. Mutexes têm a opção de serem identificados. Especificar um nome permite que o
mutex seja compartilhado entre AppDomains e processos. O mutex é desbloqueado cha-
mando o método ReleaseMutex( ). A Listagem 14.8 mostra os métodos declarados na clas-
se Mutex.

LISTAGEM 14.8 Declaração da classe System.Threading.Mutex


System.Threading.Mutex = class (System.Threading.WaitHandle)
public
constructor Create(initiallyOwned: Boolean; name: String;
var createdNew: Boolean); overload;
constructor Create(initiallyOwned: Boolean; name: String); overload;
constructor Create(initiallyOwned: Boolean); overload;
constructor Create; overload;
procedure ReleaseMutex;
end;

Além dos métodos Wait herdados de WaitHandle, a classe Mutex adiciona vários constru-
tores e o método ReleaseMutex( ). Esses construtores fornecem a capacidade de criar, opci-
onalmente bloquear (possuir), e nomear o mutex. Um mutex é desbloqueado utilizando
o método ReleaseMutex( ).

A classe System.Threading.Monitor
A classe System.Threading.Monitor foi projetada para ser mais leve que a classe Mutex. Ela deve
ser utilizada quando um mecanismo bloqueador de alto desempenho for necessário.
Embora a classe Monitor seja semelhante a um mutex, há algumas diferenças sutis.
Examine a Listagem 14.9 para a definição da classe Monitor.

LISTAGEM 14.9 Declaração da classe System.Threading.Monitor


System.Threading.Monitor = class (System.Object)
public
class procedure Enter(obj: System.Object); static;
class function TryEnter(obj: System.Object) : Boolean; overload; static;
class function TryEnter(obj: System.Object;
millisecondsTimeout: Integer) : Boolean; overload; static;
class function TryEnter(obj: System.Object;
Escrevendo código thread-safe no estilo .NET 357

LISTAGEM 14.9 Continuação


timeout: TimeSpan) : Boolean; overload; static;
class function Wait(obj: System.Object; millisecondsTimeout: Integer;
exitContext: Boolean) : Boolean; overload; static;
class function Wait(obj: System.Object; timeout: TimeSpan;
exitContext: Boolean) : Boolean; overload; static;
class function Wait(obj: System.Object;
millisecondsTimeout: Integer) : Boolean; overload; static;
class function Wait(obj: System.Object;
timeout: TimeSpan) : Boolean; overload; static;
class function Wait(obj: System.Object) : Boolean; overload; static;
class procedure Pulse(obj: System.Object); static;
class procedure PulseAll(obj: System.Object); static;
class procedure Exit(obj: System.Object); static;
end;

Utilizar os métodos Enter( ) e Exit( ) da classe Monitor tem o mesmo efeito de bloqueio
dos métodos ReleaseMutex( ) e WaitOne( ) da classe Mutex. A classe Monitor também apresenta
os métodos TryEnter( ) que tentam adquirir um bloqueio sem esperar. Um parâmetro
opcional de tempo limite especifica quanto tempo esperar no bloqueio antes de desistir.
Observe que todos os métodos da classe Monitor são métodos de classe. Isso fornece a
flexibilidade para especificar qualquer objeto a ser utilizado para bloquear.
Por fim, a classe Monitor permite sinalizar e esperar um sinal com os métodos Wait( ) e
Pulse( ). Os dois métodos Wait( ) e Pulse( ) requerem que a classe Monitor seja bloqueada –
isto é, cercam os métodos Wait( ) e Pulse( ) com um par Enter( ) / Exit( ).
Localize o código no CD: \Code\Chapter 14\Ex06\prodcons.dpr para um exemplo que de-
monstra uma fila thread-safe que utiliza as classes Mutex e Monitor.

A classe System.Threading.ReaderWriterLock
As classes Mutex e Monitor fornecem um mecanismo bloqueador que é útil para evitar que
uma seção de código seja executada simultaneamente por múltiplas threads. Entretanto,
há momentos em que um bloqueio, com distinção entre um leitor e um escritor, pode
aumentar o desempenho. Isso é especialmente verdadeiro quando há mais leitores do
que escritores ou se o escritor não tiver atualizações freqüentes. Felizmente, a classe Reader-
WriterLock fornece essa funcionalidade. A Listagem 14.10 contém a definição da classe
ReaderWriterLock.

LISTAGEM 14.10 Declaração da classe System.Threading.ReaderWriterLock


System.Threading.ReaderWriterLock = class (System.Object)
constructor Create;
procedure AcquireReaderLock(millisecondsTimeout: Integer); overload;
procedure AcquireReaderLock(timeout: TimeSpan); overload;
procedure AcquireWriterLock(millisecondsTimeout: Integer); overload;
procedure AcquireWriterLock(timeout: TimeSpan); overload;
procedure ReleaseReaderLock;
358 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.10 Continuação


procedure ReleaseWriterLock;
function UpgradeToWriterLock(millisecondsTimeout: Integer) :
LockCookie; overload;
function UpgradeToWriterLock(timeout: TimeSpan) :
LockCookie; overload;
procedure DowngradeFromWriterLock(var lockCookie: LockCookie);
function ReleaseLock: LockCookie;
procedure RestoreLock(var lockCookie: LockCookie);
function AnyWritersSince(seqNum: Integer) : Boolean;
property IsReaderLockHeld: Boolean read;
property IsWriterLockHeld: Boolean read;
property WriterSeqNum: integer read;
end;

Como esperado, há dois tipos de bloqueios disponíveis. Esses bloqueios são chama-
dos bloqueio de leitor (reader lock) e bloqueio de escritor (writer lock). Os bloqueios são
adquiridos utilizando os métodos AcquireReaderLock( ) e AcquireWriterLock( ), respectiva-
mente.
Um bloqueio de leitor é adquirido somente quando nenhum bloqueio de escritor é
mantido. Dessa maneira, múltiplos leitores são permitidos. Entretanto, todos os leitores
são bloqueados quando um bloqueio de escritor é obtido. Somente um bloqueio de escri-
tor é permitido. Localize o código no CD: Code\Chapter 14\Ex07\TestRWLock.dpr DE para um
exemplo de como utilizar a classe ReaderWriterLock.

A classe System.Threading.Interlocked
Devido à natureza aleatória da maneira como o kernel agenda threads, não há como im-
pedir que um bloco de código interrompido execute uma outra thread. O código que
precisa ser executado atomicamente – isto é, sem interrupção – requer o uso da classe
System.Threading.Interlocked. A Listagem 14.11 contém a definição da classe Interlocked.

LISTAGEM 14.11 Declaração da classe System.Threading.Interlocked


System.Threading.Interlocked = class (System.Object)
class function Increment(var location: Integer) : Integer; overload; static;
class function Increment(var location: Int64) : Int64; overload; static;

class function Decrement(var location: Integer) : Integer; overload; static;


class function Decrement(var location: Int64) : Int64; overload; static;
class function Exchange(var location1: Integer; value: Integer)
: Integer; overload; static;
class function Exchange(var location1: Single;
value: Single) : Single; overload; static;
class function Exchange(var location1 : System.Object;
value : System.Object) : System.Object; overload;
➥static;
Escrevendo código thread-safe no estilo .NET 359

LISTAGEM 14.11 Continuação


class function CompareExchange(var location1: Integer;
value: Integer;
comparand: Integer) : Integer; overload;
➥static;
class function CompareExchange(var location1: Single;
value: Single;
comparand: Single) : Single; overload; static;
class function CompareExchange(var location1 : System.Object;
value : System.Object;
comparand : System.Object) : System.Object;
➥overload; static;
end;

Observe que todos os métodos são métodos de classe, portanto uma instância dessa
classe nunca é necessária. É garantido que cada operação distinta termina a execução de-
pois de iniciada.

Eventos
Embora impedir acesso a um bloco crítico de código seja uma tarefa importante ao escre-
ver aplicações multithread, também é preciso ser capaz de sinalizar uma thread em espe-
ra. É comum fazer com que uma ou mais threads façam o trabalho de manipulação de
outras threads . As threads que podem criar o trabalho são chamadas threads produtoras.
As threads consumidoras são aquelas que fazem o trabalho real distribuído pelas threads
produtoras.
O sinal utilizado pelo .NET Framework é conhecido como um evento. Há dois tipos
de eventos disponíveis – AutoResetEvent e ManualResetEvent. Quando um evento é configu-
rado, ele é sinalizado. De maneira semelhante, quando um evento é redefinido, o estado
do evento não é sinalizado. Os eventos são redefinidos automaticamente pelo .NET Fra-
mework exatamente quando uma thread é liberada depois de esperar o evento. Eventos
redefinidos manualmente devem ser limpos programaticamente e há probabilidades de
múltiplas threads serem liberadas. As Listagens 14.12 e 14.13 mostram a definição de
classe dos dois tipos de eventos.

LISTAGEM 14.12 Declaração da classe System.Threading.ManualResetEvent


System.Threading.ManualResetEvent = class (System.Threading.WaitHandle)
public
constructor Create(initialState: Boolean);
procedure Close; virtual;
function WaitOne: Boolean; virtual; overload;
function WaitOne(timeout: TimeSpan; exitContext: Boolean) :
Boolean; virtual; overload;
function WaitOne(millisecondsTimeout: Integer; exitContext: Boolean) :
Boolean; virtual; overload;
function Reset: Boolean;
360 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.12 Continuação


function Set: Boolean;
class function WaitAll(waitHandles: WaitHandle[ ];
millisecondsTimeout: Integer; exitContext: Boolean) : Boolean; overload;
➥static;
class function WaitAll(waitHandles: WaitHandle[ ]; timeout: TimeSpan;
exitContext: Boolean) : Boolean; overload; static;
class function WaitAll(waitHandles: WaitHandle[ ]) : Boolean; overload; static;
class function WaitAny(waitHandles: WaitHandle[ ];
millisecondsTimeout: Integer; exitContext: Boolean) : Integer; overload;
➥static;
class function WaitAny(waitHandles: WaitHandle[ ]; timeout: TimeSpan;
exitContext: Boolean) : Integer; overload;
class function WaitAny(waitHandles: WaitHandle[ ]) : Integer; overload; static;
property Handle: System.IntPtr read; write;
end;

LISTAGEM 14.13 Declaração da classe System.Threading.AutoResetEvent


System.Threading.AutoResetEvent = class (System.Threading.WaitHandle,
IDisposable)
public
constructor Create(initialState: Boolean);
function Reset: Boolean;
function Set: Boolean;
end;

A utilização de qualquer um dos tipos de evento é idêntica. Utilize o método Set( )


para sinalizar um evento e o método Reset( ) para limpar o sinal. Observe que vários mé-
todos são utilizados para esperar um evento. O método WaitOne( )sobrecarregado espera
somente um evento, enquanto o método WaitAll( ) espera que cada entidade seja sinali-
zada e o método WaitAny( ) permite esperar que um sinal qualquer seja disparado. Por
fim, os métodos WaitAll( ) e WaitAny( ) fornecem a capacidade de esperar uma classe Mutex
ou qualquer tipo de evento. Lembre-se de que os métodos WaitAll( ) e WaitAny( ) são her-
dados de WaitHandle e estão listados na seção anterior “A classe System.Threading.Wait-
Handle.”
Localize o código no CD: \Code\Chapter 14\Ex06\prodcons.dpr para um exemplo que de-
monstra como utilizar eventos. Certifique-se de definir o símbolo USE_EVENTS.

Armazenamento local de thread


Utilizar variáveis globais em uma aplicação multithread é seguramente a maneira de cau-
sar pesadelos de depuração. A menos que essas variáveis globais sejam protegidas, com
certeza haverá noites de insônia.
Às vezes, é necessário ter uma variável que seja global para uma thread. Thread Local
Storage é o nome dado a variáveis que são globais para uma thread específica. A maneira
mais fácil de criar esse tipo de variável é utilizar a palavra-chave threadvar. Embora sejam
Escrevendo código thread-safe no estilo .NET 361

declarados como uma variável global de unit padrão, a cada thread é alocado um slot se-
parado para armazenar o conteúdo da variável.
A inicialização de threadvars não é permitida. Cada thread precisa inicializar a variá-
vel antes de utilizá-la. Um exemplo de uma threadvar é semelhante a

threadvar
MyThreadVariable: integer; // lembre-se, nenhuma inicialização

No .NET Framework, a classe System.Threading.Thread tem dois métodos para criar o


Thread Local Storage. Eles são os métodos AllocateDataSlot( ) e AllocateNamedDataS-
lot( ). Por fim, utilize ThreadStaticAttribute para criar um campo estático único para cada
thread. O Delphi for .NET atualmente mapeia threadvars com o atributo [ThreadStatic].
Preste atenção especialmente ao utilizar Thread Local Storage com qualquer meca-
nismo que utiliza threads a partir de ThreadPool. Uma outra thread poderia ou não ter ini-
cializado uma variável local da thread com um valor não-esperado.

Comunicações interprocessos do Win32


A Win32 API tem mais mecanismos IPC disponíveis que não foram portados para o
mundo gerenciado. Podemos tirar vantagem de pipes nomeados e semáforos utilizando
a Platform Invoke (PInvoke). Essa talvez seja a melhor opção quando é necessária a inte-
gração com aplicações Win32 atuais. Entretanto, tenha cuidado com questões de desem-
penho porque a PInvoke tem overhead associado. PInvoke é abrangida no Capítulo 16 .

Classes e métodos thread-safe do .NET Framework


A segurança de threads é uma questão séria ao escrever aplicações multithread. Portanto,
como podemos determinar se um método ou classe particular é thread-safe?
Quando se trata de segurança de threads no .NET Framework, uma coisa é garan-
tida: todos os métodos que são métodos public class (static) são thread-safe, contanto
que eles só se referenciem seus parâmetros. Isso faz sentido porque métodos de classe
não podem se referenciar quaisquer dados de instância. Em geral, todos os outros mé-
todos não são thread-safe a menos que a documentação SDK claramente afirme o
contrário.

O método Synchronized( )
A maioria das classes de coleção tem propriedades e métodos auxiliares. Uma fila thread-
safe é obtida utilizando o código a seguir:

myThreadSafeQueue = System.Collections.Queue.Synchronized(
System.Collections.Queue.Create);

Todo o acesso à fila sincronizada agora é thread-safe. Esteja ciente de que uma cole-
ção thread-safe será aproximadamente duas vezes mais lenta do que uma coleção
não-sincronizada.
362 Capítulo 14 Threading no Delphi for .NET

As propriedades IsSynchronized e SyncRoot


As coleções que suportam o método Synchronized( ) também têm uma propriedade que
indica se a coleção está de fato sincronizada. Utilize a propriedade IsSynchronized para de-
terminar se a coleção está sincronizada.
Um outro método de tornar uma coleção thread-safe é utilizar uma System.Threa-
ding.Monitor. Lembre-se de que uma classe Monitor requer um objeto para realizar o blo-
queio. Esse objeto deve ser o objeto retornado pela propriedade SyncRoot da coleção.
Não deixe de procurar pelas propriedades IsSynchronized e SyncRoot em outras classes
dentro do .NET Framework – especificamente, aquelas classes que implementam a inter-
face ICollection. Por exemplo, a classe System.Array é uma classe que também implementa
essas propriedades. Classes Array não são thread-safe e a IsSynchronized retorna false. Seria-
lize o acesso ao array bloqueando a propriedade SyncRoot do array.

ATENÇÃO
As classes que contêm a propriedade SyncRoot requerem a devida diligência por parte do desen-
volvedor a fim de assegurar que todo o acesso ao array esteja bloqueado antes de iterar por ou in-
dexar seus membros. Nada dentro do .NET Framework impede que uma thread bloqueie adequa-
damente uma instância e outra utilize a mesma instância, driblando qualquer mecanismo bloque-
ador. Uma alternativa melhor é escrever uma classe thread-safe que encapsula o mecanismo de ar-
mazenamento apropriado e garante que todos os acessos à classe interna sejam bloqueados.

Questões da interface com o usuário


Para otimizar o desempenho da interface com o usuário (User Interface –UI), tanto apli-
cações WinForms como VCL não implementaram nenhum mecanismo thread-safe.
Quaisquer atualizações em um controle devem ocorrer na thread principal da interface
com o usuário.
Para aplicações WinForm, a maioria dos métodos da classe System.Windows.Forms.Con-
trol não é thread-safe. Há, entretanto, quatro métodos que são seguros para executar a
partir de qualquer thread. Esses métodos são identificados nas seções a seguir. A Lista-
gem 14.14 contém os métodos e propriedades que nos interessam nesta seção.

LISTAGEM 14.14 Métodos System.Windows.Forms.Control thread-safe.


System.Windows.Forms.Control = class (
System.ComponentModel.Component,
ISynchronizeInvoke, …)
public

function Invoke(method: Delegate) : System.Object; overload;
function Invoke(method: Delegate;
args: array of System.Object) :
System.Object; overload;
function EndInvoke(asyncResult: IAsyncResult) :
System.Object;
function BeginInvoke(method: Delegate;
Questões da interface com o usuário 363

LISTAGEM 14.14 Continuação


args: array of System.Object) :
IAsyncResult; overload;
function BeginInvoke(method: Delegate) : IAsyncResult;
overload;
property InvokeRequired: Boolean read;
end;

O método System.Windows.Forms.Control.Invoke( )
A maneira mais comum de assegurar que um controle seja atualizado a partir da thread
da interface com o usuário é utilizar um dos métodos Invoke( ) sobrecarregados. Invoke( )
requer um delegate e um array opcional de argumentos. Quando Invoke( ) é chamado,
ele garante que o método delegate seja executado na mesma thread em que o controle foi
criado. Além disso, o Invoke( ) bloqueia até que o método seja executado, fazendo assim
que a thread em trabalho pause. (A Listagem 14.15 mostra um exemplo da chamada do
método Invoke( )).

NOTA
Nunca chame Join( ) a partir da thread principal que utiliza o método Invoke( ) para atualizar o
thread da UI. Isso faz com que a aplicação trave porque a thread principal espera a thread em tra-
balho e esta espera a thread da UI.

A propriedade System.Windows.Forms.Control.InvokeRequired
Suponha que um método será compartilhado entre uma thread da interface com o usuá-
rio e uma thread em trabalho. Como discutido anteriormente, a thread em trabalho pre-
cisará utilizar o método Invoke( ) a fim de assegurar que a atualização ocorra na thread
adequada. Porém, a thread da interface com o usuário não precisa utilizar o método Invo-
ke( ). Felizmente, a propriedade InvokeRequired fornece a flexibilidade de saber se o méto-
do pode ser chamado diretamente ou se o método Invoke( ) deve ser utilizado. A Lista-
gem 14.15 mostra como utilizar a propriedade InvokeRequired.

LISTAGEM 14.15 Exemplo de Invoke( ) e InvokeRequired


type
TMyUpdateDelegate = procedure of object;

procedure TWinForm.UpdateControls;
var
myDelegate: TMyUpdateDelegate;
tmpStr : string;
begin
if progBar.InvokeRequired then
begin
tmpStr := System.String.Format('{0} invoke required!',
System.Object(AppDomain.GetCurrentThreadID));
364 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.15 Continuação


UpdateTextBox(System.Object(tmpStr));
myDelegate := @self.UpdateControls;
progBar.Invoke(System.Delegate(@myDelegate));
end
else
begin
progBar.Increment(1);
trackBar.Value := trackBar.Value + 1;
end;
end;

u Localize o código no CD: \Code\Chapter 14\Ex08\.

Selecionado do projeto ThreadingExamples, o método UpdateControls( ) é chamado a


partir da thread principal da interface com o usuário e de outras threads em trabalho. Se
ele for chamado da thread em trabalho, um delegate será utilizado para chamar o mesmo
método na thread principal da interface com o usuário. Observe que dois controles são
atualizados: progBar e trackBar. O único teste InvokeRequired ocorre na barra de progresso
uma vez que os dois controles foram criados na mesma thread.

O método System.Windows.Forms.Control.BeginInvoke( )
Quando não é desejável esperar que a thread principal execute, utilize um dos métodos
BeginInvoke( ) sobrecarregados. Esses métodos fazem a mesma coisa que Invoke( ), exceto
que eles executam o método assincronamente, sem esperar. Certifique-se de que o méto-
do delegate utilizado é thread-safe. (A Listagem 14.16 mostra um exemplo da utilização
de BeginInvoke( ).)

O método System.Windows.Forms.Control.EndInvoke( )
Executar um método assincronamente é muito poderoso, especialmente se um valor de
retorno não for necessário. Quando um valor de retorno é necessário em um método
executado com BeginInvoke( ), utilize o método EndInvoke( ). EndInvoke( ) retorna uma
System.Object, que representa o valor de retorno do delegate executado assincronamente.
Utilize EndInvoke( ) cuidadosamente porque ele bloqueará se o método não tiver comple-
tado quando EndInvoke( ) for chamado. A Listagem 14.16, também do projeto Threading-
Examples, ilustra como utilizar os métodos BeginInvoke( ) e EndInvoke( ).

LISTAGEM 14.16 Exemplo de BeginInvoke( )/EndInvoke( )


procedure TWinForm.UpdateControlsNoWaiting;
var
myDelegate: TMyTextBoxDelegate;
tmpStr : string;
parms : array[0..0] of System.object;
lastAsyncInvoke: IAsyncResult;
Questões da interface com o usuário 365

LISTAGEM 14.16 Continuação


begin
if textBox.InvokeRequired then
begin
tmpStr := System.String.Format(
'{0} Invoke required in UpdateControlsNoWaiting',
System.Object(AppDomain.GetCurrentThreadID));
myDelegate := @self.LongRunningMethod;
parms[0] := System.Object(tmpStr);
lastAsyncInvoke := textBox.BeginInvoke(
System.Delegate(@myDelegate), parms );
tmpStr := System.String.Format(
'{0} After BeginInvoke call in UpdateControlsNoWaiting',
System.Object(AppDomain.GetCurrentThreadID));
UpdateTextBox(System.Object(tmpStr));
// Agora, vamos esperar que a chamada assíncrona termine.
textBox.EndInvoke(lastAsyncInvoke);
tmpStr := System.String.Format(
'{0} After EndInvoke call in UpdateControlsNoWaiting',
System.Object(AppDomain.GetCurrentThreadID));
UpdateTextBox(System.Object(tmpStr));
end
else
UpdateTextBox(
'Invoke is not required in UpdateControlsNoWaiting');
end;

u Localize o código no CD: \Code\Chapter 14\Ex08\.

O método System.Windows.Forms.Control.CreateGraphics( )
O acesso ao subsistema de desenho GDI+ é realizado pela classe System.Drawing.Graphics.
Uma instância dessa classe torna-se disponível chamando o método CreateGraphics( ) de
um controle. O método CreateGraphics( ) é thread-safe, permitindo que múltiplas threads
atualizem uma superfície de desenho GDI+ sem afetar uma a outra. Os recursos GDI+ são
limitados, assim certifique-se de utilizar o método Dispose( ) ao terminar o trabalho com
a instância Graphics.
Tenha cuidado ao utilizar CreateGraphics( ). Certifique-se de que quaisquer desenhos
sejam feitos dentro de um handler de pintura. Qualquer atualização de um controle que
ocorra fora de um handler de pintura será apagada quando a próxima mensagem de pin-
tura for processada. A Listagem 14.17, também do projeto ThreadingExamples, demons-
tra como duas threads podem, ao mesmo tempo, pintar de maneira segura um controle.
A pintura não ocorre dentro de um handler de pintura, assim o desenho é facilmente
apagado.
366 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.17 Exemplo de CreateGraphics( )


1: procedure TWinForm.btnCreateGraphics_Click(sender: System.Object; e:
➥System.EventArgs);
2: var
3: inst : array[0..1] of TMyGraphicsClass;
4: thrd : Thread;
5: i : integer;
6: begin
7: // esse método cria duas instâncias de TMyGraphicClass
8: // e executa o método draw em diferentes threads
9: UpdateTextBox('Anything causing a repaint erase the graphics being
➥currently drawn');
10: for i:=low(inst) to high(inst) do
11: begin
12: inst[i] := TMyGraphicsClass.Create;
13: inst[i].Control := Self.Panel1;
14: inst[i].DrawRectangle := (i = 0);
15:
16: thrd := Thread.Create(@inst[i].Draw);
17: thrd.Start;
18: end;
19: end;
20:
21: procedure TMyGraphicsClass.Draw;
22: var
23: gr : System.Drawing.Graphics;
24: clr : Color;
25: i : integer;
26: height : integer;
27: width : integer;
28: rnd : System.Random;
29: curX : integer;
30: curY : integer;
31: curW : integer;
32: curH : integer;
33: thrdID : System.Object;
34: tmpStr : string;
35: begin
36: // esse método desenha aleatoriamente círculos vermelhos ou retângulos azuis
37: thrdID := System.Object(AppDomain.GetCurrentThreadId);
38: tmpStr := System.String.Format('{0} Graphics Class Thread
➥Started',thrdId);
39: theWinFormInstance.UpdateTextBox(System.Object(tmpStr));
40:
41: try
42: clr := Color.Red;
43: if FDrawRectangle then
44: clr := Color.Blue;
45:
Questões da interface com o usuário 367

LISTAGEM 14.17 Continuação


46: gr := FControl.CreateGraphics;
47: try
48: rnd := System.Random.Create;
49:
50: height := System.Convert.ToInt32(gr.VisibleClipBounds.Height);
51: width := System.Convert.ToInt32(gr.VisibleClipBounds.Width);
52:
53: for i:=1 to 10 do
54: begin
55: curX := rnd.Next(width);
56: curY := rnd.Next(height);
57: curW := rnd.Next(width div 3);
58: curH := rnd.Next(height div 3);
59:
60: if (curX + curW) > width then
61: curX := width - curW;
62:
63: if (curY + curH) > height then
64: curY := height - curH;
65:
66: if FDrawRectangle then
67: gr.FillRectangle(SolidBrush.Create(clr), curX, curY, curW, curH)
68: else
69: gr.FillEllipse(SolidBrush.Create(clr), curX, curY, curW, curH);
70:
71: Thread.Sleep(2000);
72: end;
73: finally
74: gr.Dispose;
75: end;
76: except
77: on e : exception do
78: begin
79: thrdID := System.Object(AppDomain.GetCurrentThreadId);
80: tmpStr := System.String.Format('{0} Graphics class thread
➥Exception {1}', thrdID,
81: System.Object(e.message));
82: theWinFormInstance.UpdateTextBox(System.Object(tmpStr));
83: end;
84: end;
85:
86: thrdID := System.Object(AppDomain.GetCurrentThreadId);
87: tmpStr := System.String.Format('{0} Graphics Class Thread
➥Finished',thrdId);
88: theWinFormInstance.UpdateTextBox(System.Object(tmpStr));
89: end;

u Localize o código no CD: \Code\Chapter 14\Ex08\.


368 Capítulo 14 Threading no Delphi for .NET

A Listagem 14.17 cria duas threads que desenham um retângulo ou uma elipse.
Antes de o método Draw( ) poder pintar o controle, primeiro ele deve selecionar uma ins-
tância para a classe Graphics, o que é ilustrado na linha 46. Depois que essa instância é ob-
tida, cada thread tem liberdade de atualizar o mesmo controle simultaneamente.

Exceções em threads
As exceções precisam ser tratadas apropriadamente a fim de impedir que threads termi-
nem prematuramente. Qualquer exceção dentro de uma thread que não é tratada fará
com que a thread termine. Portanto, uma boa prática de programação é pelo menos in-
formar todas as exceções e tratar as exceções que são esperadas.

System.Threading.ThreadAbortException
Chamar o método Thread.Abort( ) levantará a ThreadAbortException na thread alvo. Nor-
malmente, isso fará com que a thread termine. Entretanto, uma thread pode capturar a
ThreadAbortException e optar por ignorá-la utilizando o método Thread.ResetAbort( ).
Há dois métodos Abort( ) sobrecarregados. Um deles não aceita nenhum parâmetro;
o outro aceita um System.Object que fornece informações de estado para a thread. Esse pa-
râmetro é examinado por meio da propriedade ExceptionState de ThreadAbortException. A
Listagem 14.18 demonstra como tratar a ThreadAbortException, selecionada de Threa-
dingExceptions.dpr.

LISTAGEM 14.18 Exemplo de ThreadingExceptions


1: program ThreadingExceptions;
2: {$APPTYPE CONSOLE}
3:
4: //
5: // Esse exemplo demonstra ThreadingExceptions.
6: // - ThreadAbortException é tratada e redefinida
7: // - ThreadInterruptedException é tratada
8: // - ThreadStateException é tratada
9: // - SynchronizationLockException é tratada
10: //
11:
12: uses
13: System.Threading;
14:
15: type
16: D4DNThreadMe = class
17: public
18: procedure MyThreadMethod;
19: end;
20:
21: procedure D4DNThreadMe.MyThreadMethod;
22: var
Exceções em threads 369

LISTAGEM 14.18 Continuação


23: numAborts : integer;
24:
25: begin
26: numAborts := 0;
27: while true do
28: begin
29: try
30: write('*');
31: Thread.Sleep(1000);
32: except
33: on e : System.Threading.ThreadInterruptedException do
34: begin
35: writeln('Handled ThreadInterruptedException');
36: end;
37: on e : System.Threading.ThreadAbortException do
38: begin
39: writeln('Handled threadAbortException');
40: inc(numAborts);
41: if numAborts = 1 then
42: Thread.ResetAbort;
43: end;
44: on e : exception do
45: begin
46: writeln('Unhandled exception ',e.message);
47: raise;
48: end;
49: end;
50: end;
51: end;
52:
53: var
54: thrdclass1 : D4DNThreadMe;
55: thrd1 : Thread;
56: cmd : string;
57: bDone : boolean;
58:
59: begin
60: System.Console.WriteLine('Staring threading exceptions example...');
61:
62: // cria a instância myDotNetThread
63: thrdclass1 := D4DNThreadMe.create;
64: thrd1 := Thread.Create(@thrdclass1.MyThreadMethod);
65: thrd1.Start;
66:
67: bDone := false;
68: while not bDone do
69: begin
70: System.Console.WriteLine('Enter A = Abort, I = Interrupt, ' +
370 Capítulo 14 Threading no Delphi for .NET

LISTAGEM 14.18 Continuação


71: 'S = ThreadStateException, L = SyncLockException');
72: cmd := System.Console.ReadLine.ToUpper;
73: if (cmd = 'A') then
74: thrd1.Abort // levanta uma ThreadAbortException
75: else if (cmd = 'I') then
76: thrd1.Interrupt // levanta uma ThreadInterruptedException
77: else if (cmd = 'S') then
78: begin
79: try
80: thrd1.Start
81: except
82: on e : System.Threading.ThreadStateException do
83: begin
84: System.Console.WriteLine('Handled ThreadStateException');
85: end;
86: end;
87: end
88: else if (cmd = 'L') then
89: begin
90: try
91: // precisa ser empacotada em um bloco Enter/Exit( )
92: System.Threading.Monitor.Wait(thrd1);
93: except
94: on e : System.Threading.SynchronizationLockException do
95: begin
96: System.Console.WriteLine('Handled SynchronizationLockException');
97: end;
98: end;
99: end
100: else
101: bDone := true;
102: end;
103:
104: // assegura que a thread seja terminado
105: thrd1.Abort;
106: Thread.Sleep(1000);
107: thrd1.Abort;
108:
109: System.Console.WriteLine('Done - main');
110: end.

u Localize o código no CD: \Code\Chapter 14\Ex09\.

System.Threading.ThreadInterruptedException
Threads que estão em um estado WaitSleepJoin podem ser interrompidas com o método
Thread.Interrupt( ). Se uma thread não estiver no estado WaitSleepJoin, a exceção será dis-
Garbage Collection e threading 371

parada na próxima vez que entrar nesse estado. Não tratada, essa exceção terminará a
thread. A Listagem 14.18 mostra um exemplo de ThreadInterrupedException.

System.Threading.ThreadStateException
Suponha que uma aplicação tem duas threads – A e B. Se a thread A tentar forçar a thread B
a um estado inválido, uma ThreadStateException será levantada na thread A. Tentar passar
de um estado em execução para um estado reiniciado ou de um estado terminado para
um estado reiniciado são transições de estado invalidas que irão disparar a ThreadStateEx-
ception. A Listagem 14.18 contém um exemplo de como a ThreadStateException é levanta-
da e tratada.

System.Threading.SynchronizationLockException
Tentar utilizar certos métodos da classe Monitor incorretamente levantará a Synchronizati-
onLockException. Por exemplo, chamar o método Wait( ) fora de um par de métodos
Enter( )/Exit( ) dispara essa exceção. A Listagem 14.18 ilustra um exemplo.

Garbage Collection e threading


O Garbage collection pode ocorrer a qualquer hora. A fim de realizar seu trabalho, todas
as threads são suspensas. Se todas as threads gerenciadas estiverem atualmente executan-
do código não-gerenciado, essas threads serão retomadas. Entretanto, antes de retomar
essas threads, o CLR insere código a fim suspender a thread quando ela retorna ao código
gerenciado. Essas threads têm permissão de retomar porque a memória é imobilizada (re-
tida) quando referenciada por um código não-gerenciado.
NESTE CAPÍTULO
— Reflection em um assembly CAPÍTULO 15
— Reflection em um módulo

— Reflection em tipos API Reflection


— Invocação em tempo de
execução de membros de um
tipo (vinculação tardia)
— Emitindo MSIL por meio da
A tecnologia chamada Reflection é a maneira .NET de
obter informações de metadados sobre classes e tipos. Ela
reflexão
é semelhante à natureza RTTI (runtime type
information) do Delphi, embora a implementação seja
substancialmente diferente. Os metadados são fornecidos
pelas classes definidas no namespace System.Reflection.
Reflection oferece mais capacidades do que apenas
obter informações sobre os metadados. Por meio de
reflection você pode instanciar várias classes, invocar os
métodos dessas classes, obter e configurar valores de
membros, definir e criar novas classes, adicionar
dinamicamente tipos a um assembly e muito mais. Uma
outra utilização poderosa de reflection é definir e
verificar a presença de atributos personalizados. Esses
atributos podem fornecer outras informações sobre
propriedades, métodos, classes etc. que podem ser
utilizados para informações sobre depuração,
documentação, inspeção, tempo de projeto etc. Este
capítulo aborda as capacidades da Reflection API.

O QUE ACONTECEU COM A RTTI?


A RTTI é semelhante à reflection e foi a base por trás da descoberta
dinâmica de informações sobre classes Delphi VCL em runtime. A
RTTI não desapareceu: ela pode ser encontrada no arquivo Bor-
land.Vcl.TypInfo.pas, ainda utilizada por classes para descoberta de
classes VCL e é um meio para portabilidade entre versões Win32 e
.NET da VCL.

Reflection em um assembly
O Capítulo 8 discute os assemblies e como eles contêm
metadados sobre os tipos contidos neles. Esta seção
discute a reflection no próprio assembly. As seções a
seguir mostram como investigar mais profundamente os
módulos do assembly, extraindo informações detalhadas
sobre sua estrutura e conteúdo.
Reflection em um assembly 373

A própria classe Assembly contém várias propriedades e métodos que você pode utili-
zar para refletir informações sobre ela. A Listagem 15.1 ilustra alguns desses métodos.

LISTAGEM 15.1 O exemplo do uso de reflection em um assembly


1: procedure TWinForm.ReflectAssembly(aAssembly: Assembly);
2: var
3: arModule: array of Module;
4: i: integer;
5: tn: TreeNode;
6: tni: TreeNode;
7: arAttrib: array of System.Object;
8: arAsmName: array of AssemblyName;
9: arType: array of System.Type;
10: begin
11: // Mostra informações-chave
12: tn := TreeView1.Nodes.Add(aAssembly.FullName);
13: tn.Nodes.Add('Location: '+aAssembly.Location);
14: if aAssembly.GlobalAssemblyCache then
15: tn.Nodes.Add('Global Cache: Yes')
16: else
17: tn.Nodes.Add('Global Cache: No');
18:
19: // Determina o assembly chamador
20: tn.Nodes.Add('Calling Assembly: '+aAssembly.GetCallingAssembly.FullName);
21:
22: // Examina atributos personalizados
23: tni := tn.Nodes.Add('Custom Attributes');
24: arAttrib := aAssembly.GetCustomAttributes(true);
25: for i := Low(arAttrib) to High(arAttrib) do
26: tni.Nodes.Add(arAttrib[i].ToString);
27:
28: // Examina assemblies referenciados
29: tni := tn.Nodes.Add('Referenced Assemblies');
30: arAsmName := aAssembly.GetReferencedAssemblies;
31: for i := Low(arAsmName) to High(arAsmName) do
32: tni.Nodes.Add(arAsmName[i].FullName);
33:
34: // Examina tipos definidos em um assembly
35: tni := tn.Nodes.Add('Assembly Types');
36: arType := aAssembly.GetTypes;
37: for i := Low(arType) to High(arType) do
38: tni.Nodes.Add(arType[i].ToString);
39:
40: // Examina e investiga detalhadamente um assembly
41: arModule := aAssembly.GetModules;
42: tn := tn.Nodes.Add('Modules');
43: for i := Low(arModule) to High(arModule) do
44: ReflectModule(arModule[i], tn);
45: end;

u Localize o código no CD: \Code\Chapter 15\Ex01\.


374 Capítulo 15 API Reflection

A Listagem 15.1 é na verdade parte de uma aplicação reflection maior. Ela realiza al-
gumas das mesmas funções do utilitário Reflection fornecido com o Delphi for .NET.

NOTA
Uma ferramenta excelente para refletir metadados é a Reflector de Lutz Roeder. Esse utilitário não
apenas pode refletir e exibir metadados, mas também pode desassemblar seu código em IL ou
descompilá-lo em outras linguagens .NET. Atualmente, ela suporta o C#, Visual Basic .NET e
Delphi. Você pode obter esse utilitário em
http://www.aisto.com/roeder/dotnet/

O método TWinForm.ReflectAssembly( ) mostrado aqui recebe uma referência a uma


classe de instância Assembly, que é uma referência a uma classe Assembly que foi previa-
mente carregada utilizando o código a seguir:

if (OpenFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.OK) then


begin
Cursor.Current := Cursors.WaitCursor;
try
Assem := Assembly.LoadFrom(OpenFileDialog1.FileName);
ReflectAssembly(Assem);
finally
Cursor.Current := Cursors.Default;
end;
end;

Esse código carrega um assembly especificado por meio de OpenFileDialog1. É necessá-


rio carregar o assembly a fim de refletir seus metadados.
Esse código simplesmente preenche uma TreeView com informações sobre o as-
sembly. A Tabela 15.1 descreve os métodos utilizados na Listagem 15.1.

TABELA 15.1 Membros da classe Assembly


Membro Descrição
FullName Obtém o nome de exibição do assembly. O nome de exibição mostra
as versões mais e menos importantes, números de revisão e de
construção, nome, cultura e chave pública ou token de chave pública.
Location Obtém a localização física do assembly que contém o manifesto.
GlobalAssemblyCache Indica se o assembly foi carregado no GAC retornando true. Caso
contrário, retorna false.
GetCallingAssembly( ) Obtém o assembly cujo método invocou o método atualmente em
execução.
GetCustomAttributes( ) Obtém um array de atributos personalizados definido nesse assembly.
GetReferencedAssemblies( ) Obtém um array de tipos AssemblyName para quaisquer assemblies
referenciados por esse assembly.
GetTypes( ) Obtém todos os tipos definidos nesse assembly.
GetModules( ) Obtém os módulos que compõem esse assembly.
Reflection em um módulo 375

A Tabela 15.1 é uma lista parcial dos métodos e propriedades na classe Assembly. Você
pode examinar a documentação do .NET quanto a uma referência completa sobre a clas-
se Assembly.
Você pode ver que, em alguns casos, os metadados são diretamente obtidos por
meio de uma propriedade ou método. Alguns métodos retornam um array de itens como
os métodos GetReferencedAssemblies( ) e GetModules( ). Nestes casos, como a Listagem 15.1
ilustra, você atribui o resultado desses métodos a um array apropriado e então reflete os
itens no array.
Nas linhas 41–44, a lista de array dos módulos é obtida do objeto assembly. Cada
módulo (module) é então passado para um método ReflectModule( ), que percorre os me-
tadados do módulo. A seção a seguir discute esse tópico.

Reflection em um módulo
A Listagem 15.2 é um exemplo de reflection em classes pertencentes a um módulo de um
assembly.

LISTAGEM 15.2 Refletindo um módulo


1: procedure TWinForm.ReflectModule(aModule: Module; aTn: TreeNode);
2: var
3: arType: array of System.Type;
4: arAttrib: array of System.Object;
5: arFields: array of FieldInfo;
6: arMeth: array of MethodInfo;
7: i: integer;
8: tn: TreeNode;
9: tni: TReeNode;
10: begin
11: tn := aTn.Nodes.Add(aModule.Name);
12: tn.Nodes.Add('Fully Qualified Name: '+aModule.FullyQualifiedName);
13: tn.Nodes.Add('Is Resource: '+ TObject(aModule.IsResource).ToString);
14:
15: // Examina atributos personalizados
16: tni := tn.Nodes.Add('Custom Attributes');
17: arAttrib := aModule.GetCustomAttributes(true);
18: for i := Low(arAttrib) to High(arAttrib) do
19: tni.Nodes.Add(arAttrib[i].ToString);
20:
21: // Examina campos
22: tni := tn.Nodes.Add('Fields');
23: arFields := aModule.GetFields;
24: for i := Low(arFields) to High(arFields) do
25: tni.Nodes.Add(arFields[i].ToString);
26:
27: // Examina módulos
28: tni := tn.Nodes.Add('Methods');
376 Capítulo 15 API Reflection

LISTAGEM 15.2 Continuação


29: arMeth := aModule.GetMethods;
30: for i := Low(arMeth) to High(arMeth) do
31: tni.Nodes.Add(arMeth[i].ToString);
32:
33: // Examina tipos
34: arType := aModule.GetTypes;
35: tn := tn.Nodes.Add('Types');
36: for i := Low(arType) to High(arType) do
37: ReflectType(arType[i], tn);
38: end;

u Localize o código no CD: \Code\Chapter 15\Ex01\.

A Listagem 15.2 é a chamada do método ReflectAssembly( ), mostrado na Listagem


15.1, para cada módulo de um assembly. Semelhante à Listagem 15.1, esse método con-
siste em chamadas a membros da classe module que retorna as informações sobre ele
mesmo. Essas informações são então preenchidas na TreeView.
A Tabela 15.2 descreve os métodos utilizados na Listagem 15.2.

TABELA 15.2 Membros da classe Module


Membro Descrição
FullyQualifiedName Obtém o nome completamente qualificado incluindo o caminho para esse
módulo.
IsResource( ) Indica se o objeto é um recurso.
GetCustomAttributes( ) Obtém um array de atributos personalizados para esse módulo.
GetFields( ) Retorna um array do tipo FieldInfo para campos definidos nesse módulo.
GetMethods( ) Retorna um array do tipo MethodInfo para métodos definidos nesse módulo.
GetTypes( ) Retorna um array de System.Type para tipos definidos nesse módulo.

A Tabela 15.2 é uma lista parcial dos métodos e propriedades na classe Module. Você
pode examinar a documentação do .NET quanto a uma referência completa sobre a clas-
se Module.
A Listagem 15.2 realiza os mesmos tipos de funções contra a classe Module e também
para a classe Assembly na Listagem 15.1. Vários métodos e propriedades são invocados
para obter as informações sobre o módulo. Em alguns casos, essas informações são espe-
cíficas ao módulo; enquanto em outros, um array de certos objetos é obtido que precisa
ser enumerado e examinado.

NOTA
É possível ter “métodos” globais simples (ou rotinas globais e campos globais definidos em um
módulo), mas estes não são compatíveis com CLS e não podem ser definidos em C#, Visual Basic
.NET ou Delphi. A análise dos métodos e campos é mostrada na Listagem 15.2; entretanto, não é
provável que você alguma vez os encontre.
Reflection em tipos 377

As linhas 34–37 na Listagem 15.2 obtêm um array de tipos definidos no módulo.


Cada tipo nessa lista é então passado para o método ReflectType( ), que, como o nome in-
dica, reflete os detalhes sobre o tipo específico. Isso é discutido mais detalhadamente na
seção a seguir.

Reflection em tipos
Esta seção mostra como inspecionar detalhadamente os tipos definidos em um assembly
e como refletir esses tipos.
A Listagem 15.3 ilustra os processos de refletir as informações sobre membros de um
dado tipo.

LISTAGEM 15.3 Refletindo informações sobre os membros de um tipo


1: function GetDeclareString(st: System.Type): String;
2: begin
3: Result := '';
4: if st.IsAbstract then Result := Result + 'abstract ';
5: if st.IsArray then Result := Result + 'array ';
6: if st.IsClass then Result := Result + 'class ';
7: if st.IsContextful then Result := Result + 'context ';
8: if st.IsInterface then Result := Result + 'interface ';
9: if st.IsPointer then Result := Result + 'pointer ';
10: if st.IsPrimitive then Result := Result + 'primitive ';
11: if st.IsPublic then Result := Result + 'public ';
12: if st.IsSealed then Result := Result + 'sealed ';
13: if st.IsSerializable then Result := Result + 'serializable ';
14: if st.IsValueType then Result := Result + 'record ';
15: end;
16:
17: procedure TWinForm.ReflectType(aType: System.Type; aTn: TreeNode);
18: var
19: arMembInfo: array of MemberInfo;
20: arType: array of System.Type;
21: i: integer;
22: tn, fn: TreeNode;
23: begin
24: tn := aTn.Nodes.Add(aType.FullName);
25: tn.Nodes.Add('Base Type: '+aType.BaseType.FullName);
26: tn.Nodes.Add(GetDeclareString(aType));
27: arMembInfo := aType.GetMembers;
28:
29: for i := Low(arMembInfo) to High(arMembInfo) do
30: begin
31: fn := tn.Nodes.Add(System.String.Format('{0}: {1}',
32: arMembInfo[i].MemberType, arMembInfo[i]))
33: end;
34:
378 Capítulo 15 API Reflection

LISTAGEM 15.3 Continuação


35: arType := aType.GetNestedTypes;
36: for i := Low(arType) to High(arType) do
37: ReflectType(arType[i], tn);
38: end;

u Localize o código no CD: \Code\Chapter 15\Ex01\.

As Listagens 15.1, 15.2 e 15.3 estão dentro da mesma aplicação no CD. A Listagem
15.3 consiste em dois métodos, GetDeclareString( ) e ReflectType( ). Essa listagem mostra
um uso parcial dos vários métodos e propriedades disponíveis para refletir as informa-
ções sobre um membro.
A primeira seção do método ReflectType( ), da Listagem 15.3 (linhas 24–25), adicio-
na, no Treeview, o nome completo e o tipo base de cada membro. Ela então passa o mem-
bro para a função GetDeclareString( ). GetDeclareString( ) constrói uma string com base
no nível de acesso do membro, e outras características declarativas, e adiciona a string ao
TreeView.
A segunda seção obtém um array de MemberInfo references para o membro do tipo es-
pecificado chamando o método GetMembers( ). Ela então adiciona a especificação do tipo
ao membro e o nome do membro ao TreeView (linhas 35–36, Listagem 15.3).
A Figura 15.1 ilustra a saída desse programa utilizado nas Listagens 15.1–15.3 ao re-
fletir um outro assembly.

FIGURA 15.1 Saída da aplicação Reflection.


Invocação em tempo de execução de membros de um tipo (vinculação tardia) 379

Invocação em tempo de execução de


membros de um tipo (vinculação tardia)
Vinculação tardia é um processo pelo qual uma aplicação pode invocar um membro de
uma classe (incluindo a criação dessa classe) sem conhecer, em tempo de compilação,
essa classe ou seus métodos. É uma técnica útil, particularmente ao lidar com funcionali-
dade suplementar (add-in) para aplicações. Pense no ambiente de desenvolvimento
Delphi for .NET. Ele, de alguma maneira, precisa determinar as informações sobre as
classes a fim de permitir que você projete aplicações visualmente. É a capacidade de re-
flection realizar a vinculação tardia que permite ao IDE trabalhar com classes que ele não
sabe que existem nos vários assemblies.
Para ilustrar uma estrutura suplementar simples, o exemplo a seguir consiste em
dois assemblies – ambos definem uma classe que contém um único método. É fornecido
ao método o mesmo nome por simplicidade. Uma aplicação iniciada carrega cada as-
sembly separadamente e invoca os métodos nas duas classes. As Listagens 15.4 e 15.5 lis-
tam os assemblies.

LISTAGEM 15.4 Primeiro exemplo de assembly


1: library asmRemInv1;
2: type
3: TClass1 = class
4: public
5: procedure WriteMessage(aMsg: String);
6: end;
7:
8: procedure TClass1.WriteMessage(aMsg: String);
9: begin
10: Console.WriteLine('In asmRemInv1.TClass1: '+aMsg);
11: end;
12:
13: end.

u Localize o código no CD: \Code\Chapter 15\Ex02\.

LISTAGEM 15.5 Segundo exemplo de assembly


1: library asmRemInv2;
2: type
3: TClass1 = class
4: public
5: procedure WriteMessage(aMsg: String);
6: end;
7:
8: procedure TClass1.WriteMessage(aMsg: String);
9: begin
10: Console.WriteLine('In asmRemInv2.TClass1: '+aMsg);
380 Capítulo 15 API Reflection

LISTAGEM 15.5 Continuação


11: end;
12:
13: end.

u Localize o código no CD: \Code\Chapter 15\Ex02\.

Você observará que os dois assemblies contêm a mesma classe, identicamente defi-
nida. A idéia aqui é que a aplicação de uso será capaz de invocar qualquer uma dessas
classes da mesma maneira. A implementação dessas classes talvez seja diferente. Isso é se-
melhante a utilizar uma Interface para definir a assinatura da classe. Por exemplo, consi-
dere uma classe que realize uma estrutura de classificação. Essas classes poderiam ser de-
senvolvidas a fim de realizar dois tipos diferentes de classificação, uma classificação
como bolha e uma por inserção. A aplicação chamadora não precisa conhecer a imple-
mentação.
Nesse nível, a aplicação chamadora mostrada na Listagem 15.6 deve saber algo sobre
as classes que invocará, como o nome da classe e o método da classe. É possível que a
aplicação não saiba nada sobre a classe e, mesmo assim, seja capaz de consultar, por meio
da reflection, vários métodos, determinar seus parâmetros e invocar esses métodos. A
utilidade dessa capacidade, embora poderosa, é difícil de determinar. Em algum nível, a
aplicação que invoca deve saber o que está tentando fazer com a classe que ela está invo-
cando dinamicamente.
A Listagem 15.6 ilustra como essas classes são invocadas a partir da aplicação cha-
madora.

LISTAGEM 15.6 Aplicação que invoca


1: program InvProject;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses
6: System.Reflection,
7: SysUtils;
8:
9: var
10: Assem: Assembly;
11: BFlags: BindingFlags;
12:
13: procedure InvokeType(aNameSpace: String);
14: var
15: obj: System.Object;
16: Meth: MethodInfo;
17: t: System.Type;
18: ParamTypes: array of System.Type;
19: begin
20: t := Assem.GetType(aNameSpace+'.TClass1');
Invocação em tempo de execução de membros de um tipo (vinculação tardia) 381

LISTAGEM 15.6 Continuação


21: Console.WriteLine(t.ToString);
22: obj := Activator.CreateInstance(t);
23: Console.WriteLine(obj.ToString);
24:
25: t.InvokeMember('WriteMessage', BFlags, nil, obj,
26: ['This is the message']);
27:
28: // Um método alternativo é mostrado abaixo.
29: SetLength(ParamTypes, 1);
30: ParamTypes[0] := TypeOf(System.String);
31: Meth := t.GetMethod('WriteMessage', ParamTypes);
32: Console.WriteLine(Meth.ToString);
33: Meth.Invoke(obj, ['This is the message']);
34: end;
35:
36: begin
37: BFlags := BindingFlags.DeclaredOnly or BindingFlags.Public or
38: BindingFlags.Instance or BindingFlags.InvokeMethod;
39:
40: try
41: Assem := Assembly.LoadFrom('asmRemInv1.dll');
42: InvokeType('asmRemInv1');
43:
44: Assem := Assembly.LoadFrom('asmRemInv2.dll');
45: InvokeType('asmRemInv2');
46: except
47: on E: Exception do
48: Console.WriteLine('Error: '+E.Message);
49: end;
50: Console.ReadLine( );
51: end.

u Localize o código no CD: \Code\Chapter 15\Ex02\.

A Listagem 15.6 primeiro carrega os assemblies e então chama a função InvokeType( )


que cria a classe do assembly e então chama sua função WriteMessage( ). Esse código utili-
za algumas construções, que explicaremos.
A classe Activator é utilizada para criar a instância da própria classe. Activator pode ser
utilizada para criar instâncias de classes localmente, remotamente ou a partir de um ob-
jeto COM utilizando alguns dos seus métodos alternativos. Por exemplo, a Listagem 15.6
poderia ter utilizado o método Activator.CreateInstanceFrom( ) para criar a classe sem ter
de carregar o assembly explicitamente. Esse método recebe o nome da classe, um arquivo
assembly identificado e um nome de construtor. Você pode verificar a documentação so-
bre a classe Activator a fim de examinar seus outros métodos.
Depois que uma instância da classe é criada, seu método, WriteMessage( ), é chamado
utilizando a função InvokeMember( ).
A Tabela 15.3 descreve os parâmetros exigidos pela função InvokeMember( ).
382 Capítulo 15 API Reflection

TABELA 15.3 Parâmetros do InvokeMember


Parâmetro Descrição
name Um parâmetro de string que especifica o nome do membro a invocar.
invokeAttr Um tipo BindingFlags que especifica como membros devem ser
pesquisados.
binder Um tipo Binder que especifica como vincular (corresponder) membros e
seus argumentos.
target A instância de tipo alvo (System.Object) em que invocar o membro.
args Um array de argumentos a fim de passar para o método do membro.
modifiers Um array de objetos ParameterModifier – cada um dos quais representa
atributos associados com o item correspondente no array args.
namedParameters Um array que contém os nomes dos parâmetros que são passados como
valores no array args.
culture Uma CultureInfo que é utilizada para especificar a localidade da
globalização.

Observe que o método InvokeMember( ) tem três variantes sobrecarregadas dependen-


do de qual desses parâmetros são exigidos. Na Listagem 15.6, somente os parâmetros
name, invokeAttr, binder, target e args do método são necessários.
O parâmetro invokeAttr é um bitmask que consiste em um ou mais valores de Binding-
Flags que controlam a vinculação e determinam como o membro especificado deve ser
pesquisado. Alguns desses flags especificam o tipo de operação, como o flag Binding-
Flags.InvokeMethod, que indica que um método será chamado. Outros flags especificam o
acesso ao membro a ser pesquisado. Por exemplo, BindingFlags.Public especifica a fim de
incluir membros públicos na pesquisa. Se os membros não-públicos precisarem ser inclu-
ídos na pesquisa, você incluiria o flag BindingFlags.NonPublic. Veremos outros detalhes so-
bre a utilização desses flags na seção a seguir. Uma boa idéia é conhecer os vários flags
que podem ser utilizados com esse parâmetro. Você poderia pesquisá-los na documenta-
ção do .NET.

Invocando os tipos do membro para eficiência


As linhas 29–33 na Listagem 15.6 mostram uma maneira alternativa de invocar o méto-
do do tipo chamando a função GetMethod( ) do tipo. Essa função retorna uma instância de
MethodInfo, que contém um método Invoke( ) que invoca o método especificado.
Esse método é mais eficaz do que chamar InvokeMember( ) se for chamá-lo repetida-
mente. A razão é que InvokeMember( ) precisa pesquisar o membro e, depois de encon-
trá-lo, vinculá-lo toda vez que é chamado. Obtendo uma referência ao tipo do membro,
você acessa diretamente o membro sem a operação de pesquisa/vinculação. A Tabela
15.4 descreve os vários métodos que retornam uma referência ao tipo do membro e quais
dos seus métodos devem ser chamados para invocar esse membro.
Invocação em tempo de execução de membros de um tipo (vinculação tardia) 383

TABELA 15.4 Acessando membros por meio dos seus tipos


Método Retorna Como acessar
GetConstructor( ) ConstructorInfo Invoke( )
GetEvent( ) EventInfo AddEventHandler( )
RemoveEventHandler( )
GetField( ) FieldInfo GetValue( )
SetValue( )
GetMethod( ) MethodInfo Invoke( )
GetProperty( ) PropertyInfo GetValue( )
SetValue( )

Um outro exemplo de invocação de membro


A Listagem 15.7 ilustra um outro exemplo da invocação dos membros de um tipo utili-
zando reflection.

LISTAGEM 15.7 Invocação de membro


1: program InvMemb;
2: uses
3: SysUtils,
4: System.Reflection;
5: {$APPTYPE CONSOLE}
6:
7: type
8: TOnWriteEvent = procedure of object;
9:
10: TMyType = class(TObject)
11: private
12: FIntProp: Integer;
13: FStrProp: String;
14: FOnWriteEvent: TOnWriteEvent;
15: public
16: PublicStr: String;
17: constructor Create(aStr: String; aInt: Integer;
18: aStr2: String); override;
19: procedure WriteSomething;
20: procedure WriteProc;
21: property IntProp: Integer read FIntProp write FIntProp;
22: property StrProp: String read FStrProp write FStrProp;
23: property OnWriteEvent: TOnWriteEvent read FOnWriteEvent
24: write FOnWriteEvent;
25: end;
26:
27: constructor TMyType.Create(aStr: String; aInt: Integer; aStr2: String);
28: begin
384 Capítulo 15 API Reflection

LISTAGEM 15.7 Continuação


29: inherited Create;
30: PublicStr := aStr;
31: FIntProp := aInt;
32: FStrProp := aStr2;
33: OnWriteEvent := WriteProc;
34: end;
35:
36: procedure TMyType.WriteSomething;
37: begin
38: Console.WriteLine('PublicStr: '+PublicStr);
39: Console.WriteLine('FIntProp: '+FIntProp.ToString);
40: Console.WriteLine('FStrProp: '+FStrProp);
41: if Assigned(FOnWriteEvent) then
42: FOnWriteEvent;
43: end;
44:
45: procedure TMyType.WriteProc;
46: begin
47: Console.WriteLine(' – – In WriteProc');
48: end;
49:
50: var
51: tp: System.Type;
52: obj: System.Object;
53: BFlags: BindingFlags;
54: parmAray: array[0..2] of System.Object;
55: s: String;
56: i: Integer;
57: begin
58: BFlags := BindingFlags.DeclaredOnly or BindingFlags.Public
59: or BindingFlags.Instance;
60:
61: tp := TypeOf(TMyType);
62: parmAray[0] := 'hello';
63: parmAray[1] := System.Object(23);
64: parmAray[2] := 'world';
65:
66: try
67: // cria uma instância do tipo
68:
69: obj := tp.InvokeMember('.ctor', BFlags or BindingFlags.CreateInstance,
70: nil, nil,ParmAray);
71: Console.WriteLine(obj.ToString);
72:
73: // Chama o método do tipo
74: (obj as TMyType).WriteSomething;
75: // ou
76: tp.InvokeMember('WriteSomething', BFlags or BindingFlags.InvokeMethod,
Invocação em tempo de execução de membros de um tipo (vinculação tardia) 385

LISTAGEM 15.7 Continuação


77: nil, obj, nil);
78:
79: // Configura/obtém um campo
80: tp.InvokeMember('PublicStr', BFlags or BindingFlags.SetField, nil, obj,
81: ['67 Camaro']);
82:
83: s := String(tp.InvokeMember('PublicStr', BFlags or
84: BindingFlags.GetField, nil, obj, nil));
85:
86: Console.WriteLine(s);
87:
88: // Configura/obtém uma propriedade
89: tp.InvokeMember('IntProp', BFlags or BindingFlags.SetProperty, nil,
90: obj, [23]);
91: tp.InvokeMember('StrProp', BFlags or BindingFlags.SetProperty, nil,
92: obj, ['Coffee']);
93:
94: i := Integer(tp.InvokeMember('IntProp', BFlags or
95: BindingFlags.GetProperty, nil, obj, nil));
96: s := String(tp.InvokeMember('StrProp', BFlags or
97: BindingFlags.GetProperty, nil, obj, nil));
98:
99: Console.WriteLine('Get/Set Property: {0}, {1}', [i, s]);
100:
101: except
102: on E: Exception do
103: Console.WriteLine(E.Message);
104: end;
105:
106: Console.ReadLine;
107: end.

u Localize o código no CD: \Code\Chapter 15\Ex03\.

Nesse exemplo, a classe é definida na mesma aplicação por simplicidade. Esse exem-
plo ilustra como, usando reflection, pode-se invocar vários membros de uma dada classe,
especificamente o TMyType definido nas linhas 10–25.
TMyType declara vários membros como um construtor, uma procedure, um campo e
algumas propriedades.
TMyType.WriteSomething( ) simplesmente grava no console os valores contidos nos vá-
rios membros.
TMyType é criado utilizando uma técnica diferente do que aquela anteriormente ilus-
trada na Listagem 15.6. Em vez de utilizar a classe Activator para criar a classe, chamamos
a função InvokeMember( ) do tipo. Esse exemplo passa como o nome de membro, .ctor,
que é o nome .NET do construtor. De fato, ao declarar um construtor e nomeá-lo Crea-
te( ), o compilador Delphi for .NET irá gerar um construtor chamado .ctor com os parâ-
386 Capítulo 15 API Reflection

metros apropriados (ver Figura 15.2). Na realidade, o primeiro parâmetro é ignorado. De


acordo com a documentação do .NET, ao especificar o flag BindingFlags.CreateInstance, ele
instrui a Reflection a criar uma instância do tipo especificado e chamar o construtor que
corresponde aos argumentos fornecidos. Ele foi incluído aqui para propósitos ilustrati-
vos.
Também observe que BindingFlags.CreateInstance é incluída nos flags passados para
InvokeMember( ). Passando esse flag, InvokeMember( ) localizará o construtor que correspon-
de aos argumentos especificados, invocará esse construtor e retornará uma instância da
classe. InvokeMember( ) também aceita o array de parâmetros requerido pelo construtor de
TmyType.
O código restante simplesmente ilustra como acessar os vários membros de TMyType
por meio da função InvokeMember( ) e passando o flag apropriado junto com o parâmetro
que contém o BindingFlags.

Emitindo MSIL por meio da reflexão


O .NET Framework fornece a capacidade de criar código instantaneamente por meio do
namespace System.Reflection.Emit. O código que você cria com Emit é o código MSIL.
Essencialmente, a funcionalidade nesse namespace oferece a capacidade de criar dinami-
camente um assembly e um módulo dentro desse assembly. Você pode definir dinamica-
mente quaisquer classes e métodos dentro do módulo do assembly e então invocar esses
métodos em runtime (que serão automaticamente compilados pelo JIT para código de
máquina de maneira muito eficaz). Você pode até salvar o assembly em disco para poste-
rior utilização.

FIGURA 15.2 Definição de TMyType.


Emitindo MSIL por meio da reflexão 387

NOTA
Além de ter a capacidade de gerar código MSIL por meio de System.Reflection.Emit, a tecnolo-
gia .NET CodeDom fornecida pelos namespaces System.CodeDom e System.CodeDom.Compiler ofe-
recem a capacidade de gerar código em linguagens como C#, Visual Basic .NET e Delphi (supon-
do que você tem os assemblies licenciados adequados instalados).

Portanto, por que alguém iria querer a capacidade de gerar código instantaneamen-
te? Há várias idéias lançadas sobre essa capacidade. Primeiro, as ferramentas visuais de
design, como .NET e Microsoft Visual Studio.NET, geram código instantaneamente:
Embora isso esteja na forma de uma linguagem .NET, a idéia é a mesma. A possibilidade
de uma ferramenta de design para o usuário final que gere código executável não é in-
concebível. Isso também poderia ser utilizado para gerar “scripts” mais poderosos para
sua aplicação ASP.NET. Será interessante ver onde, e até onde, praticamente, essa tecno-
logia irá.

Ferramentas/classes para emitir MSIL


Basicamente, há quatro tipos de ferramentas que você precisará para gerar código MSIL.
Estas são classes Info no namespace System.Reflection, classes de construtor e classes ILGe-
nerator e OpCodes.
As classes Info são definidas no namespace System.Reflection. Os exemplos dessas
classes são MethodInfo, EventInfo, FieldInfo e assim por diante.
As classes de construtor são definidas no namespace System.Reflection.Emit. Essas
classes são utilizadas para definir e criar partes específicas do assembly, como o próprio
assembly, o módulo e um método. A Tabela 15.5 descreve as várias classes de construtor.

TABELA 15.5 Classes de construtor em System.Reflection.Emit


Classe de construtor Descrição
AssemblyBuilder Utilizada para definir e construir um assembly dinâmico.
ConstructorBuilder Utilizada para definir e construir um construtor dinâmico para uma classe.
CustomAttributeBuilder Utilizada para definir e construir um atributo dinâmico personalizado.
EnumBuilder Utilizada para definir e construir um tipo enumerado dinâmico.
EventBuilder Utilizada para definir e construir um evento dinâmico para uma classe.
FieldBuilder Utilizada para definir e construir um campo dinâmico para uma classe.
LocalBuilder Utilizada para definir e construir uma variável local dinâmica para um
método.
MethodBuilder Utilizada para definir e construir um método dinâmico para uma classe.
ModuleBuilder Utilizada para definir e construir um módulo dinâmico para um assembly.
ParameterBuilder Utilizada para definir e associar parâmetros.
PropertyBuilder Utilizada para definir e construir propriedades dinâmicas para uma classe.
TypeBuilder Utilizada para definir e construir registros e classes dinâmicas (tipos por
valor).
388 Capítulo 15 API Reflection

A ILGenerator é uma classe especial utilizada para gerar código MSIL. Lembre-se de
que MSIL é a entrada para o compilador JIT.
OpCodes são instruções MSIL específicas e são representadas pela classe OpCodes no na-
mespace System.Reflection.Emit.

Processo de emissão
Emitir código não é diferente de escrever código no sentido de que as tarefas envolvidas são
quase seqüenciais. Por exemplo, você primeiro cria seu assembly. Você então cria o módulo.
Por fim, você cria sua classe e define as especificidades dessas classes como membro, método
e assim por diante. Esse processo será mais bem ilustrado por meio de um exemplo.

Um exemplo de System.Reflection.Emit
A Listagem 15.8 ilustra as tarefas envolvidas na utilização das classes definidas em
System.Reflection.Emit para gerar código MSIL.

LISTAGEM 15.8 Exemplo da utilização de System.Reflection.Emit


1: program EmitDemo;
2: {$APPTYPE CONSOLE}
3:
4: uses
5: System.Reflection,
6: System.Reflection.Emit;
7:
8: var
9: appDmn: AppDomain;
10: asmName: AssemblyName;
11: asmBuilder: AssemblyBuilder;
12: modBuilder: ModuleBuilder;
13: typBuilder: TypeBuilder;
14: methBuilder: MethodBuilder;
15: ilGen: ILGenerator;
16: newType: System.Type;
17:
18: procedure CreateWLCall;
19: var
20: parms: array of System.Type;
21: methInfo: MethodInfo;
22: wlTypes: array of System.Type;
23: SysConsoleType : System.Type;
24: begin
25: SetLength(Parms, 1);
26: Parms[0] := typeof(System.String);
27:
28: methBuilder := typBuilder.DefineMethod('Main', MethodAttributes.Public
29: or MethodAttributes.Static, typeof(integer), System.Type.EmptyTypes);
30:
31: ilGen := methBuilder.GetILGenerator;
Emitindo MSIL por meio da reflexão 389

LISTAGEM 15.8 Continuação


32:
33: SetLength(wltypes, 1);
34: wltypes[0] := typeof('System.String');
35: methInfo := typeof(System.Console).GetMethod('WriteLine', wlTypes);
36:
37: if methInfo = nil then
38: raise Exception.Create('unable to find WriteLine');
39:
40:
41: ilGen.Emit(Opcodes.Ldstr, 'Delphi for .NET');
42: ilGen.Emit(OpCodes.Call, methInfo);
43: ilGen.Emit(OpCodes.Ldc_I4_0);
44: ilGen.Emit(OpCodes.Ret);
45: end;
46:
47: begin
48: asmName := AssemblyName.Create;
49: asmName.Name := 'D4DNeDevGuideAsm';
50:
51: appDmn := AppDomain.CurrentDomain;
52: asmBuilder := appDmn.DefineDynamicAssembly(AsmName,
53: AssemblyBuilderAccess.Save);
54:
55: modBuilder := asmBuilder.DefineDynamicModule(asmName.Name,
56: 'd4dmasm.exe');
57: typBuilder := ModBuilder.DefineType('Class1');
58: CreateWLCall;
59:
60: asmBuilder.SetEntryPoint(methBuilder, PEFileKinds.ConsoleApplication);
61:
62: newType := typBuilder.CreateType( );
63: asmBuilder.Save('d4dmasm.exe');
64: System.Console.Write('done');
65: end.

u Localize o código no CD: \Code\Chapter 15\Ex04\.

A Listagem 15.8 gera o código IL para um assembly simples que contém uma classe
com um método main que grava uma instrução no console.
O bloco main da Listagem 15.8 realiza as funções da criação do assembly dinâmi-
co, módulo e o tipo para essa classe. Como parte desse processo, a função CreateWL-
Call( ) é invocada (linha 58), o que realiza o processo de emitir o código para o méto-
do em si.
Em geral, entidades dinâmicas são definidas dentro do contexto de uma outra enti-
dade. Por exemplo, um assembly é criado dentro do contexto de um AppDomain. Um mó-
dulo é criado dentro do contexto de um assembly. Um tipo é criado dentro do contexto
de um módulo e assim por diante. A Listagem 15.8 demonstra essa estrutura.
390 Capítulo 15 API Reflection

Como afirmado, um assembly dinâmico precisa ser criado dentro do contexto de


um AppDomain, assim esse exemplo utiliza o AppDomain atual (Linha 51). Para criar o as-
sembly, o método AppDomain.DefineDynamicAssembly( ) é invocado. Há várias versões sobre-
carregadas desse método que retornam um AssemblyBuilder. A versão utilizada nesse
exemplo recebe os tipos de parâmetro AssemblyName e AssemblyBuilderAccess. AssemblyBuil-
derAccess é um tipo enumerado cujos valores ditam o nível de acesso do assembly dinâ-
mico. Valores válidos são Run, RunAndSave e Save, os quais representam um assembly que
pode ser executado, executado e salvo ou somente salvo, respectivamente. O exemplo
aqui só salvará o assembly em disco, portanto o valor passado é Save.
A próxima tarefa é construir o módulo (linhas 55–56). Isso ocorre chamando o método
AssemblyBuilder.DefineDynamicModule( ). Esse método também tem variações sobrecarregadas e
retorna uma classe ModuleBuilder. A versão utilizada aqui passa o nome do assembly e o nome
do arquivo para o qual o assembly será persistido. O método para realmente salvar o as-
sembly é chamado mais tarde e deve utilizar o mesmo nome de arquivo utilizado aqui.
Depois que a ModuleBuilder é obtida, os tipos podem ser definidos dentro dela. A linha
57 cria uma classe chamada "Class1" e a próxima linha chama a procedure CreateWL-
Call( ), que cria um método para essa classe – mais detalhes sobre isso a seguir.
Depois de o método ser criado e associado com uma classe MethodBuilder, ele é configura-
do como o ponto de entrada para o assembly chamando o método AssemblyBuil-
der.SetEntryPoint( )(linha 60). Esse método recebe a classe MethodBuilder e um tipo PEFileKinds
como um parâmetro. PEFileKinds é um tipo enumerado que especifica o tipo de arquivo PE
que é gerado a partir do assembly dinâmico. Seus valores podem ser ConsoleApplication, DLL e
WindowApplication. Esse exemplo gera uma aplicação console.
Por fim, as linhas 62 e 63 criam e salvam o tipo em disco. Nesse ponto, pode-se real-
mente executar o arquivo.
A procedure MethodBuilder( ) é onde as classes ILGenerator e OpCodes são utilizadas.
As linhas 28–29 invocam o método TypeBuilder.DefineMethod( ), que retorna uma clas-
se MethodBuilder . DefineMethod( ) recebe o nome do método, atributos do método, o tipo de
retorno e um array de tipos de parâmetros. Ao definir um método sem parâmetros, você
pode passar o tipo System.Type.EmptyTypes, que é um array vazio predefinido de tipos.
Dada uma classe MethodBuilder, agora o código pode emitir o código para o método. Na
verdade, isso é apenas o processo de escrever o código IL que você digitaria se estivesse co-
dificando em MSIL. Nesse caso, você pode utilizar as informações dos tipos usando reflec-
tion para gerar o código IL. A linha 31 obtém uma instância ILGenerator. A primeira OpCode
especificada é LdStr, que carrega uma referência de string na pilha. Ela então utiliza reflecti-
on para localizar o método info do método System.Console.WriteLine( ) – especificamente, a
versão que recebe um único parâmetro de string. System.Type.GetMethod( ) é utilizado
para obter a classe MethodInfo desse método. Com MethodInfo, ILGenerator.Emit( ) define o
Call OpCode para o método WriteLine( ) (linha 42). Por último, o método é retornado emitin-
do o Ret OpCode (linha 44) depois de posicionar seu valor de zero na pilha (linha 43).

NOTA
A classe IlGenerator tem um método auxiliar, EmitWriteLine( ), que pode ser utilizado para sim-
plificar a emissão de MSIL a um método WriteLine( ). A Listagem 15.8 utiliza uma abordagem
mais longa para propósitos ilustrativos.
NESTE CAPÍTULO
CAPÍTULO 16 — Por que ter
interoperabilidade?

Interoperabilidade – — Questões comuns de


interoperabilidade
COM Interop e o — Utilizando objetos COM
no código .NET
Platform Invocation — Utilizando objetos .NET
no código COM
Service — Utilizando rotinas
exportadas de DLLs do
por Brian Long Win32 no código .NET
— Utilizando de rotinas .NET

A maior parte deste livro focaliza como construir no código Win32

código no Delphi 8 for .NET e como utilizar o código


.NET em outros assemblies construídos em qualquer
linguagem .NET. Entretanto, o .NET também oferece
suporte para combinar código .NET com código Win32
regular – tanto COM como rotinas simples em DLL. Este
capítulo explora o suporte à interoperabilidade
oferecida pela plataforma .NET em todos os seus
aspectos depois de primeiro entendermos por que ela
está disponível.

Por que ter interoperabilidade?


Para a maioria dos desenvolvedores em Delphi que
codificam no Delphi for Win32, a plataforma Microsoft
.NET é um ambiente de programação bem recente e um
desafio razoavelmente novo. Ao pensar em mover um
sistema para uma nova plataforma, normalmente não é
prático pensar em reimplementar todo o sistema em
uma única etapa. Levaria muito mais tempo e seria
difícil encontrar organizações que tenham condições de
parar o desenvolvimento avançado de um sistema
somente para movê-lo para uma nova plataforma. A
VCL.NET fornecida no Delphi for .NET é concebida
para ajudar a mover aplicações do Win32 para o .NET
em um só processo, mas as coisas nem sempre são tão
simples e diretas; sempre há empecilhos que se
transformam em grandes problemas, o que significa que
certas áreas do sistema provam ser complexas para
portar para a outra plataforma.
392 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Há outras considerações, por exemplo, todas aquelas DLLs de terceiros e objetos


COM que você utiliza no seu sistema. Como estes são portados para o .NET? Felizmente,
a Microsoft previu todas essas questões e trabalhou em soluções no .NET Framework logo
no início. A Microsoft leva em consideração o fato de que poucas pessoas podem ter con-
dições de mover sistemas inteiros para uma nova plataforma de uma só vez e de que há
uma base maciça de código Win32 existente na forma de aplicações, objetos COM e
DLLs. Conseqüentemente, a Microsoft assegurou que a interoperabilidade entre código
.NET e código Win32 fosse bem suportada logo na distribuição inicial do .NET 1.0 em fe-
vereiro de 2002.
A interoperabilidade entre .NET e Win32 é de duas vias. Isso significa que você pode
utilizar fragmentos do código .NET em aplicações Win32 existentes e que você também
pode utilizar o código Win32 em novas aplicações .NET. Assim, você pode fazer uma a-
valiação movendo pequenas partes da funcionalidade no seu sistema para o .NET ou
pode iniciar uma aplicação .NET e utilizar partes da sua base de código Win32.
Especificamente, o .NET oferece quatro caminhos à interoperabilidade – que iremos
explorar à medida que avançamos por este capítulo.
Ao desenvolver uma aplicação .NET, você pode
— utilizar objetos COM via Runtime Callable Wrappers (RCWs)

— utilizar rotinas exportadas de DLLs do Win32 utilizando o Platform Invocation


Service (ou P/Invoke)
Ao escrever aplicações Win32, você pode
— consumir objetos .NET como se eles fossem objetos COM regulares via COM
Callable Wrappers (CCWs)
— utilizar rotinas expostas a partir dos assemblies .NET com o Inverse P/Invoke.

NOTA
Ao examinar o código Win32 que é chamado a partir do .NET, ou o que poderia chamar código
.NET, os exemplos serão apresentados utilizando o Delphi 7 visto que ele é conhecido pela maioria
dos leitores e é fornecido na caixa do produto Delphi 8 for .NET. Naturalmente, os princípios tam-
bém são igualmente aplicáveis a todas as outras linguagens Win32.

Questões comuns de interoperabilidade


As principais questões que surgem ao utilizar qualquer um desses mecanismos de interope-
rabilidade têm dois aspectos. Primeiro, você deve tornar o código em um dos lados do limite
gerenciado/não-gerenciado visível ao código do outro lado; do contrário, ele não pode ser
chamado. Em um ou outro grau, o .NET fornece opções para isso em todos os casos.
Segundo, os parâmetros precisam ser empacotados [marshaled] de um lado a outro
no limite gerenciado/não-gerenciado para suprir a diferença entre tipos de dados geren-
ciados e não-gerenciados. Afinal de contas, um PChar no Win32 é notavelmente diferente
de uma classe String ou StringBuilder no .NET. O Interop Marshaler faz um bom trabalho
em fornecer empacotamento padrão para a maioria dos tipos de parâmetros que prova-
velmente iremos utilizar.
Utilizando objetos COM no código .NET 393

Uma questão não tão óbvia se relaciona à segurança do código gerenciado que chama
código não-gerenciado. Claramente, o código Win32 não-gerenciado está fora do esco-
po da segurança incorporada ao .NET e poderia causar danos incontáveis (por exemplo,
excluir arquivos ou formatar unidades). Conseqüentemente, chamar código não-geren-
ciado a partir de código gerenciado requer permissão para código não-gerenciado. Em
uma instalação .NET padrão, a zona My Computer (aplicações em execução na máquina
local) tem permissão para código não-gerenciado, mas as zonas Internet e Local intranet
não, portanto aplicações executadas dessas zonas não teriam permissão de chamar código
não-gerenciado. Você precisar estar ciente de que a zona Local intranet inclui aplicações
executadas a partir de compartilhamentos de rede.
Iniciaremos investigando os dois mecanismos de interoperabilidade entre
COM/.NET, normalmente chamados suporte COM Interop. Nestes casos, e com empa-
cotamento de parâmetros, também devemos estar cientes de que precisa haver uma re-
conciliação entre os dois mecanismos de gerenciamento de tempo de vida utilizados nas
duas plataformas de programação. O COM utiliza uma contagem de referência e o .NET
utiliza um garbage collection. Mais uma vez, o COM Interop rejeita basicamente essas di-
ferenças assegurando que as classes invólucros (wrappers) que ele produz para os progra-
mas ocultem essas questões.

Utilizando objetos COM no código .NET


Primeiro examinaremos o código .NET utilizando objetos COM. Antes de examinarmos
objetos COM arbitrários que implementam a interface IUnknown e outras interfaces persona-
lizadas, investiremos algum tempo examinando a utilização dos objetos que suportam Au-
tomation – em outras palavras, o caso especial dos objetos COM que implementam
IUnknown e IDispatch e outras interfaces personalizadas. A utilização de Automation é muito
comum no código Win32 uma vez que servidores Automation estão amplamente disponí-
veis. As aplicações que compõem o Microsoft Office são bons exemplos de servidores Au-
tomation que oferecem uma rica hierarquia de objetos que suportam Automation.

Automation e vinculação tardia


No Delphi for Win32, orquestramos uma sessão Automation com vinculação tardia (late
bound) utilizando uma Variant. Uma Variant pode ser inicializada com uma chamada a
CreateOleObject( ) (definido na unit ComObj), ao qual é passado um ProgID ou class string do
objeto Automation. Por exemplo, o ponto de entrada (entry point) da hierarquia Micro-
soft Word Automation é ProgID Word.Application'. CreateOleObject( ) retorna uma referên-
cia à implementação da interface IDispatch no objeto Automation instanciado que é de-
vidamente armazenado na variável Variant.
Você pode tratar a Variant como se ela fosse o próprio objeto Automation, chamando
métodos nela e lendo/gravando suas propriedades. Ao realizar Automation por meio de
Variant, você pode tirar proveito de aspectos interessantes como omissão de parâmetros
opcionais, utilização de parâmetros identificados e assim por diante. Todas as suas ins-
truções são compiladas como registros dispatch especiais e executadas em tempo de exe-
cução por um fragmento de código na unit ComObj e assim o objeto Automation dança de
394 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

acordo com sua melodia – isto é, a menos que algo dê errado em uma das suas chamadas;
caso em que, você estará sujeito a uma exceção em tempo de execução.
A razão de descrever a abordagem do Delphi for Win32 aqui é para compará-la com
a abordagem requerida no Delphi for .NET. Embora o suporte geral a Variants tenha sido
implementado na unit Borland.Vcl.Variants, a “mágica” especial que permite fazer uma
Automation com vinculação tardia por meio de Variant não ocorreu na primeira distri-
buição .NET do Delphi. Conseqüentemente, o código Automation com vinculação tar-
dia deve ser escrito utilizando reflection, assim ele será diferente (e de fato um pouco
mais prolixo) quando você o escreve para o .NET. E embora ainda seja possível omitir pa-
râmetros opcionais ou parâmetros com valores padrão, não temos mais o luxo de utilizar
parâmetros identificados.
A Listagem 16.1 é um exemplo simples do uso do Microsoft Word por meio de Auto-
mation. Esse primeiro trecho, em uma aplicação WinForms, simplesmente instancia o
objeto Automation de primeiro nível normal no Microsoft Word.

LISTAGEM 16.1 Criando uma instância do Microsoft Word por meio de Automation
1: var
2: MSWordType: &Type;
3: MSWord: TObject;
4: ...
5:
6: //Instancia o Microsoft Word
7: MSWordType := &Type.GetTypeFromProgID('Word.Application', True);
8: MSWord := Activator.CreateInstance(MSWordType);

u Localize o código no CD: Ch16\Ex01\.

A primeira instrução na linha 7 utiliza o método GetTypeFromProgID estático na classe


System.Type para criar um novo objeto System.Type que será associado com o ProgID do
Word. O objeto retornado é o mecanismo por meio do qual os membros de quaisquer
objetos Word Automation serão invocados. A versão sobrecarregada particular de GetType-
FromProgID utilizada aqui tem um segundo parâmetro que especifica se uma exceção deve
ser levantada caso o ProgID não esteja registrado no sistema.
A linha 8 utiliza o método CreateInstance( ) estático da classe System.Activator para
instanciar a CoClass representada pela classe MSWordType System.Type. O resultado, armaze-
nado no MSWord, representa a instância do objeto Automation.
Tendo obtido o objeto Automation, agora poderemos utilizar seus métodos e proprie-
dades como desejamos. A Listagem 16.2 torna o Word visível nas linhas 6 e 7. (Ele, por
padrão, inicia oculto quando carregado por meio do Automation.) Em seguida, ela cria
um novo documento nas linhas 10 a 13. (Inicia sem nenhum documento no Automa-
tion.) Por fim, ela adiciona algum texto (linhas 16 a 20), a data (linhas 23 a 25) e um en-
ter (carriage return) (linhas 28 e 29).
Utilizando objetos COM no código .NET 395

LISTAGEM 16.2. Automation com vinculação tardia no Microsoft Word


1: var
2: Documents, Selection: TObject;
3: ...
4: //Configura a propriedade Visible
5: //MSWord.Visible := True;
6: MSWordType.InvokeMember(
7: 'Visible', BindingFlags.SetProperty, nil, MSWord, [True]);
8: //Cria um documento
9: //MSWord.Documents.Add
10: Documents := MSWordType.InvokeMember(
11: 'Documents', BindingFlags.GetProperty, nil, MSWord, nil);
12: Documents.GetType.InvokeMember(
13: 'Add', BindingFlags.InvokeMethod, nil, Documents, nil);
14: //Grava algum texto
15: //MSWord.Selection.TypeText('Hello world! Time is: ');
16: Selection := MSWordType.InvokeMember(
17: 'Selection', BindingFlags.GetProperty, nil, MSWord, nil);
18: Selection.GetType.InvokeMember(
19: 'TypeText', BindingFlags.InvokeMethod, nil, Selection,
20: ['Hello world! Time is: ']);
21: //Insere data/hora
22: //MSWord.Selection.InsertDateTime('dddd, dd MMMM yyyy', False);
23: Selection.GetType.InvokeMember(
24: 'InsertDateTime', BindingFlags.InvokeMethod, nil, Selection,
25: ['dddd, dd MMMM yyyy', False]);
26: //Adiciona CR/LF
27: //MSWord.Selection.TypeParagraph;
28: Selection.GetType.InvokeMember(
29: 'TypeParagraph', BindingFlags.InvokeMethod, nil, Selection, nil);

u Localize o código no CD: \Chapter 16\Ex01\.

A fim de chamar um método ou ler/gravar uma propriedade, devemos utilizar o mé-


todo InvokeMember( ) do nosso objeto System.Type. O primeiro parâmetro especifica o méto-
do ou nome da propriedade que será consultada em tempo de execução e o segundo
parâmetro é membro do tipo enumerado BindingFlags e indica se um método está sendo
chamado ou uma propriedade está sendo lida ou gravada. Isso afeta a maneira como o
método IDispatch.Invoke( ) do objeto Automation subjacente é chamado em tempo de
execução.
O terceiro parâmetro não é necessário aqui, mas especifica uma classe Binder, às ve-
zes utilizada ao fazer reflection. Os parâmetros finais fornecem a instância do objeto
Automation e todos os parâmetros que poderiam ser necessários (passados como um
array).
Observe que um objeto System.Type para objetos adicionais retornados pelas proprie-
dades Word podem ser acessados via o método GetType( ), como utilizado nas linhas 12,
18, 23 e 28.
396 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Valor, referência e parâmetros opcionais


As coisas são bem simples e diretas com os parâmetros de valor utilizados até agora.
Entretanto, elas tornam-se mais complexas quando o servidor Automation suporta um
método que aceita parâmetros de referência uma vez que você deve utilizar uma versão
sobrecarregada de InvokeMember que aceita mais parâmetros. Um desses parâmetros é
um array contendo uma única estrutura ParameterModifier. Esse objeto precisa ser cons-
truído com um valor que indique quantos parâmetros o método recebe para em seguida
alocar um Boolean array com esse número de elementos – um para representar cada parâ-
metro de método. O array é exibido pela propriedade array default Item, e, antes de cha-
mar InvokeMember( ), você deve preencher cada elemento com um valor. Se o parâmetro
correspondente for um parâmetro de valor, passe False; se for um parâmetro de referên-
cia, passe True.
Para exemplificar, considere um objeto COM que implementa a interface COM fictí-
cia mostrada na Listagem 16.3. O primeiro método simplesmente recebe um parâmetro
de valor, assim ele pode ser chamado utilizando o mesmo tipo de código na Listagem
16.2. Entretanto, o segundo método tem um parâmetro de valor e um parâmetro de refe-
rência – nesse caso, um parâmetro de saída. Ele também tem um valor de retorno, por-
tanto devemos ver algum código Delphi 7 que mostra como utilizá-lo.

LISTAGEM 16.3 Uma interface COM de exemplo no Delphi 7


1: ICOMInterface = interface(IUnknown)
2: ['{8E9B4AAE-5290-4360-AA38-97EA7E43375E}']
3: procedure One(const Msg: WideString); safecall;
4: function Two(Input: Integer; out Output: Integer): WordBool; safecall;
5: end;

u Localize o código no CD: \Chapter 16\Ex02\InProcCOMServer\.

A Listagem 16.4 mostra o trecho do código relevante para chamar o segundo méto-
do de interface a partir do projeto dotNetAppLateBound.bdsproj.

ATENÇÃO
Não se esqueça de registrar o servidor COM antes de testar esse exemplo. Você pode fazer isso
com a linha de comando a seguir:

regsvr32 ComServer.dll
Você também pode fazer isso na IDE do Delphi 7 utilizando o item de menu Run, Register ActiveX
Server.

LISTAGEM 16.4 Automation com vinculação tardia em um objeto COM personalizado


1: var
2: TwoArgs: array[0..1] of TObject;
3: MethodResult: TObject;
4: ParamModifiers: array[0..0] of ParameterModifier;
Utilizando objetos COM no código .NET 397

LISTAGEM 16.4 Continuação


5: RefParam: Integer;
6: const
7: ValParam = 45;
8: ...
9: //Configura uma chamada de método com um parâmetro de valor e um parâm. de referência
10: TwoArgs[0] := TObject(Integer(ValParam)); //passa por valor
11: RefParam := 0;
12: TwoArgs[1] := TObject(RefParam); //passa por referência
13: //o modificador tem itens para cada parâmetro
14: ParamModifiers[0] := ParameterModifier.Create(2);
15: ParamModifiers[0][0] := False; //o primeiro argumento é por valor
16: ParamModifiers[0][1] := True; //o segundo argumento é por referência
17: MethodResult := ComObjType.InvokeMember(
18: 'Two', BindingFlags.InvokeMethod, nil, ComObj,
19: TwoArgs, ParamModifiers, nil, nil);
20: if Boolean(MethodResult) then
21: MessageBox.Show(System.String.Format('Twice {0} is {1}', TwoArgs));

u Localize o código no CD: \Chapter 16\Ex02\.

O código configura uma variável para armazenar o valor do parâmetro de referência


na linha 11. Tanto os parâmetros de valor como os parâmetros de referência são então
colocados no array de argumentos nas linhas 10 e 12. A linha 10 recebe um valor literal
definido pela constante ValParam que a converte em um Integer antes de convertê-la em
um objeto. Isso ocorre para assegurar que o compilador saiba o tipo de item antes da
coerção em TObject, cujo efeito é empacotar o tipo por valor inteiro em um objeto; a coer-
ção para inteiro é necessária para assegurar que o tipo por valor correto (que corresponde
ao parâmetro do método requerido) seja empacotado (boxed). O valor modificado do
parâmetro de referência estará em TwoArgs[1] depois da chamada de método, não na va-
riável RefParam. As linhas 14–16 configuram o objeto modificador de parâmetro e as li-
nhas 17–19 fazem a chamada de método de vinculação tardia.
Alguns métodos no Automation oferecem parâmetros que podem ser omitidos por-
que eles são marcados como parâmetros opcionais ou têm um valor padrão. No Delphi 7,
você poderia utilizar a variável EmptyParam em vez de um desses parâmetros se ela fosse re-
querida; entretanto, isso raramente acontecia com código Automation por meio de Va-
riants devido ao suporte para parâmetros identificados nos compiladores do Delphi
Win32. No .NET, o equivalente a EmptyParam é System.Type.Missing.

Vinculação inicial no COM


A fim de fazer vinculação inicial (early binding ) no COM, você precisa de algo extra
além do servidor COM e sua type library. O compilador precisa vincular diretamente a
alguma representação das interfaces. Como estamos lidando com um compilador .NET,
você precisará criar de alguma forma um assembly para ajudar o compilador. Esse as-
sembly é chamado Interop Assembly e pode ser gerado pela type library por meio de al-
gumas classes de suporte fornecidas no Microsoft .NET Framework. Felizmente, isso é fei-
398 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

to automaticamente pela IDE do Delphi quando você adiciona uma referência à type li-
brary do servidor COM alvo.
Como um exemplo, vamos examinar a utilização da Microsoft Speech API (SAPI). A
versão 5.0 ou 5.1 da SAPI é implementada como alguns objetos COM em um servidor
COM in-proc chamado sapi.dll. No Windows XP e versões superiores, a SAPI 5 é
pré-instalada, mas em versões anteriores ela deve ser instalada separadamente. Você
pode encontrar os arquivos de instalação apropriados da SAPI 5.1, incluindo vozes de
exemplo, nos links disponíveis em http://www.microsoft.com/speech/download/sdk51.
Para criar uma aplicação simples de fala no Delphi for .NET, você deve adicionar
uma referência à type library da SAPI ao seu projeto (a type library é vinculada ao servi-
dor COM em formato binário). Muito semelhante à adição de uma referência a um as-
sembly .NET regular do seu projeto, você também pode adicionar uma referência a uma
type library COM (um arquivo type library independente ou um contido dentro do pró-
prio servidor COM). Na caixa de diálogo Add Reference – obtida por meio de Project, Add
Reference ou clicando com o botão direito do mouse no nó References no Project Mana-
ger e escolhendo Add Reference – a página Imports do COM é onde encontramos uma
lista de todas as type libraries COM registradas e de onde também podemos pesquisar
type libraries não-registradas. Localize a Microsoft Speech Object Library versão 5.0 ou
5.1 e clique em Add Reference para adicioná-la à lista New References como mostrado na
Figura 16.1.
Depois de pressionar OK, a referência é adicionada junto com outras referências no
Project Manager, como ilustrado na Figura 16.2. Observe que ela não é adicionada como
uma referência a sapi.dll, mas a um Interop Assembly chamado Interop.SpeechLib.dll.
Com a referência adicionada, agora podemos utilizar os objetos COM nas aplicações
.NET. O Interop Assembly define um namespace que, nesse caso, é SpeechLib. Examinare-
mos os Interop Assemblies em mais detalhes, incluindo seu esquema de atribuição de
nomes, nomes de namespaces e conteúdo depois de analisarmos este exemplo.

FIGURA 16.1 Importando uma biblioteca de tipos COM.


Utilizando objetos COM no código .NET 399

FIGURA 16.2 Project Manager com referência a um COM Interop Assembly.

A Listagem 16.5 mostra um código simples que cria um objeto COM que implemen-
ta a interface ISpVoice (ver linha 16) e então utiliza o método Speak básico para fazê-lo fa-
lar em voz alta o texto escrito na caixa de texto txtWords (linha 22). Observe que a variável
na verdade é definida como SpVoice, não como ISpVoice, por razões que iremos explicar a
seguir (embora a ISpVoice também seja válida). O flag SVSFDefault é parte do tipo enume-
rado SpeechVoiceSpeakFlags da type library original e faz com que a chamada retorne so-
mente depois que o texto foi falado. Ela é uma aplicação simples, mas você pode se diver-
tir bastante compondo frases interessantes, perguntas e comentários a ser pronunciados
pelo mecanismo de fala.

LISTAGEM 16.5 Utilização simples da Microsoft Speech API 5.x


1: uses
2: SpeechLib,
3: ...
4:
5: type
6: TMainForm = class(System.Windows.Forms.Form)
7: ...
8: private
9: Voice: SpVoice;
10: ...
11: end;
12: ...
13: procedure TMainForm.TMainForm_Load(sender: System.Object;
400 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

LISTAGEM 16.5 Continuação


14: e: System.EventArgs);
15: begin
16: Voice := SpVoiceClass.Create;
17: end;
18:
19: procedure TMainForm.btnSay_Click(sender: System.Object;
20: e: System.EventArgs);
21: begin
22: Voice.Speak(txtWords.Text, SpeechVoiceSpeakFlags.SVSFDefault);
23: end;

u Localize o código no CD: Chapter 16\Ex03\.

Assemblies de interoperabilidade
O ato de adicionar uma referência a uma biblioteca de tipos de um projeto Delphi for
.NET (normalmente) faz com que a IDE gere um Interop Assembly utilizando o suporte a
importação de type libraries do .NET Framework. Se utilizar o compilador de linha de co-
mando, você mesmo precisará seguir esse passo, como discutido a seguir.
A type library da SAPI 5.x é vinculada a sapi.dll do servidor COM in-proc, mas a type
library especifica que o nome da biblioteca seja realmente SpeechLib. Isso pode ser verifi-
cado abrindo o arquivo que contém a type library no Delphi for .NET; o nome da biblio-
teca é mostrado como o nó de primeiro nível na hierarquia da type library, como mostra-
do na Figura 16.3.

FIGURA 16.3 A type library da Microsoft SAPI 5.x.


Utilizando objetos COM no código .NET 401

NOTA
Os servidores COM criados com versões Win32 do Delphi e C++Builder assumem o padrão do
nome do projeto como o nome da biblioteca, de modo que haja menos confusões entre o nome
do servidor COM e o nome do Interop Assembly.

Esse nome de biblioteca é utilizado como a base do nome do Interop Assembly e do


namespace interno. A convenção dita que os Interop Assemblies devem ser prefixados
com Interop e um ponto, fazendo com que o Interop Assembly da SAPI 5.x seja chamado
Interop.SpeechLib.dll que contém os itens dentro de um namespace da SpeechLib.

I N T E R O P A SSE M B LI E S NA I D E
A IDE cria os Interop Assemblies em um subdiretório do seu diretório de projeto chamado
ComImports. Quando a aplicação é carregada, o Interop Assembly normalmente não é encontrado
nesse local. (Ele não é instalado no Global Assembly Cache e não está em um diretório que seria
encontrado investigando a base da aplicação ou diretórios de cultura.) Por causa disso, a referên-
cia ao Interop Assembly no Project Manager configura sua opção Copy Local no menu de contex-
to, como mostrado na Figura 16.4, e é exibida no Object Inspector (ver Figura 16.2).

FIGURA 16.4 Os Interop Assemblies utilizam a opção Copy Local.

A opção Copy Local significa que o Interop Assembly será copiado para o diretório de saída do pro-
jeto a fim de assegurar que ele seja compilado se executado na máquina de desenvolvimento.
Entretanto, ainda deveremos levar em consideração o que acontece em máquinas nas quais a apli-
cação é instalada. Você pode implantar o Interop Assembly no mesmo diretório da aplicação (ou
em um diretório que será localizado por uma investigação) ou você pode instalá-lo no Global
Assembly Cache. Como uma alternativa, você pode definir um arquivo de configuração de aplica-
ção que informe o .NET onde encontrar o assembly. Uma opção final envolve a especificação de
um Primary Interop Assembly para o servidor COM, como veremos um pouco mais adiante.
402 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Criando um assembly de interoperabilidade


A IDE faz um bom trabalho ao criar Interop Assemblies, como vimos anteriormente.
Entretanto, alguns desenvolvedores precisam saber como realizar essas operações sozi-
nhos – por exemplo, a fim de criar sistemas de construção em lote que utilizam ferra-
mentas de linha de comando.
A IDE alcança seus objetivos criando um objeto System.Runtime.InteropServices.Type-
LibConverter e chamando o método ConvertTypeLibToAssembly( ). Essa operação também é
realizada pela ferramenta de linha de comando Type Library to Assembly Converter,
TlbImp.exe, instalada como parte do .NET Framework SDK (que, por padrão, reside em
C:\Program Files\Microsoft.NET\SDK\v1.1). Você pode criar o mesmo Interop Assembly re-
sultante do uso dessa ferramenta pela IDE. Por exemplo, o Interop Assembly da Speech
API pode ser criado com esta ferramenta de linha de comando:

TlbImp "C:\Program Files\Common Files\Microsoft


➥Shared\Speech\sapi.dll" /sysarray /namespace:SpeechLib
➥/out:Interop.SpeechLib.dll

UTILIZANDO AS FERRAMENTAS NO .NET FRAMEWORK SDK


A fim de utilizar as ferramentas de linha de comando do Microsoft .NET Framework e Framework
SDK eficazmente, você deve adicionar o diretório do Framework e o do Framework SDK ao seu ca-
minho de pesquisa (search path) no Windows. Se planeja utilizar essas ferramentas regularmente,
você pode adicioná-las à variável de ambiente global PATH utilizando o botão Environment Varia-
bles na página Advanced da caixa de diálogo System Properties (acessível por meio do Control Pa-
nel), pelo item do menu Properties no menu de contexto do My Computer ou de maneira mais
simples pressionando a tecla logotipo do Windows + Break.
Uma abordagem alternativa é configurar um atalho na área de trabalho ou no grupo de menus de
programas Start do Framework SDK que carrega um prompt de comando que inicialmente execu-
ta um arquivo de lote. O arquivo de lote pode configurar uma PATH local, o que significa que todas
as ferramentas podem ser utilizadas de dentro do prompt de comando. De fato, o Framework SDK
vem equipado com um arquivo de lote para esse propósito, chamado SDKVars.bat.
O atalho pode executar uma sessão de prompt de comando que invoca o arquivo de lote utilizan-
do essa linha de comando como um alvo, assumindo o diretório de instalação padrão
cmd /K "C:\Program Files\Microsoft.NET\SDK\v1.1\bin\SDKVars.bat"

O que há em um assembly de interoperabilidade?


Um Interop Assembly não contém nenhum código real, mas contém metadados que
descrevem todos os tipos disponíveis na type library do servidor COM. Ele é utilizado
pelo CLR em tempo de execução para construir objetos invólucros adequados que per-
mitem ao código gerenciado .NET conversar com objetos COM correspondentes. Esses
objetos invólucros são conseqüentemente chamados Runtime Callable Wrappers
(RCWs). A criação desses objetos é automática, supondo que seu código seja compilado
com um Interop Assembly adequado.
É importante entender como os itens descritos na type library original são disponi-
bilizados em um Interop Assembly. Podemos deduzir alguns desses itens a partir do
Utilizando objetos COM no código .NET 403

exemplo simples da Speech API anterior. Mas apenas para dar uma idéia geral, examina-
remos os detalhes aqui. Considere um exemplo em que você tem um servidor COM que
implementa um CoClass (um objeto COM que pode ser criado via uma chamada ao COM)
denominado Foo que implementa duas interfaces chamadas IFoo e IBar. As interfaces se-
rão exibidas com os mesmos nomes, IFoo e IBar; entretanto, uma interface adicional será
criada, chamada Foo, que é uma combinação tanto de IFoo como de IBar. O próprio objeto
CoClass será exposto como uma FooClass da classe RCW gerenciada.
No exemplo da aplicação de voz anterior, o CoClass COM original sendo abordado
foi chamado SpVoice, que implementa as interfaces ISpeechVoice (concebidas para utiliza-
ção por meio do Automation que oferecem assinaturas de métodos simples) e ISpVoice
(gerada em linguagens COM de nível mais baixo e que oferece assinaturas de método
mais incômodas). O Interop Assembly combina as duas interfaces em uma nova chama-
da SpVoice e torna o CoClass disponível por meio da SpVoiceClass do RCW. Portanto, na Lis-
tagem 16.5, a variável Voice na linha 9 pode ser declarada utilizando a interface SpVoice
criada, como na listagem, ou a ISpeechVoice da interface COM original.
Se um objeto COM tiver uma interface sainte (ou interface de eventos), todos os mé-
todos de evento serão expostos por meio de tipos delegate com nomes que seguem um
padrão de SourceInterfaceName_MethodNameEventHandler. O objeto Speech API voice, utilizado
no exemplo anterior, oferece uma interface sainte chamada _ISpeechVoiceEvents. Essa in-
terface oferece vários métodos de evento – OnWord que é disparado toda vez que uma nova
palavra é encontrada no texto sendo falado, OnSentence que é disparado toda vez que uma
nova frase é encontrada e OnEndStream quando todo o fluxo da fala foi processado. Esses
eventos estão disponíveis no .NET com os tipos delegate:

_ISpeechVoiceEvents_WordEventHandler, _ISpeechVoiceEvents_SentenceEventHandler e
_ISpeechVoiceEvents_EndStreamEventHandler, respectivamente.

Utilização de eventos COM


A Listagem 16.6 mostra parte do código de um projeto que trata alguns eventos de fala.
Isso oferece uma UI simples que contém um TextBox de múltiplas linhas em que você
pode escrever texto. No evento Load do formulário, o código cria um objeto SAPI voice (li-
nha 3) e solicita que os eventos Word e EndStream sejam disparados conforme apropriado
(linhas 4 e 5). Os handlers de evento para esses eventos são então configurados utilizan-
do chamadas Include (linhas 6 e 7).

LISTAGEM 16.6 Utilização de eventos COM na Microsoft Speech API


1: procedure TMainForm.TMainForm_Load(sender: System.Object;
➥e: System.EventArgs);
2: begin
3: Voice := SpVoiceClass.Create;
4: Voice.EventInterests :=
5: SpeechVoiceEvents.SVEWordBoundary or
➥SpeechVoiceEvents.SVEEndInputStream;
6: Include(Voice.Word, VoiceWord);
404 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

LISTAGEM 16.6 Continuação


7: Include(Voice.EndStream, VoiceEndStream);
8: end;
9:
10: procedure TMainForm.btnSay_Click(sender: System.Object;
➥e: System.EventArgs);
11: begin
12: //Fala assincronamente de modo que os eventos possam ser disparados
13: Voice.Speak(txtWords.Text, SpeechVoiceSpeakFlags.SVSFlagsAsync);
14: end;
15:
16: procedure TMainForm.VoiceEndStream(StreamNumber: Integer;
17: StreamPosition: TObject);
18: begin
19: //Quando o fluxo da fala termina, remove o destaque da caixa de texto
20: txtWords.SelectionLength := 0;
21: txtWords.SelectionStart := Length(txtWords.Text);
22: end;
23:
24: procedure TMainForm.VoiceWord(StreamNumber: Integer;
25: StreamPosition: TObject; CharacterPosition, Length: Integer);
26: begin
27: //À medida que cada palavra é encontrada, ela é destacada na caixa de texto
28: txtWords.SelectionStart := CharacterPosition;
29: txtWords.SelectionLength := Length; //destaca a palavra
30: end;

u Localize o código no CD: \Chapter 16\Ex04\.

IDENTIFICANDO ASSINATURAS REQUERIDAS PARA HANDLERS


DE EVENTO COM
Para encontrar a assinatura requerida para os handlers de evento, você deve examinar a definição
dos tipos delegates relevantes. Você pode fazer isso facilmente com o Reflector da Lutz Roeder,
disponível gratuitamente em http://www.aisto.com/roeder/dotnet. O Reflector oferece disassembly
em IL (embora mais elegante e mais rico em recursos do que o ILDASM.exe do Framework SDK),
bem como a capacidade de visualizar declarações em várias linguagens e também descompilar IL
nessas linguagens. Ele suporta o C# e Visual Basic .NET da Microsoft assim como Delphi 8 for .NET.
Abra o Interop Assembly no Reflector, selecione Language, Delphi e então localize o tipo delegate
que você precisa dentro do assembly. A Figura 16.5 mostra a declaração para o tipo delegate ge-
renciado do evento Word COM. Se compará-lo com a assinatura de handler de evento na Lista-
gem 16.6, verá que ele informa tudo o que você precisa saber.
Utilizando objetos COM no código .NET 405

FIGURA 16.5 Reflector de Lutz Roeder.

A tarefa dos handlers de evento na Listagem 16.6 é destacar palavras individuais na


caixa de texto como se fossem faladas, você pode ver o resultado na Figura 16.6. (Execute
a aplicação de exemplo e você também poderá ouvi-las.)

FIGURA 16.6 Microsoft Speech API em ação.

Controle do tempo de vida do COM


O tempo de vida de um objeto COM é controlado por meio da contagem de referência,
gerenciada pelo RCW. Quando um RCW é liberado pela garbage collection, a contagem
de referência do objeto COM cairá a zero e se autodestruirá. Entretanto, como você nor-
malmente não controla quando uma garbage collection ocorre, permanece indetermi-
nada quando o objeto COM será destruído. Às vezes, você precisa dispensar um objeto
COM deterministicamente e isso pode ser alcançado passando-lhe o método estático
Marshal.ReleaseComObject( ) do namespace System.Runtime.InteropServices.
406 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

NOTA
Uma vez que Marshal.ReleaseComObject é chamado em uma instância do RCW, ele não pode ser
utilizado para acessar quaisquer interfaces a partir desse ponto.

Tratamento de erros
As rotinas COM indicam erros retornando os códigos de status de Hresult. Entretanto,
talvez você se lembre de que na programação anterior em Delphi COM, o Delphi oferece
a conveniente convenção de chamadas SafeCall, que remove a questão do HResult dos
olhos do programador e a deixa para o código compilado. Qualquer HResult retornado
por um método COM que representa um erro é transformado transparentemente em
uma exceção pelo código chamador. Se o método COM configurar informações extras de
erros por meio da interface IErrorInfo (como os métodos SafeCall do Delphi fazem quan-
do ocorre uma exceção em suas implementações), a exceção do lado cliente será mais in-
formativa.
O .NET utiliza esse mesmo modelo para ocultar HResults do código .NET e silenciosa-
mente os transforma em objetos de exceção gerenciados. Uma ampla variedade de erros
de valores HResult específicos transforma-se em tipos específicos de exceção – por exem-
plo, COR_E_FILENOTFOUND ($80070002), a versão HResult do erro Win32 ERROR_FILE_NOT_FOUND
(erro 2), é transformada em System.IO.FileNotFoundException; entretanto, códigos HResult
personalizados que não são reconhecidos pelo CLR simplesmente transformam-se em
System.Runtime.InteropServices.COMException.

Assemblies de interoperabilidade primários


Se um servidor COM particular for utilizado disseminadamente, você poderá verificar o
problema que ocorre em que vários desenvolvedores criam seu próprio Interop Assem-
bly para ele. À medida que várias aplicações são implantadas em máquinas alvo, uma
dada máquina poderia acabar tendo vários Interop Assemblies para o mesmo servidor
COM instalados em vários locais. Para evitar essa situação, há a noção do Primary Inte-
rop Assembly ou PIA.
O objetivo é que fornecedores de servidores COM criem um PIA para seus servidores
COM e o disponibilizem para desenvolvedores .NET. Como um desenvolvedor .NET,
você deve verificar com o fornecedor de todos os servidores COM que você utiliza para
ver se eles têm um PIA disponível. De maneira semelhante, se for o autor de um servidor
COM distribuído para desenvolvedores, você deve construir e disponibilizar um PIA para
eles. Os PIAs podem ser criados com o utilitário TlbImp.exe utilizando a opção /primary de
linha de comando.
O Microsoft Office XP é anterior à utilização do Microsoft .NET, portanto os PIAs es-
tão disponíveis em http://msdn.microsoft.com/downloads/list/office.asp; o Microsoft Office
2003, porém, instala PIAs durante uma instalação completa do produto.

NOTA
Esses (e outros) PIAs são personalizados depois de criados a fim tornar a utilização dos objetos
COM mais fácil.
Utilizando objetos COM no código .NET 407

Um PIA apresenta certos requisitos impostos para assegurar que ele funcione como
projetado. Primeiro, deve ser atribuído um strong name pelo desenvolvedor utilizando um
arquivo-chave com strong name. Isso é criado pelo utilitário .NET sn.exe (utilizando sua op-
ção /k) e incorporado ao assembly pelo atributo AssemblyKeyFile que é automaticamente
colocado no arquivo de origem do projeto ao criar uma nova aplicação. Segundo, se o
servidor COM utilizar outros objetos COM, o PIA só deverá referenciar outros PIAs (para
os servidores COM requeridos) para resolver as referências. Um local comum para insta-
lar PIAs é C:\Program Files\Microsoft.NET\Primary Interop Assemblies; entretanto, os PIAs de-
vem ser instalados no Global Assembly Cache quando implantados de modo que pos-
sam ser prontamente localizados, independentemente de onde eles sejam fisicamente
implantados na máquina.
Os PIAs devem ser registrados com regasm.exe, que adiciona uma entrada descreven-
do o PIA no registro na subchave da biblioteca de tipos do servidor COM original. Isso
permite aos ambientes de desenvolvimento, como o Delphi for .NET, perceberem que
um PIA está disponível quando você referencia uma type library e assim ele evita gerar
um Assembly Interop regular.

NOTA
O link para o PIA é encontrado pelo IDE chamando o método GetPrimaryInteropAssembly( ) do
objeto System.Runtime.InteropServices.TypeLibConverter quando uma referência a uma type
library for adicionada a um projeto. Se o método retornar True, a referência será criada para o PIA
identificado; caso contrário, o IDE chama ConvertTypeLibToAssembly( ) para criar um Interop
Assembly como descrito anteriormente.

DICA
Como uma alternativa à adição de uma referência à type library para um servidor COM que sabe-
mos estar associada a um PIA você também pode vinculá-la diretamente ao PIA. Entretanto, antes
que o PIA seja fisicamente localizado e adicionado aos assemblies .NET instalados, na caixa de diá-
logo Add References, você precisará adicionar o diretório à lista de caminhos de pesquisa do as-
sembly. Essa lista pode ser personalizada na página Assembly Search Paths do diálogo invocado
pelo item de menu Installed .NET Components no menu Component.

Há diretrizes para nomear seu PIA e os namespaces dentro dele. Para ajudar a identi-
ficar seu PIA, é recomendável que você o nomeie como VendorName.LibraryName.dll, onde
LibraryName é o nome da biblioteca como definido na type library. Essa mesma convenção
de VendorName.LibraryName é recomendável para nomear o namespace dentro do PIA (espe-
cificado pela opção do namespace para TlbImp.exe).
Entretanto, uma diretriz secundária sugere que o padrão VendorName.LibraryName seja
reservado para uma futura versão gerenciada do servidor COM que você poderia desen-
volver. Se houver possibilidades de isso acontecer, você deve adotar o esquema VendorNa-
me.LibraryName.Interop para o PIA. Ao mover a biblioteca COM para código gerenciado,
você pode então utilizar o padrão VendorName.LibraryName como a base do seu nome de as-
sembly e namespace.
408 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Personalizando assemblies de interoperabilidade e PIAs


A principal questão que você encontra nos Interop Assemblies é o empacotamento de
parâmetros para tipos de parâmetro “interessantes” não sendo muito corretos. O empa-
cotamento envolve conversão de dados entre tipos não-gerenciados, como um char * do
C ou um PChar do Delphi e um tipo gerenciado adequado, como o tipo System.String do
.NET. O processo .NET de gerar os Interop Assemblies faz um bom trabalho quanto ao
empacotamento de muitos tipos de dados, mas com tipos complexos há claramente uma
boa chance de algo sair errado ou não ser possível trabalhar com eles da maneira mais fá-
cil possível.
Como conseqüência disso, é recomendável ajustar os tipos definidos em um Interop
Assembly para facilitar o trabalho com os RCWs. Isso é especialmente importante ao cri-
ar um PIA utilizado por numerosos desenvolvedores; é aconselhável tornar sua utiliza-
ção a mais fácil possível. Por exemplo, os PIAs no Office XP e Office 2003 são em boa par-
te modificados para que eles sejam utilizáveis o máximo possível pelos desenvolvedores
de aplicações .NET.
Infelizmente, quando escrevíamos este livro havia poucas condições para personali-
zação dos Interop Assemblies de uma maneira sensata ou sustentável porque eram sim-
plesmente gerados pela classe System.Runtime.InteropServices.TypeLibConverter sem gan-
chos (hooks) oferecidos para interceptar o processo. Como resultado, a única opção é
utilizar um processo chamado “creative round tripping”, que é uma extensão da round
tripping.
Round tripping é um termo que descreve um processo de dois passos que envolve se-
lecionar um assembly .NET gerenciado e desassemblá-lo para o código-fonte e metada-
dos correspondentes em IL (juntamente com quaisquer recursos gerenciados ou não-
gerenciados) e então reassemblar o código, metadados e recursos IL em um assembly
.NET funcionalmente equivalente. Por exemplo, dado um assembly chamado dotNet-
Assembly.dll, esses comandos irão desassemblá-lo e reassemblá-lo a fim de fornecer um as-
sembly funcionalmente idêntico:

ildasm dotNetAssembly.dll /linenum /nobar /out:dotNetAssembly.il


ilasm dotNetAssembly /dll /quiet /debug /resource:dotNetAssembly.res

Por causa da rica natureza descritiva dos metadados em arquivos PE gerenciados


(Portable Executable, o formato de arquivo do Win32) esse processo de ida e volta é mui-
to confiável. Algumas coisas selecionadas não sobrevivem ao processo, mas estas são as
não suportadas pelo compilador do Delphi for .NET – por exemplo, dados sobre dados
(dados que contêm o endereço de outras constantes de dados) e código nativo não-
gerenciado incorporado. Além disso, os nomes de variável local serão perdidos se não
houver nenhum arquivo de símbolo de depuração (arquivo PDB) disponível, pois eles
são definidos apenas nas informações de depuração e não nos metadados.
Round tripping só é útil para provar que você obtém um executável funcional de
volta depois de um processo de desassemblagem/reassemblagem. O termo “creative
round tripping” (viagem de ida e volta criativa) é utilizado para descrever um trabalho de
uma viagem de ida e volta com um passo extra. Depois de desassemblar o assembly em
código IL, você modifica o código IL antes da reassemblagem. A “creative round trip-
ping” é utilizada nos cenários em que você quer alterar o código IL gerado por um com-
Utilizando objetos .NET no código COM 409

pilador de uma maneira não permitida pelo compilador, como utilizar um recurso CLR
não suportado por ele ou adicionar código IL personalizado a suas classes.

NOTA
Você pode encontrar informações sobre “creative round tripping” em um artigo em http://
www.blong.com/ Conferences/BorConUK2002/Interop1/Win32AndDotNetInterop.htm. Este artigo foi
escrito para o compilador do Delphi for .NET Preview distribuído no Delphi 7, antes de o Delphi for
.NET ter seu conjunto de recursos completo; veremos na seção “Utilizando rotinas .NET no código
Win32” que a razão para discutir “creative round tripping” no artigo desapareceu na distribuição do
Delphi 8 for .NET, mas a discussão sobre o assunto continua interessante. No mínimo, você verá como
o Delphi 8 for .NET implementa a diretiva exports (discutida no final deste capítulo).

Nesse caso, para personalizar o empacotamento de parâmetros, você precisará modifi-


car a IL gerada pelo importador de type library a fim de especificar as opções de empacota-
mento explícito ou modificar os tipos de parâmetros. Entretanto, manipular o código IL
está além do escopo deste capítulo (e de fato de todo este livro). Portanto, ele é deixado
como um exercício para você explorar. Examinaremos o empacotamento de dados um
pouco mais detalhadamente ao explorar os objetos .NET utilizados nas aplicações Win32,
visto que isso ocorre no nível do Delphi.

DICA
Para informações adicionais sobre o empacotamento padrão que será aplicado ao Interop
Assembly você pode pesquisar “default marshaling” (empacotamento padrão) na ajuda do Delphi
for .NET.

Utilizando objetos .NET no código COM


Depois de termos examinado o código .NET que utiliza os objetos COM, vamos agora
inverter as posições e examinar aplicações clientes Win32 que utilizam objetos .NET
como se eles fossem objetos COM. À medida que você desenvolve o código .NET, talvez
você queira utilizá-lo nas aplicações clientes COM existentes, e o .NET suporta isso.
Com informações suficientes no registro, um cliente COM pode solicitar que um objeto
seja criado pelo COM e terminará com a instanciação de um objeto COM que represen-
ta seu objeto .NET. O objeto COM fará a parte complexa do empacotamento dos dados
de parâmetro de um lado a outro pelo limite gerenciado/não-gerenciado e irá reconciliar
o gerenciamento do tempo de vida da contagem de referências do .NET com a estrutura
de garbage collection no .NET. Como o objeto COM é um invólucro em torno de um
objeto .NET acessível ao cliente COM Win32, ele é chamado COM Callable Wrapper ou
CCW.

Registrando um assembly .NET para Automation


Nosso primeiro requisito ao tornar objetos .NET disponíveis para o COM é adicionar en-
tradas ao registro que o COM usa para fazer o trabalho de criação de seu objeto. Isso pode
ser feito utilizando a classe System.Runtime.InteropServices.RegistrationServices ou, mais
convenientemente, pelo utilitário de linha de comando regasm.exe do .NET Framework
410 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

SDK. Por exemplo, a classe .NET na Listagem 16.7 no projeto dotNetAssembly.bdsproj pode
ser registrada para utilização por aplicações Win32 com esta linha de comando:
regasm dotNetAssembly.dll

Essa instrução na linha de comando configura as informações de registro para cada


classe exposta no assembly; em cada caso, um ProgID e um ClassID para uma CoClass que re-
presenta a classe .NET. O ProgID é o namespace mais o nome da classe, que, para a classe na
Listagem 16.7, é dotNetAssembly.DotNETClassForCOM. (Você pode ver o nome da classe especifi-
cado na linha 2.) O ClassID é criado pelo regasm.exe a menos que especificado pelo uso de
GuidAttribute na classe. Abaixo da subchave ClassID, podem ser encontradas informações
do assembly que implementa a classe .NET e a versão do .NET runtime em que ele foi com-
pilado. Entretanto, o servidor COM in-proc para a CoClass é sempre especificado como a bi-
blioteca de mecanismo de execução .NET, mscoree.dll. Essa DLL produzirá um objeto COM
em tempo de execução (um CCW) que expõe os métodos e propriedades que correspon-
dem à classe .NET subjacente utilizando reflection para examinar seus membros de classe.
A Figura 16.7 mostra as informações sobre o registro da nossa classe de exemplo.

LISTAGEM 16.7 Uma classe .NET simples a ser exposta para o COM
1: type
2: DotNETClassForCOM = class
3: public
4: procedure ShowDotNetMessage(Msg, Caption: String);
5: function DotNETVersion: String;
6: end;
7:
8: function DotNETClassForCOM.dotNETVersion: String;
9: begin
10: Result := System.String.Format('Running .NET {0} on {1}',
11: Environment.Version, Environment.OSVersion);
12: end;
13:
14: procedure DotNETClassForCOM.ShowDotNetMessage(Msg, Caption: String);
15: begin
16: MessageBox.Show(Msg, Caption)
17: end;

u Localize o código no CD: \Chapter 16\Ex05\.

A Figura 16.7 mostra as entradas no registro que permitem que uma classe .NET seja
utilizada pelo COM Win32.

ATENÇÃO
O assembly deve ser colocado em um diretório adequado a fim de ser localizado em tempo de
execução pelos mecanismos naturais de localização de DLL. Geralmente, é sensato implantar o as-
sembly no GAC; entretanto, durante o desenvolvimento, você deixa esse assembly no mesmo di-
retório da aplicação.
Utilizando objetos .NET no código COM 411

FIGURA 16.7 As entradas no registro do COM Interop criadas pelo Regasm.

Automation e vinculação tardia


Depois que seu assembly foi registrado dessa maneira, as classes expostas tornam-se ime-
diatamente disponíveis para acesso por meio de Automation, usando vinculação tardia.
Por exemplo, ele poderia ser acessado em uma aplicação Delphi 7 utilizando o código na
Listagem 16.8.

LISTAGEM 16.8 O código Delphi 7 utilizando um objeto .NET na Listagem 16.7


1: uses
2: ComObj;
3:
4: var
5: dotNETObject: Variant;
6:
7: procedure TfrmAutomation.FormCreate(Sender: TObject);
8: begin
9: dotNETObject := CreateOleObject('dotNetAssembly.DotNETClassForCOM')
10: end;
11:
12: procedure TfrmAutomation.btnAutomationClick(Sender: TObject);
13: var
14: Info: String;
15: begin
16: Info := dotNETObject.DotNETVersion;
17: dotNETObject.ShowDotNetMessage(Info, 'Information')
18: end;

u Localize o código no CD: \Code\Chapter 16\Ex05\Delphi7AutomationClient.

ATENÇÃO
Mais uma vez, é importante assegurar que o assembly .NET esteja em um local em que possa ser
localizado ao testar essa aplicação. A coisa mais fácil a fazer é colocar a aplicação e o assembly no
mesmo diretório. Você pode ajudar esse processo a ser automatizado utilizando a opção de diretó-
rio Output nas opções de projeto do assembly.
412 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Como esse código Automation conta com a utilização da interface IDispatch exposta
para a classe .NET, isso deve funcionar bem contanto que os tipos de dados utilizados
para os parâmetros dos métodos expostos estejam limitados àqueles que o Windows em-
pacota automaticamente em favor do código Automation. Entretanto, a maior parte do
código de produção utilizará código COM com vinculação inicial, portanto ele será exa-
minado mais detalhadamente.

Type Libraries de interoperabilidade


A fim de que um cliente COM Win32 faça a codificação de vinculação inicial solicitando
a classe .NET, devemos criar uma type library de modo que sua ferramenta de desenvol-
vimento Win32 possa gerar classes e interfaces apropriadas a serem vinculadas em tem-
po de compilação. Uma type library pode ser gerada a fim de descrever o COM Callable
Wrapper (CCW) que o CLR construirá em runtime exatamente para esse propósito, ela é
chamada Interop Type Library. Você pode criar uma Interop Type Library utilizando a
classe System.Runtime.InteropServices.TypeLibConverter e seu método ConvertAssemblyToType-
Lib( ), mas o suporte também é oferecido pelo utilitário TlbExp.exe do Framework SDK;
TlbExp.exe irá gerar uma Interop Type Library, mas não fará nada com ela. Isso não é
necessariamente um problema, mas o utilitário RegAsm.exe tem uma opção de linha de co-
mando, /tlb, que faz o mesmo trabalho do TlbExp.exe e também registra a Interop Type
Library de modo que ela possa ser encontrada no registro pelas ferramentas de desenvol-
vimento Win32.

O que há em uma Type Library de interoperabilidade?


Quando você criar uma Interop Type Library, ela conterá as informações sobre cada clas-
se e interface visível no assembly. Na maioria dos casos, é recomendável ser seletivo so-
bre o que é descrito na Interop Type Library e você pode utilizar ComVisibleAttribute para
alcançar isso. Quando não especificado, esse atributo assume o padrão True, mas você
pode aplicá-lo a itens a fim de mantê-los fora da type library. O atributo pode ser aplica-
do a um assembly inteiro, interfaces, classes e registros bem como a campos, métodos,
propriedades, tipos enumerados e delegates. Dessa forma, você pode marcar o assembly
inteiro como não exposto e então selecionar e escolher os itens que você quer adicionar à
type library.
Para cada classe Foo do .NET exposta a uma type library, haverá uma CoClass Foo e
uma interface _Foo correspondente. Entretanto, por padrão, na verdade a interface é uma
dispinterface sem membros, o que faz com que qualquer cliente Win32 utilize o Automa-
tion com vinculação tardia para acessar os objetos. Isso não faz com que avancemos mais
do que antes de descobrir as Interop Type Libraries. Para controlar essa situação, cada
classe pode ter ClassInterfaceAttribute aplicado a ela que dita o tipo de interface que será
gerada para ela na Interop Type Library.
O valor padrão para o atributo é ClassInterfaceType.AutoDispatch, que causa o comporta-
mento descrito anteriormente. Observe que quando uma interface é gerada para uma clas-
se, quaisquer campos de dados públicos são automaticamente expostos como proprieda-
des. Você pode especificar um valor de ClassInterfaceType.AutoDual para obter uma interface
dual baseada em IDispatch a ser gerada, expondo todos os membros públicos da classe.
Utilizando objetos .NET no código COM 413

ATENÇÃO
ClassInterfaceType.AutoDual talvez seja a opção mais desejável para ClassInterfaceAttribute,
mas você deve estar ciente de que ela causa problemas de controle de versão. Toda vez que modi-
ficar a classe e reconstruir a Interop Type Library, você irá gerar uma versão modificada da mesma
interface que, naturalmente, é algo que devemos evitar.

O outro valor que podemos fornecer ao atributo é ClassInterfaceType.None. Isso faz


com que a type library não contenha nenhuma interface gerada para a classe. Esse valor
de atributo é utilizado quando houver classes .NET que já implementam uma interface;
não há nenhum requisito para expor toda a classe uma vez que o código pode acessar as
partes importantes dela por meio da interface. A própria classe será exposta por uma
CoClass que implementa a interface apropriada. Se a classe implementar múltiplas inter-
faces, a CoClass retornará a primeira interface COM visível implementada como seu pa-
drão. Se a classe não implementar nenhuma interface, ela exporá a primeira interface
COM visível implementada pela sua classe base. Se não houver absolutamente nenhuma
interface implementada, ela retornará _Object, a interface que representa os membros pú-
blicos de System.Object.
A interface que você implementa na classe pode ser exclusivamente definida no có-
digo .NET, que torna-se visível ao COM via a Interop Type Library ou pode ser uma inter-
face COM existente. No último caso, você ganharia acesso à interface COM utilizando
um Interop Assembly gerado adicionando ao seu projeto uma referência à type library
que contém a definição da interface. Se a interface COM não estiver definida em uma
type library, você precisará escrever uma definição de interface equivalente no código
.NET.

NOTA
ClassInterfaceType.None é o valor preferido para ClassInterfaceAttribute porque ele
evita problemas desnecessários de controle de versão da interface.

Implementando interfaces
A Listagem 16.9 mostra a Listagem 16.7 retrabalhada, onde a classe DotNETClassForCOM
implementa IDotNETClassForCOM para a interface. A linha 7 oculta todas as classes RTL da
Interop Type Library (juntamente com todas as nossas próprias classes que também po-
deriam estar em um assembly prático) aplicando o atributo [ComVisible(False)] a todo o
assembly. A interface torna-se visível ao COM na linha 10 e recebe um IID na linha 11.
As linhas 17 e 18 tornam a classe visível e expõem uma CoClass que retorna a interface
IDotNetClassForCOM.

LISTAGEM 16.9 Uma classe .NET simples exposta ao COM via uma interface
1: library dotNetAssembly;
2:
3: uses
4: System.Runtime.InteropServices,
5: System.Windows.Forms;
414 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

LISTAGEM 16.9 Continuação


6:
7: [assembly: ComVisible(False)]
8:
9: type
10: [ComVisible(True)]
11: [Guid('3C32D881-43DA-40D2-A7F6-0AE830C2920F')]
12: IDotNETClassForCOM = interface
13: procedure ShowDotNetMessage(Msg, Caption: String);
14: function DotNETVersion: String;
15: end;
16:
17: [ComVisible(True)]
18: [ClassInterface(ClassInterfaceType.None)]
19: DotNETClassForCOM = class(TObject, IDotNETClassForCOM)
20: public
21: procedure ShowDotNetMessage(Msg, Caption: String);
22: function DotNETVersion: String;
23: end;
24:
25: function DotNETClassForCOM.dotNETVersion: String;
26: begin
27: Result := System.String.Format('Running .NET {0} on {1}',
28: Environment.Version, Environment.OSVersion);
29: end;
30:
31: procedure DotNETClassForCOM.ShowDotNetMessage(Msg, Caption: String);
32: begin
33: MessageBox.Show(Msg, Caption)
34: end;
35:
36: begin
37: end.

u Localize o código no CD: \Code\Chapter 16\Ex06\.

ATENÇÃO
Um IID foi aplicado à interface IDotNETClassForCOM na Listagem16.9 utilizando GuidAttribute (li-
nha 11). Isso é diferente da maneira como alcançamos o mesmo objetivo no Delphi 7, utilizando a
sintaxe de ID dedicada à interface:

IDotNETClassForCOM = interface
['{3C32D881-43DA-40D2-A7F6-0AE830C2920F}']
...
end;
Essa sintaxe tradicional do Delphi ainda é aceita pelo compilador do Delphi for .NET, mas na ver-
são inicial é completamente ignorada. (O CLR deve gerar seu próprio IID, essencialmente aleató-
Utilizando objetos .NET no código COM 415

rio, mas é criado a partir de um valor com hash do namespace, nome da classe e nome completo
do assembly, incluindo a versão.) Esperamos que esse descuido seja retificado em uma atualização
subseqüente, mas para evitar riscos, em vez disso, você deve utilizar o atributo do .NET.

Uma Interop Type Library pode ser criada para esse assembly utilizando esta linha de
comando:

RegAsm dotNetAssembly.dll /tlb

Entretanto, se você adicionar a opção de linha de comando /verbose, o utilitário


RegAsm.exe listará todos os tipos adicionados à type library. Nesse caso, há dois: IDotNET-
ClassForCOM e DotNETClassForCOM.
A type library resultante pode então ser importada para sua ferramenta favorita de
desenvolvimento e utilizada normalmente. Um cliente COM de exemplo escrito no
Delphi 7 é fornecido no CD no Chapter 16\Ex06\Delphi7COMClient. Ele chama os métodos da
classe .NET por meio da sua interface de uma maneira semelhante ao código da aplicação
Automation na Listagem 16.8. A aplicação é mostrada em execução com a caixa de men-
sagem do .NET mostrada na Figura 16.8.

Tipos de parâmetros e empacotamento


Muitas assinaturas de método gerenciado funcionarão corretamente depois de exporta-
das por meio da Interop Type Library, graças às regras padrão de mapeamento e empaco-
tamento (marshaling) de dados utilizadas pelo sistema do COM Interop. De fato, para
muitos tipos de dados, nenhum empacotamento é necessário uma vez que eles têm uma
representação comum tanto no código gerenciado como no não-gerenciado. Esses tipos
chamados “blittable” incluem todos os vários tipos de dados Integer definidos pelo com-
pilador Delphi (Byte, Smallint, Word, Shortint, Cardinal/LongWord, Integer/Longint, UInt64,
Int64, NativeUInt, NativeInt) e seus tipos subjacentes definidos no namespace System
(System.Byte, System.SByte, System.UInt16, System.Int16, System.UInt32, System.Int32, System.
UInt64, System.Int64) e qualquer array de uma dimensão de um tipo “blittable”.
Tipos “non-blittable” exigem empacotamento porque os tipos não-gerenciados são
ambíguos e podem ser representados em mais de uma maneira no código gerenciado. O
interop marshaler tem certas regras sobre o que ele fará para cada tipo por padrão. Por
exemplo, um String no .NET será empacotado no COM como um BSTR, que pode ser aces-
sado no Delphi 7 como um WideString e um array será empacotado do outro lado como
um array seguro, que pode ser acessado por meio de uma Variant no Delphi 7. Em alguns

FIGURA 16.8 O assembly .NET sendo utilizado por um cliente Win32/COM.


416 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

casos, essas regras padrão de empacotamento diferem entre o COM Interop e o Platform
Invoke simplesmente porque o que em geral é requerido no COM e no não-COM difere.
Quando as regras padrão de empacotamento não correspondem aos requisitos espe-
cíficos da função que você planeja chamar, você poderá modificar o processo utilizando
MarshalAsAttribute (em conjunção com o tipo enumerado UnmanagedType) e a classe Marshal.
Por exemplo, para empacotar um String no COM como um PWideChar em vez de um Wide-
String, você pode aplicar este atributo ao parâmetro:

[MarshalAs(UnmanagedType.LPWStr)]

O tipo enumerado do tipo não-gerenciado tem muitos valores a fim de oferecer


controle pleno ao desenvolvedor e MarshalAsAttribute também tem campos adicionais que
podem ser utilizados para ajudar a fornecer informações adicionais – por exemplo, ao em-
pacotar um array gerenciado em um array no estilo C (com UnmanagedType.LPArray), em opo-
sição a um array seguro, você pode especificar o tipo e tamanho do elemento do array.

NOTA
Você pode aplicar um atributo, como MarshalAsAttribute, a um valor de retorno de função utili-
zando o atributo modificador result especial, semelhante ao modificador assembly utilizado na li-
nha 7 da Listagem 16.9. Por exemplo,
[result: MarshalAs(UnmanagedType.Bool)]
function A(D: Double): Integer;

O requisito para personalizar o empacotamento padrão nem sempre precisa ser por-
que o empacotamento está incorreto, mas, às vezes, isso pode ocorrer porque o gerencia-
mento de memória precisa ser especificamente controlado. Geralmente, o interop
marshaler tem o hábito bem-intencionado de tentar liberar memória sempre que vir
uma oportunidade, mas isso nem sempre é adequado. Por exemplo, um ponteiro pode-
ria ser retornado contendo um endereço de uma string compilada dentro de algum códi-
go não-gerenciado, em vez de na memória alocada. Veremos um exemplo como esse ao
discutirmos o sistema P/Invoke.
Como um exemplo simples, considere um método .NET com dois parâmetros, um
array Integer e um String. Inicialmente, ele talvez se pareça a isto:
TSample = class
public
procedure SomeMethod(Values: array of Integer; Msg: String);
end;

Talvez você queira oferecer acesso COM a esse método mas personalizar a maneira
como ele vê os parâmetros. Talvez as informações sobre o Integer sejam passadas para o
outro lado como valores Boolean porque o lado do COM só precisa saber se os valores são
zero ou não-zero. Isso corresponde à maneira como os valores Boolean operam em C, embo-
ra seja diferente dos valores Booleans normais do Delphi, que utilizam o bit menos signifi-
cativo para decidir se eles representam True (o bit está configurado) ou False (o bit está lim-
po). E em vez de a string ser passada como um WideString, seria recomendável que ela fosse
passada como um PWideChar. Você pode alcançar isso estendendo a classe desta maneira:
Utilizando objetos .NET no código COM 417

[ComVisible(True)]
ISample = interface
procedure SomeMethod(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BOOL)]
Values: array of Integer;
[MarshalAs(UnmanagedType.LPWStr)]
Msg: String);
end;

[ComVisible(True)]
[ClassInterface(ClassInterfaceType.None)]
TSample = class(TObject, ISample)
public
procedure SomeMethod(
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BOOL)]
Values: array of Integer;
[MarshalAs(UnmanagedType.LPWStr)]
Msg: String);
end;

Você personaliza o tipo de dados no array seguro utilizando o tipo enumerado


VarEnum. Quando o assembly que contém essa classe é executada pelo exportador de type
library, ele gera as informações sobre o tipo que corresponde às suas especificações de
atributo, como demonstrado na type library de exemplo na Figura 16.9.

FIGURA 16.9 Uma Interop Type Library personalizada.


418 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

NOTA
Se adicionar atributos de empacotamento personalizados a um método, certifique-se de aplicar
os mesmos atributos em todos os lugares em que o método é definido – isto é, na classe que o
define e também na interface, supondo que o método é declarado em uma. Do contrário, os cli-
entes COM poderiam obter um diferente comportamento de empacotamento com base na ma-
neira como eles acessam o método: diretamente pela interface definida ou pela dispinterface
da classe.

Você pode encontrar mais detalhes sobre o suporte a empacotamento do .NET na se-
ção sobre a utilização do Invocation Platform Service (P/Invoke ) mais adiante neste capí-
tulo.

ATENÇÃO
Os parâmetros declarados com o modificador const nas interfaces COM e retornos de chamadas
para as APIs não-gerenciadas (como a API Win32) não são passados por referência como ocorria
no Delphi 7. Para manter a mesma mecânica de passagem de parâmetros, mude as declarações de
const para [in] var, como a seguir:
// Declaração VCL
TFontEnumProc = function (const lplf: TLogFont;
const lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer;

// Declaração VCL.NET
TFontEnumProc = function ([in] var lplf: TLogFont;
[in] var lptm: TTextMetric; dwType: DWORD; lpData: LPARAM): Integer;
Observe que isso só se aplica ao COM Interop e é irrelevante para rotinas do P/Invoke (discutidas
mais adiante).

Tratamento de erros
Quando ocorre uma exceção em um método .NET que está sendo chamado a partir do
COM, o CCW realizará o mesmo truque útil dos métodos safecall com o qual os desen-
volvedores em Delphi Win32 estão habituados. A exceção será capturada e irá se trans-
formar em um código HResult de erro específico à classe de exceção que silenciosamente é
retornada da rotina para se adaptar aos protocolos de erros do COM. É possível mapear
alguns códigos HResult de volta ao tipo de exceção que deu origem, mas essa não é uma
ciência perfeita visto que os códigos HResult podem ser reutilizados (e de fato são reutili-
zados) por novas classes de exceção.
Entretanto, o CCW também irá configurar um objeto COM rico sobre erros com in-
formações adicionais (a mensagem da exceção) para qualquer cliente COM que esteja in-
teressado. Se chamado de um cliente Delphi Win32 COM que entende tudo a respeito de
objetos ricos sobre erros, a exceção gerenciada será recriada no lado do cliente, exibindo
a mensagem da exceção.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 419

DICA
Você pode alterar esse comportamento do tratamento de erros utilizando PreserveSigAttribute
individualmente por método. Se quisesse retornar vários códigos de sucesso HResult significati-
vos, você poderia especificar um tipo de retorno Integer e você mesmo retornar os códigos Hre-
sult. Fazer isso requer a especificação do PreserveSigAttribute para esse método de modo que o
CLR não modifique a assinatura e retorne seu próprio código HResult.

Utilizando rotinas exportadas de DLLs do Win32


no código .NET
Além da utilização de objetos COM no código .NET, há um requisito comum para utilizar
rotinas de DLL Win32 regulares. Estas poderiam ser aspectos da API Win32 que ainda não
estão disponíveis por meio da .NET Framework Class Library ou poderiam ser rotinas a
partir de DLLs arbitrárias escritas por você ou por terceiros. Os exemplos do primeiro caso
poderiam incluir as APIs de sincronização de alto desempenho, QueryPerformanceCounter e
QueryPerformanceFrequency. Um outro exemplo é a API simples que cria um bip: Beep.

NOTA
Tecnicamente, o .NET Framework na verdade exibe uma rotina que cria um bip, mas isso é parte
do assembly de runtime do Visual Basic .NET Microsoft.VisualBasic.dll. Em geral, somente as
aplicações Visual Basic .NET têm uma dependência desse arquivo, mas se você quisesse, poderia
adicionar uma referência a Microsoft.VisualBasic.dll ao seu projeto e adicionar Microsoft.Vi-
sualBasic à sua cláusula uses. Então uma chamada a Interaction.Beep criaria um bip.
Isso, porém, não é implementado utilizando a API Beep, ela faz uma chamada não-gerenciada a Messa-
geBeep(MB_OK). Portanto, o resultado é de fato dependente de como o evento de som Default Beep Win-
dows é definido. Normalmente, ele não é absolutamente um bip, mas o arquivo de som ding.wav.

O requisito para chamar rotinas exportadas de DLLs Win32 não-gerenciadas é aten-


dido pelo Platform Invocation Service do .NET, às vezes chamado Platform Invoke,
P/Invoke ou mesmo PInvoke.
Para utilizar o P/Invoke, você precisa escrever a declaração da rotina na DLL
não-gerenciada no código gerenciado de modo que o CLR possa localizar e elaborar
como chamá-la. Isso é quase como a tarefa de escrever uma declaração tradicional de im-
portação de DLL na versão Win32 do Delphi, mas requer suporte adicional a fim de su-
prir o empacotamento de vários tipos de parâmetros do outro lado do limite do código
gerenciado/não-gerenciado. Historicamente, o trabalho de escrever uma declaração de
importação de DLL envolvia a utilização de tipos de dados do Delphi que mapeavam di-
retamente para os tipos de dados do C utilizados pela ampla maioria das DLLs. No .NET,
poucos tipos de dados mapeiam diretamente para tipos C não-gerenciados, assim o em-
pacotamento torna-se muito importante.

NOTA
O Platform Invocation Service é definido como parte da CLI (Common Language Infrastructure)
padrão, portanto, de alguma forma, ele deve estar presente em outras implementações em con-
formidade com a CLI como Mono da Ximian e Portable .NET da dotGNU. Você pode encontrar os
420 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

detalhes na seção 14.5.2 da Partition II dos documentos padrões, instalados como parte do .NET
Framework SDK. Você pode encontrar todas as cinco Partitions como documentos do Microsoft
Word no subdiretório Tool Developers Guide\docs do diretório de instalação do Framework SDK
que, por padrão, seria C:\Program Files\Microsoft.NET\SDK\v1.1\Tool Developers Guide\docs.

Dito isso, o Delphi for .NET suporta a sintaxe de declaração tradicional de importa-
ção de DLL Win32, que funcionará para algumas rotinas de DLL. Nos bastidores, essa sin-
taxe é transposta para uma forma apropriada do .NET que utiliza o principal atributo de
importação de DLL (DllImportAttribute), suficientemente flexível para suprir a maioria
dos requisitos. Também podemos utilizar MarshalAsAttribute contra parâmetros individu-
ais, que salva o dia quando o DllImportAttribute alcança sua limitação.

NOTA
O Delphi for .NET vem com algumas units de importação contendo declarações de importação
P/Invoke para algumas partes da Windows API. Geralmente, o principal exemplo que será útil é
Borland.Vcl.Windows.pas, mas você também encontrará Borland.Vcl.Mapi.pas, Borland.Vcl.
ShellAPI.pas, Borland.Vcl.SHFolder.pas e outros.

Sintaxe Delphi tradicional


Para começar, iremos examinar um exemplo simples. Considere uma DLL chamada So-
meDll.dll que exporta uma rotina que você utilizou em uma versão Win32 do Delphi –
uma rotina simples chamada Some_Func( ) que recebe um Integer, retorna um Integer e uti-
liza a convenção de chamadas StdCall regular do Win32. Para utilizar essa rotina no Delp-
hi 7, provavelmente você escreveria uma declaração import em uma unit de importação
como aquela nas linhas 5, 9 e 10 da Listagem 16.10.

LISTAGEM 16.10 Uma unit simples de importação de DLL


1: unit SomeDllImports;
2:
3: interface
4:
5: function SomeFunc(InputValue: Integer): Integer; stdcall;
6:
7: implementation
8:
9: function SomeFunc(InputValue: Integer): Integer; stdcall;
10: external 'SomeDll.dll' name 'Some_Func';
11:
12: end.

A melhor notícia é que isso funcionará bem no Delphi for .NET. De fato, as declara-
ções para quaisquer rotinas que recebem e retornam tipos simples de parâmetros como
integers, doubles e booleanos funcionarão como anteriormente. Os casos que exigem al-
gum trabalho são as rotinas com parâmetros que têm tipos de dados mais interessantes,
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 421

como registros e strings.

Sintaxe de atributos personalizados


Ao compilar o código na Listagem 16.10, o compilador irá convertê-lo a fim de utilizar
DllImportAttribute (no namespace System.Runtime.InteropServices), que é utilizado para ati-
var o sistema P/Invoke . Assim, ao escrever código como na Listagem 16.10, o compila-
dor essencialmente compila a Listagem 16.11. Isso mostra DllImportAttribute aplicado à
declaração na seção de implementação. A linha 12 constrói o atributo passando o nome
da DLL como o parâmetro do construtor, enquanto as linhas 13–15 configuram alguns
valores dos campos (às vezes chamados parâmetros identificados) para o atributo.

LISTAGEM 16.11 Uma unit bruta de importação .NET para uma DLL simples
1: unit SomeDllImports;
2:
3: interface
4:
5: function SomeFunc(InputValue: Integer): Integer;
6:
7: implementation
8:
9: uses
10: System.Runtime.InteropServices;
11:
12: [DllImport('SomeDll.dll',
13: EntryPoint = 'Some_Func',
14: CharSet = CharSet.Auto,
15: CallingConvention = CallingConvention.StdCall)]
16: function SomeFunc(InputValue: Integer): Integer; external;
17:
18: end.

DllImportAttribute suporta uma variedade de campos para personalizar sua operação


entre os quais alguns são especificados pelo compilador e mostrados na Listagem 16.11.
O campo EntryPoint (linha 13) especifica o nome real exportado da rotina na DLL e é o
mesmo modificador de nome em uma cláusula externa; ele só é requerido se o identifica-
dor do Delphi para a rotina diferir do nome exportado da rotina real. O campo Calling-
Convention (linha 15) identifica a convenção de chamadas em que a rotina na DLL foi
compilada; os principais valores que funcionam são CallingConvention.Cdecl e CallingCon-
vention.StdCall. O valor CallingConvention.FastCall poderia ser aplicável à convenção de
chamadas register, padrão do Delphi for Win32, por duas razões. A primeira é que o su-
porte interno ainda não foi implementado no CLR. A segunda é que a implementação da
Microsoft de convenção de chamadas register é diferente da implementação da Borland.
Nesse exemplo, o campo CharSet (linha 14) é irrelevante uma vez que não há ne-
nhum parâmetro textual, mas de qualquer forma exige alguma cobertura. O campo ins-
trui o CLR como empacotar parâmetros string do outro lado para a DLL, controlando se
422 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

os valores de string .NET (que são implicitamente strings Unicode) são empacotadas do
outro lado como strings ANSI ou Unicode. Ele também afeta o nome do ponto de entra-
da que é procurado ao vincular à rotina, supondo que você não configurou o campo
DllImport ExactSpelling como True. (Ele assume o padrão de False, permitindo que essa cor-
respondência com o nome modificado ocorra.)
O nome que corresponde ao comportamento destina-se mais para rotinas da API
Win32 que são implementadas rotineiramente duas vezes quando recebem parâmetros
textuais. Por exemplo, a API CreateProcess( )utilizada para carregar aplicações é imple-
mentada uma vez como CreateProcessA( ) e uma vez como CreateProcessW( ). A versão
com o sufixo A espera strings de caracteres ANSI (assim, utiliza os tipos não-gerenciados
PChar ou PAnsiChar); a versão com o sufixo W espera caracteres Unicode (e aceita tipos
PwideChar). Normalmente, faríamos uma chamada a CreateProcess( ) e esse identificador
do Delphi seria mapeado para CreateProcessA( ). Como implementações Unicode são, em
geral, removidas do Windows 95/98/Me há uma boa razão de chamar a versão ANSI por
padrão. A Tabela 16.1 descreve os possíveis valores do tipo enumerado CharSet.

TABELA 16.1 Os valores do tipo enumerado CharSet


Valor do tipo Descrição
enumerado CharSet
Ansi Isso assegura que os valores de parâmetro String .NET sejam empacotados do
outro lado como strings ANSI. Com relação à correspondência de nomes, o
CLR procura o ponto de entrada que você especifica – por exemplo,
CreateProcess( ) – e se o CLR não puder localizá-lo, procura
CreateProcessA( ). Esse é o valor padrão se o campo CharSet não estiver
especificado.
None Isso está descrito como obsoleto, mesmo no .NET 1.0 e mapeia para
CharSet.Ansi.
Unicode CharSet.Unicode empacota valores de string como Unicode e primeiro
pesquisa CreateProcessW( ) e então CreateProcess( ) se CreateProcessW( )
não for localizado.
Auto Esse valor é implicitamente especificado quando você utiliza a sintaxe
tradicional de importação do Delphi. Ele funciona como CharSet.Ansi no
Windows 98 e Windows Me, mas como CharSet.Unicode no Windows NT,
Windows 2000, Windows XP e Windows Server 2003.

ATENÇÃO
O valor CharSet.Auto deve ser utilizado com cuidado porque o empacotamento de string funcio-
na de maneira diferente nos dois principais tipos de plataformas Windows. Isso é freqüentemente
bom para Win32 APIs tanto com uma implementação ANSI como com uma Unicode, mas, em ge-
ral, rotinas de DLL personalizadas não têm essa implementação dupla. Lembre-se de que a decla-
ração da sintaxe tradicional de importação do Delphi especifica esse valor, assim as rotinas que re-
cebem parâmetros do tipo string geralmente não devem ser declaradas com essa sintaxe a menos
que passos extras sejam seguidos. Esses passos extras seriam a aplicação de MarshalAsAttribute a
todos os parâmetros textuais a fim de especificar os requisitos de empacotamento corretos.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 423

Você pode navegar pela seção de implementação da unit Borland.Vcl.Windows a fim de


encontrar muitos exemplos das declarações P/Invoke que especificam conjuntos de ca-
racteres. DllImport também suporta outros campos – algum dos quais você também verá
nessa unit e outros nós encontraremos ao explorar o P/Invoke em mais detalhes.

Tipos de parâmetros e empacotamento


O ingrediente-chave para termos sucesso na utilização do P/Invoke é fazer o empacota-
mento correto dos parâmetros. Como você geralmente moverá código que utiliza as roti-
nas de DLLs do Win32 para o .NET, freqüentemente é possível utilizar as declarações de
importação do Win32 fornecidas no Delphi 7 for Win32 e no Delphi for .NET como um
guia para ajudá-lo a compreender como converter as listas de parâmetros do Win32 para
.NET.
Digamos que você tenha uma rotina em uma DLL escrita em C que recebe certo tipo
de parâmetro, um parâmetro de entrada char *, que você declarou como um parâmetro
PChar no Delphi 7. Você poderia procurar uma Win32 API que de maneira semelhante re-
cebe um parâmetro char * de entrada, como SetWindowText( ). No Delphi 7, a declaração
dessa importação nas seções de implementação e interface da unit Windows.pas se parece a
isto, onde user32 é uma constante definida como 'user32.dll':

function SetWindowText(hWnd: HWND; lpString: PChar): BOOL; stdcall;


...
function SetWindowText; external user32 name 'SetWindowTextA';

As declarações no Delphi for .NET correspondentes são semelhantes a:

function SetWindowText(hWnd: HWND; lpString: string): BOOL;


...
[DllImport(user32, CharSet = CharSet.Auto, SetLastError = True,
EntryPoint = 'SetWindowText')]
function SetWindowText; external;

Como você pode ver, o parâmetro PChar é representado como uma String no .NET.
Como uma observação, você pode ver que o campo CharSet DllImport foi configurado
como CharSet.Auto, que resultará na vinculação dele a SetWindowTextA( ) no Windows
98/Me e a SetWindowTextW( ) nas plataformas baseadas no Windows NT. Por enquanto, ig-
nore o campo SetLastError; voltaremos a ele mais adiante.
Agora, vamos tentar um parâmetro de saída char * em que o programa configura
um buffer de caracteres de certo comprimento e o passa para a rotina na DLL a fim de
ser preenchido. A API Win32 GetUserName( )utiliza esse tipo de parâmetro. Mais uma
vez, eis as declarações no Delphi 7, em que advapi32 é uma constante definida como 'ad-
vapi32.dll':

function GetUserName(lpBuffer: PChar; var nSize: DWORD): BOOL; stdcall;


...
function GetUserName; external advapi32 name 'GetUserNameA';
424 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

E eis as equivalentes no Delphi for .NET:

function GetUserName(lpBuffer: StringBuilder; var nSize: DWORD): BOOL;


...
[DllImport(advapi32, CharSet = CharSet.Auto, SetLastError = True,
EntryPoint = 'GetUserName')]
function GetUserName; external;

Como o tipo String do .NET é imutável, ele utiliza um objeto StringBuilder para um
parâmetro textual de saída; ele tem de ser configurado com a capacidade apropriada
como especificado pelo parâmetro nSize.
As declarações de importação fornecidas pelo P/Invoke ajudam você a se familiarizar
com as equivalentes dos parâmetros. Você também pode ver como configurar parâme-
tros de registro a fim de que eles sejam empacotados corretamente examinando as defi-
nições de alguns tipos de parâmetro da API. Retornando à CreateProcess( ), essa API rece-
be alguns tipos diferentes de registro. Você precisa ter cuidado com os registros porque o
.NET geralmente tem a liberdade de organizar os campos na ordem em que preferir. Cla-
ramente, não estávamos acostumados a isso no código não-gerenciado e precisamos es-
pecificar que os registros sendo empacotados do outro lado como parâmetros de rotina
P/Invoke mantenham sua organização seqüencial. A Borland fornece declarações como
essa para CreateProcess( ):

function CreateProcess(lpApplicationName: string; lpCommandLine: StringBuilder;


const lpProcessAttributes: TSecurityAttributes; lpThreadAttributes: IntPtr;
bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: string;
lpCurrentDirectory: string; const lpStartupInfo: TStartupInfo;
out lpProcessInformation: TProcessInformation): BOOL; overload;
...
[DllImport(kernel32, CharSet = CharSet.Auto, SetLastError = True,
EntryPoint = 'CreateProcess')]
function CreateProcess(lpApplicationName: string; lpCommandLine: StringBuilder;
const lpProcessAttributes: TSecurityAttributes; lpThreadAttributes: IntPtr;
bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: string;
lpCurrentDirectory: string; const lpStartupInfo: TStartupInfo;
out lpProcessInformation: TProcessInformation): BOOL; external;

Vários tipos de registro são utilizados na lista de parâmetros: TSecurityAttributes


(definido como igual a _SECURITY_ATTRIBUTES), TStartupInfo (ou _STARTUPINFO) e TProcessInfor-
mation (ou _PROCESS_INFORMATION). Examinando qualquer um desses parâmetros mostra a
utilização de um outro atributo, por exemplo, StructLayoutAttribute:

[StructLayout(LayoutKind.Sequential)]
_SECURITY_ATTRIBUTES = record
nLength: DWORD;
lpSecurityDescriptor: IntPtr; { PSecurityDescriptor }
bInheritHandle: BOOL;
end;
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 425

Se o tipo de registro for proveniente de uma DLL personalizada, talvez seja necessá-
rio que algum alinhamento específico de campo seja correspondido pelo empacotador.
Você pode utilizar StructLayoutAttribute com um parâmetro de Explicit para acomodar
isso. Cada campo tem seu deslocamento de bytes especificado com um segundo atributo
personalizado: FieldOffsetAttribute. E, naturalmente, se os campos de registro precisarem
ser empacotados do outro lado como tipos de dados específicos, você pode continuar a
utilizar MarshalAsAttribute nesses campos individuais.
Como mencionado na discussão sobre o COM Interop, o interop marshaler tem al-
gumas regras padrão para o empacotamento, mas algumas dessas regras mudam entre o
P/Invoke e o COM Interop. Por exemplo, com o P/Invoke um objeto String será empaco-
tado por padrão como um PChar não-gerenciado (PAnsiChar), enquanto que com o COM
Interop ele será empacotado como um WideString. Um array gerenciado será empacotado
como um array no estilo do C com o P/ Invoke, enquanto que com o COM Interop ele é
empacotado como um array seguro. Você precisa conhecer os padrões do interop
marshaler; caso contrário, você pode acabar poluindo seu código com atributos de em-
pacotamento não-requeridos, diminuindo desnecessariamente a legibilidade do código.
Às vezes, uma rotina de DLL recebe um parâmetro que é um tipo de ponteiro ou talvez
um registro que aceita um tipo de ponteiro. Nos casos em que é difícil representar o tipo de
dados com algo adequado no lado gerenciado, você pode especificá-lo como um tipo
IntPtr. O IntPtr é projetado para representar um ponteiro do tamanho de um inteiro nati-
vo e é compatível com a CLS. A classe System.Runtime.InteropServices.Marshal pode ser utili-
zada no código .NET para copiar os dados para dentro ou fora do buffer não-gerenciado re-
presentado pelo parâmetro IntPtr e para alocar memória não-gerenciada.
Você também pode utilizar IntPtr para representar um parâmetro com o qual você
controlará o gerenciamento de memória, a fim de evitar problemas com o comportamen-
to padrão. Um bom exemplo disso é a API GetCommandLine( ). Essa rotina retorna um pontei-
ro para a linha de comando utilizada para iniciar o processo e, como ocorre com muitas
outras APIs, na prática, não é provável que seja chamada uma vez que a propriedade estáti-
ca Environment.CommandLine do .NET faz isso para você. Entretanto, ela permite examinar um
problema particular que poderia aparecer com rotinas de DLL personalizadas uma vez que
ela retorna um ponteiro para alguma parte da memória no kernel do Windows que não
deve ser liberada. Se declarar a rotina da maneira mais óbvia, ela se parecerá a isto:

function GetCommandLine: String;


...
[DllImport(kernel32, CharSet = CharSet.Auto]
function GetCommandLine; external;

Entretanto, com essa declaração, o empacotador entraria em cena e tentaria liberar a


memória não-gerenciada representada pelo objeto String, isso não seria correto. Em vez
disso, podemos evitar o problema declarando o tipo de retorno como um IntPtr:

function GetCommandLine: IntPtr;


...
[DllImport(kernel32, CharSet = CharSet.Auto]
function GetCommandLine; external;
426 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

Para utilizar a rotina, você atribui o valor de retorno a uma variável IntPtr e então
utiliza um membro apropriado da classe Marshal para obter as informações por trás dela:

var
CmdLinePtr: IntPtr;
CmdLineStr: String;
...
CmdLinePtr := GetCommandLine;
CmdLineStr := Marshal.PtrToStringAuto(CmdLinePtr);

Tratamento de erros
Como foi mencionado, boa parte das APIs Win32 regulares já tem declarações de impor-
tação P/Invoke nas units de importação como Borland.Vcl.Windows. Estas são configuradas
apropriadamente para você utilizar, mas, ocasionalmente, talvez você precise de uma
API que não seja coberta pelas units de importação fornecidas. Claramente, você pode
escrever sua própria declaração de importação, mas há uma ou duas questões quanto ao
tratamento de erros com rotinas Win32 que precisam ser resolvidas.
A Windows API não está consistentemente organizada em relação ao tratamento de
erros. Muitas rotinas utilizam a abordagem de retornar um valor Bool indicando o suces-
so ou a falha ou um valor Integer que pode ser tratado como um Boolean (zero significa
False e não-zero, True). Se sua chamada retornar False, você irá segui-la com uma chama-
da para GetLastError( ) a fim de obter um código de erro que foi configurado pela rotina
com SetLastError. Converter isso em uma mensagem de erro textual significa pular vários
hoops metafóricos com a API FormatMessage( ) do Win32 ou, para programadores Delphi,
uma chamada simples para SysErrorMessage( ).
Entretanto, há uma família de rotinas de APIs relacionada ao COM que segue um
modelo diferente de erros. Essas rotinas retornam os códigos de status HResult que indi-
cam o sucesso, falha, categoria (ou recurso) do resultado e um número real de código. O
mecanismo P/Invoke tem suporte específico de diferentes tipos para cada uma dessas
abordagens de tratamento de erros.

Códigos de erros Win32


As APIs que exigem uma chamada para GetLastError( ) precisam de tratamento especial
para funcionar corretamente. Se chamar uma dessas APIs por meio de uma declaração de
P/Invoke e então tentar chamar a API GetLastError( ) por meio de uma outra declaração
de P/Invoke, você poderá obter resultados inconsistentes. Na verdade, o CLR talvez faça
outras chamadas próprias à API entre o retorno da sua primeira chamada e a chamada a
GetLastError( ). Como resultado disso, o código de erro retornado por GetLastError( ) po-
deria na verdade ter sido configurado por uma rotina de API inteiramente diferente, for-
necendo assim informações completamente errôneas. Por causa disso, é considerado
uma prática ruim chamar a API GetLastError( ) diretamente.
O campo DllImportAttribute SetLastError é fornecido para ajudar nessa situação. Ele
assume o padrão de False, mas se configurado como True, significa que o mecanismo do
P/Invoke chamará GetLastError( ) para você depois de a rotina ser encerrada e armazena-
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 427

rá o valor do resultado. Se precisar, você poderá obtê-lo com uma chamada para Marshal.
GetLastWin32Error( ). A unit Borland.Delphi.System, que está implicitamente na cláusula
uses de todos os arquivos-fonte no Delphi for .NET, tem uma função invólucro chamada
GetLastError( ) que invoca essa rotina. Além disso, para evitar quaisquer ambigüidades
no caso de você chamar rotinas Win32 e ter Borland.Vcl.Windows (ou simplesmente Win-
dows) na sua cláusula uses, essa unit não define uma declaração de importação P/Invoke
para GetLastError( ). Em vez disso, ela contém uma implementação simples de GetLast-
Error( ) que chama a rotina invólucro na Borland.Delphi.System.

NOTA
Todas as declarações de importação P/Invoke fornecidas nas units de importação da Borland para
Win32 APIs que utilizam esse modelo de código de erro configuram o campo SetLastError como
True. Entretanto, se você souber que não precisa do código de erro, é um exercício sem sentido fa-
zer com que o CLR chame GetLastError( ). Você pode obter uma melhoria de desempenho muito
pequena redeclarando a importação de API e omitindo o campo SetLastError.

Se estiver acostumado a utilizar as rotinas auxiliares da API Win32 fornecidas no


Delphi RTL, você ficará aliviado por saber que elas continuam aí:
— SysErrorMessage( ) transformará um código de erro no Win32 em uma string des-
critiva.
— RaiseLastWin32Error( ) tornou-se obsoleta no Delphi 7 e 6 a favor de RaiseLast-
OSError( ).

— RaiseLastOSError( ) chama GetLastError( ) (o invólucro Borland.Delphi.System); se


for um código de erro não-zero, uma exceção EOSError será levantada com a descri-
ção do erro (em SysErrorMessage) como sua mensagem.
— Win32Check( ) recebe um valor de retorno Boolean para a API; se for False, chamará
RaiseLastOSError( ).

Como um exemplo para mostrar esse princípio, podemos chamar a API GetUserNa-
me( ). Isso não é exigido nos cenários normais de programação porque o usuário conectado
pode ser identificado por meio da propriedade estática System.Windows.Forms.SystemInfor-
mation.UserName, mas servirá como um exemplo. GetUserName é definida na unit de importação
P/Invoke, Borland.Vcl.Windows, desta maneira:

function GetUserName(lpBuffer: StringBuilder; var nSize: DWORD): BOOL;


...
[DllImport(advapi32, CharSet = CharSet.Auto, SetLastError = True,
EntryPoint = 'GetUserName')]
function GetUserName; external;

Se o buffer passado for muito pequeno, a função irá configurar um código de erro e
retornará False. Podemos evitar escrever código para verificar o valor de retorno e desviar
utilizando Win32Check( ) como na Listagem 16.12.
428 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

LISTAGEM 16.12 Utilização de Win32Check( ) para gerar exceções


1: uses
2: Windows, SysUtils, System.Text;
3:
4: procedure frmWin32Example.frmWin32Example_Load(sender: System.Object;
5: e: System.EventArgs);
6: var
7: UserName: StringBuilder;
8: UserNameLen: DWord;
9: begin
10: UserName := StringBuilder.Create;
11: //Cria espaço para 5 caracteres, não incluindo um terminador null
12: UserName.Capacity := 5;
13: //Passa o tamanho do buffer incluindo o terminador null
14: UserNameLen := Succ(UserName.Capacity);
15: Win32Check(GetUserName(UserName, UserNameLen));
16: lblUserName.Text := System.String.Format(
17: 'Logged on user is: {0}', UserName)
18: end;

u Localize o código no CD: \Code\Chapter 16\Ex07\.

As linhas 10 e 12 configuram um objeto StringBuilder com espaço suficiente para


cinco caracteres (deliberadamente pequeno, assim alguns nomes de usuários não irão ca-
ber). A linha 14 inicializa a variável de comprimento de acordo com o tamanho do buffer;
no caso dessa API, o valor do tamanho deve incluir o espaço para o terminador null. A li-
nha 15 utiliza a rotina auxiliar Win32Check( ). Se tudo der certo (se você tiver um nome de
usuário curto), ele será exibido em um rótulo no formulário. Se não, uma exceção será le-
vantada descrevendo o problema.

Códigos de erros HResult


As rotinas Win32 em torno do subsistema COM informam os erros por meio dos valores
de retorno de HResult. Por exemplo, CLSIDFromProgID( ) é uma API definida na unit ActiveX
do Delphi 7 que recebe o ProgID de um objeto COM e retorna o ClassID.
Ela é declarada no Delphi 7 desta maneira, onde ole32 é uma constante definida
como 'ole32.dll':

function CLSIDFromProgID(pszProgID: POleStr;


out clsid: TCLSID): HResult; stdcall;
...
function CLSIDFromProgID; external ole32 name 'CLSIDFromProgID';

No Delphi for .NET, ela é definida assim, onde TCLSID é definido como igual ao tipo
por valor System.Guid:

function CLSIDFromProgID(pszProgID: IntPtr; out clsid: TCLSID): HResult;


...
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 429

[DllImport(Ole32, CharSet = CharSet.Ansi, SetLastError = True,


EntryPoint = 'CLSIDFromProgID')]
function CLSIDFromProgID; external;

A definição construída pela Borland tem o ProgID definido como um ponteiro


não-gerenciado dimensionado como um inteiro, mas podemos redefinir essas importa-
ções com diferentes parâmetros conforme necessário. Não há necessariamente uma ma-
neira correta de definir uma importação P/Invoke; contanto que funcione, você pode uti-
lizar quaisquer que sejam os tipos de parâmetros mais apropriados.

E V I T A ND O E F E I T O S C O LA T E RA I S I ND ES EJÁ V EIS N A V C L
Nesse caso, há pouca necessidade real de redefinir essa API uma vez que a unit Borland.
Vcl.ComObj fornece uma rotina invólucro chamada ClassIDToProgID( ) que chama a API subja-
cente depois de empacotar uma string no parâmetro IntPtr. Entretanto, você precisa estar ciente
de que ao construir uma aplicação WinForms, qualquer utilização das várias units Delphi RTL/VCL
antigas possivelmente terá efeitos colaterais indesejáveis. Primeiro, você acrescentará bastante
overhead – por exemplo, você talvez queira utilizar a unit Borland.Vcl.ComObj apenas para obter
acesso à rotina invólucro útil, mas isso acrescentará a Borland.Vcl.SysUtils e talvez ainda mais.
Utilizar inadvertidamente essas units extras aumentará consideravelmente o tamanho do seu exe-
cutável e talvez seja preferível evitar todo o suporte de herança da VCL ao construir novas aplica-
ções WinForms no Delphi for .NET.
O segundo efeito colateral é mais importante – qualquer aplicação que utiliza Borland.Vcl.
SysUtils não executará a partir de uma unidade de rede. A razão disso é que o código de iniciali-
zação da unit faz com que uma rotina Win32 API seja chamada pelo P/Invoke. As chamadas
P/Invoke exigem certos privilégios que não são concedidos à zona Local Intranet; isso faz com que
a aplicação termine imediatamente com uma exceção de segurança. A principal questão é que as
aplicações VCL.NET não executarão a partir de unidades de rede e nem qualquer aplicação
WinForms que utilize implícita ou explicitamente Borland.Vcl.SysUtils.
Poderíamos evitar a utilização da rotina invólucro nessa instância copiando a implementação dela
para nossos próprios arquivos de origem ou simplesmente redefinindo a declaração de importa-
ção do P/Invoke. Por exemplo, esta declaração utiliza DllImportAttribute a fim de especificar os
parâmetros string que devem ser empacotados como strings Unicode:

[DllImport(ole32, CharSet = CharSet.Unicode)]


function CLSIDFromProgID(ppsz: String; out rclsid: Guid): Integer; external;
Uma declaração alternativa poderia utilizar a sintaxe tradicional de importação, mas com uma es-
pecificação inline do empacotamento requerido no primeiro parâmetro com MarshalAsAttribute
para sobrescrever o valor CharSet.Auto padrão que o Delphi configurará para o CharSet do campo
DllImportAttribute implícito:
function CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] ppsz: String;
out rclsid: Guid): Integer; stdcall;
external ole32;]

Por exemplo, é suficientemente fácil detectar o sucesso ou a falha desse tipo de roti-
na passando o HResult retornado para as rotinas auxiliares Succeeded ou Failed na unit
Borland.Vcl.Windows:
430 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

if Failed(CLSIDFromProgID('Word.Application', Guid)) then


//faz algo sobre a falha

Ainda há suporte para a antiga rotina auxiliar OleCheck( )na unit Borland.Vcl.ComObj.
Essa rotina recebe um código de HResult como um parâmetro e se ele indicar uma falha,
levantará uma exceção EOleSysError com o HResult exposto pela propriedade ErrorCode.
Mas, mais uma vez, isso talvez aumente o tamanho do seu executável, mais do que você
gostaria, acrescentando várias outras units:

OleCheck(CLSIDFromProgID('Word.Applicatio', Guid));

Ao utilizar objetos COM no .NET, o comportamento padrão do RCW é ocultar os có-


digos HResult e representar falhas de HResults por meio de exceções. É essencialmente a
mesma coisa que uma chamada para OleCheck( ), mas é realizado automaticamente sem
intervenção do programador, em vez da maneira como ocorre com a convenção de cha-
madas safecall do Win32 Delphi. Esse mesmo mecanismo também é suportado pelo
P/Invoke embora, nesse caso, seja opcional porque somente um subconjunto potencial
de rotinas DLL utiliza HResults para comunicar informações sobre erros. Você controla
isso com o campo PreserveSig de DllImportAttribute, que assume o padrão de True. Confi-
gurá-lo como False informa ao .NET que você modificará a assinatura de importação para
ocultar o HResult e esperar que o .NET preencha as lacunas. Portanto, poderíamos reescre-
ver a declaração de importação P/Invoke desta maneira:

[DllImport(ole32, CharSet = CharSet.Unicode, PreserveSig = False)]


procedure CLSIDFromProgID(ppsz: String; out rclsid: Guid); external;

Ela também poderia ser escrita assim:

[DllImport(ole32, PreserveSig = False)]


procedure CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] ppsz: String;
out rclsid: Guid); external;

Agora, quaisquer falhas serão imediatamente transformadas em uma exceção descre-


vendo o erro. A Figura 16.10 mostra o erro produzido quando a API recebe um ProgID invá-
lido. O erro Hresult é $800401F3 e a descrição do problema é exibida como string de classe
inválida (Invalid class string). Isso implica que um ProgID não-reconhecido foi passado.

Questões de desempenho
Qualquer chamada P/Invoke tem um custo associado. Fazer a transição entre código ge-
renciado e não-gerenciado claramente levará algum tempo. Uma chamada P/Invoke bá-
sica em um processador x86 sempre incorrerá em um overhead em torno de 10 instru-
ções de máquina, mas isso talvez aumente devido a outros fatores. A quantidade de tem-
po depende de quanto empacotamento deve ser feito, quer GetLastError( ) seja chamada
em seu nome, quer um valor de retorno HResult seja examinado no caso de uma exceção
ser levantada e assim por diante. Além de tudo isso há o fato de que na primeira chamada
de uma rotina P/Invoke, a DLL de implementação tem de ser carregada na memória e a
rotina localizada dentro dela. E, naturalmente, há também as verificações de segurança.
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 431

Quando você fizer uma chamada P/Invoke, todas as funções na pilha de chamadas são
verificadas para ver se elas têm permissões de código não-gerenciado. Código não-geren-
ciado pode fazer qualquer coisa em uma máquina, assim uma permissão de código
não-gerenciado tem de ser concedida para que ele execute. A execução do código na
zona Internet ou intranet não terá essa permissão por padrão.
Há casos em que é importante otimizar o tempo exigido pelas chamadas P/Invoke –
por exemplo, se fizer uma ou mais chamadas em um loop compacto. Ou se você tentar
sincronizar precisamente algumas operações utilizando as APIs de sincronização de alto
desempenho do Win32 (estas ainda não apareceram no .NET Framework), você clara-
mente irá requerer um número mínimo de overhead envolvido na chamada das próprias
rotinas de sincronização.
Os aspectos a serem levados em consideração para otimizar uma chamada P/Invoke
incluem os seguintes:
— Esforce-se para tornar o empacotamento o mais eficaz ou limitado possível. Em
particular, tente evitar converter strings Unicode .NET para ou a partir de strings
ANSI não-gerenciadas uma vez que isso tem um alto custo.

— Utilize InAttribute e OutAttribute seletivamente (em System.Runtime.InteropServices)


onde for possível para ajudar o empacotador a saber quando ele não precisa copi-
ar dados para uma dada direção.
— Evite configurar o campo SetLastError como True a menos que você realmente pre-
cise obter o código de erro Win32. Lembre-se de que todas as importações P/Invo-
ke da Borland realmente configuram isso como True, assim talvez seja necessário
copiar e modificar a declaração.
— Se os recursos acessados pela API não puderem ser utilizados maliciosamente, ou se a
API estiver definida internamente no seu assembly de uma maneira não chamável a
partir do código externo (na seção de implementação de uma unit), você poderá de-
sativar as verificações Code Access Security (CAS) de runtime normal. Isso só deve ser
feito depois de uma consideração cuidadosa do possível impacto, mas se tiver certe-
za, você poderá utilizar SuppressUnmanagedCodeSecurityAttribute (em System.Security)
para desativar as verificações quando a rotina P/Invoke for chamada.

FIGURA 16.10 Exceção em um HResult.


432 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

— Para evitar o retardo do carregamento da DLL na primeira vez em que chamar


uma rotina P/Invoke, você pode instruir o CLR a fazer a vinculação em tempo de
execução antecipadamente com uma chamada para Marshal.Prelink( ). Você tam-
bém pode instruí-lo a pré-vincular todas as rotinas P/Invoke em uma dada unit
com Marshal.PrelinkAll( ). Entretanto, no caso das Win32 APIs, isso não terá ne-
cessariamente muita diferença uma vez que as DLLs básicas do Win32 já estarão
carregadas no espaço de endereçamento do processo. Afinal de contas, boa parte
da implementação atual do .NET Framework envolve a utilização da API Win32
existente para fazer seu trabalho por meio das suas próprias chamadas P/Invoke.

Vejamos algumas destas no contexto de dois exemplos. O primeiro seria a API


GetUserName( ) anterior. Mais uma vez, esse é um exemplo não-realista de como o .NET
oferece uma maneira de localizar o nome do usuário conectado, mas é suficiente para de-
monstrar os princípios. Da maneira como está, a unit de importação P/Invoke do Delphi
for .NET Windows oferece estas declarações:

function GetUserName(lpBuffer: StringBuilder; var nSize: DWORD): BOOL;


...
[DllImport(advapi32, CharSet = CharSet.Auto, SetLastError = True,
EntryPoint = 'GetUserName')]
function GetUserName; external;

Do ponto de vista do desempenho, há duas coisas que podemos fazer para melhorar
isso. Primeiro, você pode remover o campo SetLastError; não há necessidade de obter o
código de erro se a API retornar False uma vez que sabemos que isso quase certamente
significa que o buffer era muito pequeno.
O campo CharSet pode permanecer como está de modo que em plataformas NT a ver-
são Unicode da API seja localizada e utilizada. Em plataformas do tipo Windows 98, não
há nenhuma implementação Unicode, assim contamos com a string ANSI não-gerencia-
da para empacotar o .NET Unicode String .
A segunda coisa que podemos alterar diz respeito ao objeto StringBuilder. Ele só é uti-
lizado para passar informações a partir da API de volta ao código chamador; não há ne-
nhuma informação sendo passada. Para esse propósito, podemos utilizar OutAttribute
para instruir o empacotador de que não há necessidade de copiar o conteúdo de StringBuil-
der ao chamar a rotina. Observe que mudar a definição de parâmetro para ser um parâ-
metro externo não seria apropriado porque um parâmetro externo adiciona um nível ex-
tra de indireção, assim como um parâmetro var faz. As novas definições seriam seme-
lhantes a :

function GetUserName([Out]lpBuffer: StringBuilder; var nSize: DWORD): BOOL;


...
[DllImport(advapi32, CharSet = CharSet.Auto, EntryPoint = 'GetUserName')]
function GetUserName; external;

Agora, vamos tentar um outro exemplo que utiliza as rotinas de sincronização de


alto desempenho. Essas rotinas são atualmente definidas no Delphi 8 desta maneira:
Utilizando rotinas exportadas de DLLs do Win32 no código .NET 433

function QueryPerformanceCounter(out lpPerformanceCount: TLargeInteger): BOOL;


function QueryPerformanceFrequency(out lpFrequency: TLargeInteger): BOOL;
...
[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True,
EntryPoint = 'QueryPerformanceCounter')]
function QueryPerformanceCounter; external;
[DllImport(kernel32, CharSet = CharSet.Ansi, SetLastError = True,
EntryPoint = 'QueryPerformanceFrequency')]
function QueryPerformanceFrequency; external;

Se nenhum hardware contador de alto de desempenho estiver presente, essas APIs


irão retornar False; não há necessidade do campo SetLastError. Como não há nenhum pa-
râmetro string, não há necessidade do campo CharSet; essa não é uma otimização de de-
sempenho, simplesmente a remoção de um campo redundante. Uma coisa semelhante
pode ser dita quanto ao campo EntryPoint, que também é redundante.
Como essas rotinas simplesmente fazem a sincronização e não podem ser utilizadas
maliciosamente, seria seguro desativar a verificação normal de permissão de código
não-gerenciado CAS (Code Access Security). Novamente, o mesmo poderia ser feito para
GetUserName( ), mas, como mencionado, esse exemplo é essencialmente um exemplo teó-
rico. As APIs de sincronização representam um exemplo mais realista. Eis suas declara-
ções modificadas:

function QueryPerformanceCounter(out lpPerformanceCount: TLargeInteger): BOOL;


function QueryPerformanceFrequency(out lpFrequency: TLargeInteger): BOOL;
...
[DllImport(kernel32), SuppressUnmanagedCodeSecurity]
function QueryPerformanceCounter; external;
[DllImport(kernel32), SuppressUnmanagedCodeSecurity]
function QueryPerformanceFrequency; external;

NOTA
Várias units da VCL.NET declaram suas próprias importações privadas de rotinas API Win32 chave
(como SendMessage, DefWindowProc e CallWindowProc) de uma maneira semelhante a fim de me-
lhorar o desempenho. Essas rotinas configuram SetLastError como False e também especificam
o atributo SuppressUnmanagedCodeSecurity. Você pode ver exemplos disso no código-fonte da
unit Borland.Vcl.Controls, Borland.Vcl.Forms e em outras units. Essas importações estão na se-
ção de implementação das units e assim não podem ser utilizadas pelo código externo. Isso faz
sentido uma vez que o atributo SuppressUnmanagedCodeSecurity afirma que o uso das funções de
API é seguro e a Borland não pode afirmar isso para o código de outras pessoas.

A outra medida que podemos tomar é fazer o CLR pré-vincular as rotinas P/Invo-
ke antes de elas serem chamadas. Isso pode ser feito com base em cada rotina ou em
massa para todas as rotinas declaradas em uma dada unit do Delphi. A Listagem 16.13
mostra parte de uma unit simples chamada PInvokeExampleU.pas que mede quanto tem-
po leva para calcular 50 milhões de raízes quadradas utilizando QueryPerformanceCoun-
ter e QueryPerformanceFrequency. Para evitar qualquer retardo desnecessário associado às
434 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

rotinas na sua primeira chamada, o evento Load do formulário solicita que elas sejam
pré-vinculadas. As linhas 6 e 7 pré-vinculam uma das rotinas e as linhas 8 e 9 fazem
isso na outra rotina.

LISTAGEM 16.13 Removendo o retardo na primeira chamada em chamadas P/Invoke


1: unit PInvokeExampleU;
2: ...
3: procedure frmPInvoke.frmPInvoke_Load(sender: System.Object;
4: e: System.EventArgs);
5: begin
6: Marshal.Prelink(GetType.Module.GetType('PInvokeExampleU.Unit').GetMethod(
7: 'QueryPerformanceFrequency'));
8: Marshal.Prelink(GetType.Module.GetType('PInvokeExampleU.Unit').GetMethod(
9: 'QueryPerformanceCounter'));
10: end;
11:
12: procedure frmPInvoke.btnDoPInvoke_Click(sender: System.Object;
13: e: System.EventArgs);
14: var
15: StartTime, EndTime, TimerFrequency: Int64;
16: TimeDiff, SqrRoot: Double;
17: I: Integer;
18: const
19: NumIterations = 50000000;
20:
21: procedure TimerError;
22: begin
23: raise Exception.Create('High accuracy timing not supported')
24: end;
25:
26: begin
27: if not QueryPerformanceFrequency(TimerFrequency) then
28: TimerError;
29: if not QueryPerformanceCounter(StartTime) then
30: TimerError;
31: for I := 1 to NumIterations do
32: SqrRoot := Sqrt(2);
33: if not QueryPerformanceCounter(EndTime) then
34: TimerError;
35: TimeDiff := (EndTime - StartTime) / TimerFrequency;
36: MessageBox.Show(System.String.Format(
37: '{0} square roots takes {1:f02} seconds', [NumIterations, TimeDiff]),
38: 'Timing Results', MessageBoxButtons.OK, MessageBoxIcon.Information)
39: end;
40:
41: end.

u Localize o código no CD: \Code\Chapter 16\Ex08\.


Utilizando rotinas .NET no código Win32 435

O método Prelink( ) recebe um parâmetro MethodInfo para descrever a rotina P/Invo-


ke a ser pré-vinculada. Podemos fazer isso obtendo um objeto System.Type que representa
a classe de formulário e utilizando seu método Module( ) para obter um objeto Module que
descreve o módulo binário em que ele reside. Em seguida, um objeto System.Type é extraí-
do do Module para uma classe especificada no módulo. Aqui, tiramos proveito do fato de
que o Delphi gera uma classe para representar a unit completa nos bastidores, utilizada
para implementar o que vemos como rotinas globais; na verdade, elas não são globais,
mas parte da classe unit produzida. No objeto System.Type da classe unit, podemos solici-
tar um objeto MethodInfo que descreve quaisquer métodos dessa classe, como as rotinas
P/Invoke.
A fim de pré-vincular todas as rotinas P/Invoke globais declaradas na unit, você
pode substituir as linhas 6–9 por esta chamada:

Marshal.PrelinkAll(GetType.Module.GetType('PInvokeExampleU.Unit'));

Utilizando rotinas .NET no código Win32


O CLR oferece suporte para o oposto do que a Platform Invoke oferece, que é expor mé-
todos estáticos .NET ao mundo não-gerenciado do Win32. Estas costumam ser chama-
das exportações não-gerenciadas. Exportações não-gerenciadas são implementadas pelo
CLR utilizando exatamente o inverso ao seu mecanismo P/Invoke, razão pela qual o su-
porte é, às vezes, chamado Inverse P/Invoke.
Uma aplicação Win32 pode importar essas rotinas expostas da mesma maneira
como com quaisquer rotinas exportadas de DLL regular. Muitas linguagens não tiram
proveito do suporte desse recurso do CLR; somente o Managed C++ e Delphi for .NET o
suportam diretamente como um recurso de linguagem. No caso do Delphi, ele está limi-
tado à exportação de funções globais, que são internamente implementadas como méto-
dos estáticos de um invólucro de classe gerado pelo compilador para a unit.

NOTA
Esse aspecto da interoperabilidade .NET, chamar rotinas gerenciadas a partir de código Win32
não-gerenciado, recebe pouca cobertura em livros didáticos e na World Wide Web presumivel-
mente porque nem o C# nem o Visual Basic .NET suportam o mecanismo Inverse P/Invoke.

Código escrito em outras linguagens pode na verdade ser exportado com o Inverse
P/Invoke, mas fazer isso conta com uma “creative round tripping”, como descrito anteri-
ormente. Isso envolve desassemblar o assembly na origem de IL, modificando-o em vári-
os lugares para alcançar o efeito Inverse P/Invoke para então reassemblá-lo. Como prova-
velmente você pode imaginar, esse processo é bastante não-amigável em relação aos ob-
jetivos normais da manutenção de código.
Como o C# e Visual Basic .NET não suportam prontamente esse recurso, você pode
utilizar o Delphi for .NET para aprimorar efetivamente essas linguagens. Se você tiver
“co-desenvolvedores” que utilizem estas ou outras linguagens que não suportam expor-
tações não-gerenciadas e que tenham assemblies com código que precisa ser acessado a
partir de aplicações Win32, você poderá escrever um assembly simples no Delphi for
436 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

.NET que atue como um invólucro do assembly alvo. Você adiciona algumas rotinas glo-
bais que chamam os métodos estáticos do assembly alvo e essas rotinas globais podem
então ser exportadas do assembly Delphi. Isso fornece uma maneira simples de superar
as deficiências das outras linguagens .NET nessa área.

Sintaxe Delphi tradicional


As exportações não-gerenciadas são suportadas no Delphi for .NET como uma parte sim-
ples da linguagem via a cláusula exports que utilizamos ao escrever DLLs Win32. Como
resultado disso, só há algumas coisas a dizer sobre elas. Você pode adicionar cláusulas de
exportações no arquivo-fonte do projeto ou nas seções interface ou implementation das
suas units.
Você pode ver os princípios demonstrados pelo assembly de exemplo trivial na Lis-
tagem 16.14 que exporta duas procedures chamadas DoSomething( ) e DoSomethingElse( ).
As rotinas globais que você exporta estarão disponíveis para o código não-gerenciado
como rotinas utilizando a convenção das chamadas stdcall.

LISTAGEM 16.14 Rotinas .NET simples exportadas para o Win32


1: library dotNetAssembly;
2:
3: {$UNSAFECODE ON}
4:
5: uses
6: System.Windows.Forms;
7:
8: procedure DoSomething(I: Integer);
9: begin
10: MessageBox.Show(Convert.ToString(I))
11: end;
12:
13: procedure DoSomethingElse(const Msg: String);
14: begin
15: MessageBox.Show(Msg)
16: end;
17:
18: exports
19: DoSomething,
20: DoSomethingElse;
21:
22: begin
23: end.

u Localize o código no CD: \Code\Chapter 16\Ex09\.

Um aspecto importante de produzir exportações não-gerenciadas é que elas geram


código inseguro – PEVerify.exe irá reclamar sobre o fato de o assembly resultante ter um
Utilizando rotinas .NET no código Win32 437

stub Header/nativo no formato PE não-verificável. Por causa disso, você deve permitir
compilação de código insegura inserindo a diretiva de compilador {$UNSAFECODE ON} no ar-
quivo de origem que contém a cláusula exports como foi feito na linha 3. Alternativa-
mente, se estiver compilando com o compilador de linha de comando, você poderá utili-
zar a opção de compilador –unsafe+.

Tipos de parâmetros e empacotamento


O empacotamento de parâmetros funcionará da mesma maneira como com o P/Invoke.
Assim, por exemplo, o tipo de dados String no .NET assumirá o padrão sendo empacotado
como uma string ANSI. Isso significa que essas duas rotinas poderiam ser acessadas a par-
tir do código Delphi Win32 utilizando as declarações de importação exibidas na unit de
importação na Listagem 16.15.

LISTAGEM 16.15 Declarações de importação do Delphi 7 para exportações


não-gerenciadas na Listagem 16.14
1: unit dotNetAssemblyImport;
2:
3: interface
4:
5: procedure DoSomething(I: Integer); stdcall;
6: procedure DoSomethingElse(Msg: PChar); stdcall;
7:
8: implementation
9:
10: const
11: dotNETAssembly = 'dotNETAssembly.dll';
12:
13: procedure DoSomething(I: Integer); stdcall; external dotNETAssembly;
14: procedure DoSomethingElse(Msg: PChar); stdcall; external dotNETAssembly;
15:
16: end.

u Localize o código no CD: \Code\Chapter 16\Ex09\.

Esse exemplo é bem simples e direto, assim vamos examinar um que seja um pouco
mais interessante. Vamos utilizar um assembly que oferece uma função para o código
Win32 que fornece algumas informações textuais. A prática normal, como vimos anteri-
ormente, é fazer com que o código Win32 aloque um buffer de caracteres e o passe para
uma rotina juntamente com seu tamanho. A rotina chamada preenche então o buffer
(nesse exemplo, com uma string que descreve a versão .NET) e retorna. Dessa maneira, o
chamador é responsável por alocar e liberar o buffer. Freqüentemente, essas rotinas re-
tornarão o número de caracteres requerido na string. Isso permite ao código chamador
identificar se ele passou um buffer muito pequeno e talvez crie um maior e tente nova-
mente. Vamos seguir esse modelo comum da API Win32 na nossa rotina de exemplo; en-
tretanto, nesse caso, ele será um buffer de caracteres Unicode.
438 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

O assembly também exportará uma outra rotina que exibe uma caixa de mensagem
e, por consistência com a primeira rotina, ele será exportado para receber uma string
Unicode. Esse requisito nos força a fazer algum empacotamento personalizado (Strings
.NET para strings ANSI) como o empacotamento P/Invoke padrão – isto é, PChar ou
PAnsiChar no Delphi for Win32.
O resultado pode ser encontrado no projeto de assembly mostrado na Listagem
16.16. Como, de qualquer jeito, o assembly tem de ser marcado como inseguro, pode-
mos imediatamente utilizar um tipo de ponteiro inseguro na lista de parâmetros da pri-
meira rotina, GetDotNETVersion( ). A linha 13 define um parâmetro PWideChar, que poderia
ter igualmente definido um parâmetro PChar de acordo com a plataforma em que esta-
mos compilando. O uso de um tipo de ponteiro de caracteres apropriado evita a preocu-
pação com questões de empacotamento.

ATENÇÃO
Há muito tempo a Borland instrui os desenvolvedores a não contar com o fato de Char sempre ser
o mesmo que AnsiChar. No Delphi for .NET, esse aviso se aplica inteiramente uma vez que as in-
formações de texto são tratadas como texto Unicode. Portanto, agora Char é sinônimo de WideChar,
String é o mesmo que WideString e PChar é o mesmo que um PWideChar.
Naturalmente, trabalhamos, em geral, tendo em mente que ponteiros e o .NET não se combinam
muito bem; mas no código inseguro você pode utilizar tipos de ponteiro como PWideChar. Só te-
nha cuidado com o que você faz com eles, se fizer algo!

LISTAGEM 16.16 Exportações não-gerenciadas mais interessantes


1: library dotNetAssembly;
2:
3: {$UNSAFECODE ON}
4:
5: uses
6: System.Text,
7: System.Runtime.InteropServices,
8: System.Windows.Forms;
9:
10: //BufferLen é o comprimento do buffer não-gerenciado,
11: //sem contar o terminador null, isto é, o número de caracteres
12: function GetDotNETVersion(
13: Buffer: PWideChar; BufferLen: Integer): Integer; unsafe;
14: var
15: I: Integer;
16: VersionStr: String;
17: MinLen: Integer;
18: begin
19: VersionStr := System.String.Format(
20: '.NET version {0}', Environment.Version.ToString);
21: MinLen := Math.Min(VersionStr.Length, BufferLen);
22: for I := 0 to MinLen-1 do
23: Buffer[I] := VersionStr.Chars[I];
Utilizando rotinas .NET no código Win32 439

LISTAGEM 16.16 Continuação


24: Buffer[MinLen] := #0;
25: Result := VersionStr.Length
26: end;
27:
28: //Com P/Invoke, String assume o padrão do empacotamento como UnmanagedType.LPStr
29: //Assim, em D7 precisaríamos utilizar um PChar. Mas, por consistência com a
30: //outra rotina exportada daqui, faremos com que saia como um PWideChar
31: procedure ShowAMessage([MarshalAs(UnmanagedType.LPWStr)]Msg: String);
32: begin
33: MessageBox.Show(Msg)
34: end;
35:
36: exports
37: GetDotNETVersion,
38: ShowAMessage;
39:
40: begin
41: end.

u Localize o código no CD: \Code\Chapter Ch16\Ex10\.

ATENÇÃO
Observe que a classe StringBuilder não foi utilizada nesse exemplo. Isso ocorre por causa de uma
limitação do .NET 1.x, em que somente os primeiros 32 bytes de dados na string em um buffer
não-gerenciado serão empacotados na StringBuilder, dando-lhe uma capacidade de apenas 16
caracteres Unicode independentemente do tamanho que o buffer não-gerenciado tinha. Isso re-
presenta um problema lastimável que precisa ser entendido e evitado.

Depois que as linhas 19 e 20 constroem a string a retornar, o código nas linhas 22 e


23 faz um loop pelos seus caracteres, copiando-os para o buffer. Devemos tomar cuidado
para assegurar que o final do buffer não seja ultrapassado tomando nota do comprimen-
to do buffer que foi passado. Uma vez que todos os caracteres foram copiados, o impor-
tante terminador null é adicionado ao final da string no buffer e o valor de retorno é con-
figurado como o comprimento da string.

ATENÇÃO
Você deve tomar cuidado ao extrair caracteres de strings. A linha 23 na Listagem 16.16 utiliza ex-
plicitamente a propriedade array Chars disponível no tipo System.String, que opera com base no
fato de o primeiro caractere estar na posição 0. Historicamente, as strings Delphi operavam com
base no fato de o primeiro caractere estar no deslocamento 1. Se quiser evitar a propriedade array
Chars você pode reescrever a linha desta maneira

Buffer[I] := VersionStr[I+1];
Você pode considerar que a propriedade array Chars tem um efeito semelhante na indexação de
caracteres String no Delphi for .NET que tem no Delphi 7 ao fazer uma coerção de tipo de String
em um Pchar.
440 Capítulo 16 Interoperabilidade – COM Interop e o Platform Invocation Service

A linha 31 da Listagem 16.16 contém o atributo empacotamento (marshaling) que


assegura que o empacotamento padrão de caracteres ANSI seja substituído pelo empaco-
tamento de caracteres Unicode. A Listagem 16.17 mostra as declarações de importação
Win32 correspondentes na sintaxe do Delphi; você pode ver o uso do tipo PWideChar à
medida que as duas exportações não-gerenciadas negociam strings Unicode. Por fim, a
Listagem 16.18 mostra um código Delphi 7 que utiliza as duas exportações não-
gerenciadas para exibir a versão da plataforma .NET em uma caixa de mensagem .NET. A
caixa de mensagem gerenciada é representada sendo exibida em uma aplicação Win32
na Figura 16.11.

LISTAGEM 16.17 Declarações de importação para exportações não-gerenciadas na


Listagem 16.16
1: unit DotNetAssemblyImports;
2:
3: interface
4:
5: procedure ShowAMessage(Msg: PWideChar); stdcall;
6: function GetDotNETVersion(
7: Buffer: PWideChar; BufferLen: Integer): Integer; stdcall;
8:
9: implementation
10:
11: procedure ShowAMessage(Msg: PWideChar); stdcall;
12: external 'dotNetAssembly.dll';
13:
14: function GetDotNETVersion(
15: Buffer: PWideChar; BufferLen: Integer): Integer; stdcall;
16: external 'dotNetAssembly.dll';
17:
18: end.

u Localize o código no CD: \Code\Chapter 16\Ex10\.

LISTAGEM 16.18 Código Delphi 7 utilizando exportações não-gerenciadas do Delphi 8


for .NET
1: procedure TfrmUseUnmanagedExports.btnCallUnmanagedExportsClick(
2: Sender: TObject);
3: const
4: NumChars = 32;
5: var
6: Buf: array[0..NumChars] of WideChar;
7: CharsRequired: Integer;
8: begin
9: CharsRequired := GetDotNETVersion(Buf, NumChars);
10: if CharsRequired <= NumChars then
11: ShowAMessage(Buf)
Utilizando rotinas .NET no código Win32 441

LISTAGEM 16.18 Continuação


12: else
13: raise Exception.CreateFmt(
14: 'Buffer too small. Should be %d characters', [CharsRequired])
15: end;

u Localize o código no CD: \Code\Chapter 16\Ex10\.

FIGURA 16.11 Uma caixa de mensagem .NET a partir de uma aplicação Win32.
PÁGINA EM BRANCO
PARTE IV:
CAPÍTULO 17 DESENVOLVIMENTO
PARA BANCOS DE
DADOS COM O
Visão geral sobre o ADO.NET

ADO.NET 17 Visão geral sobre o


ADO.NET

18 Utilizando o objeto
connection
A DO.NET é a tecnologia .NET que permite aos 19 Utilizando os objetos
desenvolvedores criar aplicações para várias origens de Command e DataReader
dados utilizando uma abordagem moderna para
desenvolvimento distribuído. Embora semelhante ao 20 DataAdapters e DataSets
seu predecessor ADO, as diferenças são significativas. O 21 Trabalhando com
ADO.NET foi projetado em torno de objetivos diferentes WinForms – Visualização e
– muitos dos quais instigados pelas limitações das vinculação de dados
tecnologias anteriores.
22 Salvando dados na origem
de dados
Princípios de projeto 23 Trabalhando com
De acordo com a Microsoft, os objetivos do projeto transações e DataSets
ADO.NET são fortemente tipificados

— Arquitetura de dados desconectada que suporta 24 O Borland Data Provider


programação n-camadas
— Integração compacta com XML NESTE CAPÍTULO
— Representação comum de dados — Princípios de projeto

— Objetos ADO.NET
— Baseado no .NET Framework
— Data Providers .NET
— Aproveitamento das tecnologias antigas

Esses objetivos são resultado da necessidade de


atender a tendência atual de desenvolvimento
distribuído e as limitações das tecnologias atuais.

Arquitetura de dados desconectados


Arquiteturas modernas de sistemas distribuídos
evoluíram para ser baseadas em componentes
desacoplados ou desconectados. Essa abordagem foi
desencadeada pelo aumento da demanda por aplicações
Web. Os sistemas baseados nessa arquitetura
freqüentemente utilizam XML como o transporte de
dados entre componentes e contam com protocolos
444 Capítulo 17 Visão geral sobre o ADO.NET

como HTTP, requerendo gerenciamento stateless (sem estado) entre solicitações. Tecno-
logias anteriores como ADO, ODBC, DAO e RDO não alcançam ou simplesmente não
podem atender as necessidades dos desenvolvedores de criar esses sistemas. Por
exemplo, enquanto os Recordsets originais no ADO 2.x eram desconectados, eles con-
tavam com o COM. Esse requisito existia nas duas extremidades da conexão. Portanto,
um servidor não poderia ser escrito utilizando recordsets que alimentavam um cliente
não-Windows.

Integração com XML


A XML tornou-se excessivamente popular não apenas por transportar dados entre siste-
mas distribuídos, mas também como um formato comum para manipulação de dados.
As tentativas anteriores da Microsoft de incorporar XML às suas tecnologias de gerencia-
mento de dados foram úteis; entretanto, elas não fornecem as capacidades que os desen-
volvedores exigem hoje em dia. Por exemplo, o ADO era capaz de enviar representações
de dados XML em streaming para um arquivo. Essa capacidade não conseguiu oferecer
ao desenvolvedor um controle sobre o formato desses arquivos.
O ADO.NET não apenas abrange a XML, mas a XML também é o formato básico so-
bre o qual os dados são manipulados. Por exemplo, você pode obter dados a partir do
SQL Server em XML. É fácil enviar dados por stream em XML para disco ou serviços re-
motos. Os desenvolvedores também têm controle completo sobre o esquema XSD. O
ADO.NET é completamente capaz em XML.

Representação comum de dados


Um problema em aberto com relação a aplicações de banco de dados é que diferentes
bancos de dados ou formatos exigiam diferentes mecanismos para manipular os dados.
As classes ADO.NET abstraem o formato de dados em um nível que permite aos desen-
volvedores trabalhar com dados de uma maneira uniforme, independentemente do for-
mato de origem.

Embutido no .NET Framework


A fim de alcançar essa uniformidade dos dados, fazia sentido basear o ADO.NET no .NET
Framework. Isso permitiu que a Microsoft tirasse proveito de um sistema de tipos co-
muns e de um rico modelo orientado a objetos no .NET. Esse fato permite aos desenvol-
vedores aproveitarem tanto o conhecimento inicial sobre o ADO como sobre o .NET a
fim de construir fácil e rapidamente sistemas distribuídos robustos. Por exemplo, a com-
binação entre ADO.NET e ASP.NET é um duo poderoso para desenvolver serviços Web
completos com todos os recursos de negócio.

Vantagens das tecnologias existentes


Já afirmei como os desenvolvedores serão capazes de capitalizar seus conhecimentos
existentes sobre o ADO. A maioria dos desenvolvedores que já fez algum tipo de desen-
volvimento no ADO irá sentir-se confortável ao trabalhar com o ADO.NET. Embora haja
diferenças, muitos conceitos foram transferidos para o ADO.NET.
Objetos ADO.NET 445

Objetos ADO.NET
O ADO.NET suporta dois tipos de ambientes de desenvolvimento: conectado e desco-
nectado (ver Figura 17.1). Cada ambiente inclui seu próprio conjunto de classes para su-
portar a funcionalidade final.
As classes de ambiente conectadas fornecem obtenção de dados de leitura a partir
das origens de dados. Elas também permitem aos desenvolvedores executar comandos
na origem de dados a fim de fazer alterações – desde que essa funcionalidade seja supor-
tada pela origem de dados. Basicamente, o ambiente conectado funciona com um banco
de dados físico em que uma conexão é mantida.
As classes de ambiente desconectadas permitem aos desenvolvedores manipular
completamente uma origem de dados genérica ou abstrata. Você pode pensar nessa ori-
gem de dados como um cache na memória.
Esses dois ambientes funcionam em conjunção utilizando uma classe de ponte cha-
mada DataAdapter. Por exemplo, você pode obter dados a partir de uma origem de dados
específica, como SQL Server ou Oracle, utilizando as classes conectadas. Utilizando a
classe DataAdapter, você pode transferir esses dados para os objetos das classes desconecta-
das em que você pode manipular os dados. Ao trabalhar dentro do ambiente desconecta-
do, você não precisa manter nenhuma conexão com o banco de dados. Por fim, você
pode reconciliar suas alterações no banco de dados por meio da DataAdapter.
A Figura 17.1 mostra essas classes nos seus respectivos ambientes. Este capítulo for-
nece uma breve visão geral sobre cada objeto. Os capítulos restantes sobre o ADO.NET
abordam cada objeto em mais detalhes.

Classes conectadas Classes desconectadas

Comando Select
Comando Insert
Comando Update
Comando Delete

Parameter

Banco
de dados

FIGURA 17.1 Classes conectadas e desconectadas no ADO.NET.


446 Capítulo 17 Visão geral sobre o ADO.NET

D A D O S D E S C O N E C T A D O S – UM RO S T O F A M I L I A R
A idéia dos dados desconectados e conectados é notícia velha para desenvolvedores Delphi. Há
anos a tecnologia MIDAS do Delphi (agora chamada DataSnap) introduziu esse conceito ao Delphi
no Delphi 3. Mais tarde, no Delphi 6, ela foi renomeada para DataSnap. TClientDataSet é o com-
ponente do Delphi Win32 para manipular datasets desconectados, e TDataSetProvider é o com-
ponente de ponte entre o cliente Delphi e a aplicação servidor. Boa parte dos conceitos no
ADO.NET não será uma idéia nova se você já desenvolveu utilizando a tecnologia MIDAS/Datasnap,
portanto você não deve ter nenhum problema em pular diretamente para o desenvolvimento em
ADO.NET.

Classes conectadas
A Tabela 17.1 lista e descreve as classes que você utilizaria ao desenvolver dentro do am-
biente conectado do ADO.NET.

TABELA 17.1 Classes conectadas do ADO.NET


Classe Descrição
Connection A classe Connection mantém informações sobre uma origem de dados incluindo
local, nome, credenciais de autorização e outras configurações específicas ao tipo de
banco de dados. A Connection é o canal pelo qual outras classes ganham acesso a
um banco de dados. Connection também define os métodos para abrir e fechar e
processamento de transação.
Command A classe Command permite executar comandos SQL numa origem de dados. Por
exemplo, você pode executar uma instrução SELECT ou uma stored procedure
utilizando a classe Command. Os métodos da classe Command são utilizados com base
no tipo de comando que você pretende executar na origem de dados. Em essência,
Command é a classe que fornece a capacidade de manipular a origem de dados no
ambiente desconectado.
DataReader A classe DataReader fornece acesso de leitura unidirecional (somente para frente) aos
dados a partir de uma origem de dados. Essa classe é utilizada em conjunção com
uma classe Command. DataReader é eficiente porque tem funcionalidade limitada.
DataAdapter A classe DataAdapter é um novo conceito para os desenvolvedores em ADO e um
conceito antigo para os desenvolvedores em Delphi que utilizam MIDAS/Datasnap.
A classe DataAdapter serve como uma ponte para comunicações entre classes
conectadas e classes desconectadas. Por exemplo, você utilizaria os objetos
conectados para obter dados. Então, utilizando DataAdapter, você move os dados
para objetos desconectados em que você manipula esses dados. De fato, você pode
estar completamente off-line e mesmo assim manter esses dados em um estado
desconectado. Por fim, a classe DataAdapter é utilizada para reconciliar suas
alterações de volta à origem dos dados.
Parameter A classe Parameter permite utilizar consultas parametrizadas e stored procedures na
classe Command. Parameter é acessada pela classe Command de ParameterCollection.
Transaction Transaction permite que funções realizadas no banco de dados sejam tratadas como
uma unidade atômica. Isso permite a capacidade de reverter ao estado original do
banco de dados se um erro ocorrer ou aceitar todas as alterações feitas no banco de
dados. Em termos de banco de dados, essas funções comuns são chamadas roll back
(reverter) e commit (confirmar), respectivamente.
Data Providers .NET 447

Classes desconectadas
A Tabela 17.2 lista e descreve as classes que você utilizaria ao desenvolver dentro do am-
biente desconectado do ADO.NET.

TABELA 17.2 Classes desconectadas do ADO.NET


Classe Descrição
DataSet A classe DataSet permite trabalhar com dados independente de qualquer
formato específico da origem de dados. DataSet pode serializar os dados
com base no formato XML. DataSet mantém uma coleção de DataTables,
que mantém coleções de outros objetos. Basicamente, DataSet representa
um banco de dados relacional na memória que você pode manipular
completamente desconectado do banco de dados físico e a partir do qual
você pode reconciliar suas alterações de volta à origem de dados. Embora
os nomes sejam semelhantes, uma DataSet não corresponde diretamente a
um TclientDataSet do Delphi Win32 (ver descrição sobre DataTable). Uma
classe DataSet é mais parecida com um DataModule com múltiplos
TClientDataSets, mas sem a lógica base de codificação baseada em evento.
DataTableCollection DataTableCollection é a coleção de DataTables mantida por um DataSet.
Por meio dessa classe, você obtém acesso a uma classe DataTable
específica.
DataTable Uma classe DataTable, como o nome implica, mantém os dados em uma
coleção de colunas. Na terminologia de banco de dados, uma DataTable é
sinônimo de uma tabela e uma linha e coluna com um registro e campo,
respectivamente. A DataTable na maioria das vezes corresponde
diretamente a um TClientDataSet do Delphi Win32.
DataRelation DataRelation encapsula os relacionamentos entre DataTables. Uma DataSet
contém uma DataRelationCollection que contém uma coleção de
DataRelations.
DataColumn DataColumn armazena informações sobre a estrutura da coluna de uma
DataTable. Essas informações incluem tipo, acessibilidade e expressões de
cálculo, só para citar algumas. DataColumn é mantida pela coleção
DataColumnCollection.
DataRow DataRow expõe as propriedades e métodos que permitem a edição de dados
na DataTable. DataRow é mantida pela coleção DataRowCollection.
Constraint Constraint permite impor restrições como chaves estrangeiras sobre os dados
desconectados. Constraint é mantida pela coleção ConstraintCollection.
DataView DataView permite visualizar os dados armazenados em uma DataSet de
várias maneiras; por classificação, filtragem e assim por diante.

Data Providers .NET


Data providers .NET fornecem acesso a tipos específicos de bancos de dados. Esses providers
também são chamados provedores gerenciados (managed providers). Data providers po-
dem ser desenvolvidos a fim de fornecer acesso a qualquer tipo de origem de dados. Qua-
tro data providers são distribuídos na versão 1.1 do .NET Framework. Vários fornecedo-
res independentes também desenvolveram e estão desenvolvendo providers para origens
de dados adicionais.
448 Capítulo 17 Visão geral sobre o ADO.NET

A versão 1.1 do .NET Framework é distribuída com os quatro data providers .NET lis-
tados na Tabela 17.3.

TABELA 17.3 Data providers ADO.NET


Data providers .NET Descrição
SQL Server Suporte para o Microsoft SQL Server, versão 7.0 ou superior. Há classes SQL
Server no namespace System.Data.SqlClient.
OLE DB Suporte para origens de dados expostas via o provedor OLE DB. Há classes
de data providers OLE DB no namespace System.Data.OleDb.
ODBC Suporte para origens de dados expostos via ODBC. Nota: Esse provider não
é distribuído no .NET Framework 1.0, mas está disponível como um
suplemento. Há essas classes do provider no namespace
Microsoft.Data.Odbc.
Oracle Suporte para Oracle utilizando a Oracle Call Interface (OCI). Esse provider
suporta a distribuição 3 do Oracle 8i, versão 8.1.7. Essas classes do provider
existem no namespace Microsoft.Data.OracleClient.
NESTE CAPÍTULO
CAPÍTULO 18 — Funcionalidade de conexão

— Configurando a propriedade
Utilizando o objeto ConnectionString
— Abrindo e fechando conexões
connection — Eventos de conexão

— Pool de conexão

Connections são as classes que lidam com a


comunicação real entre sua aplicação .NET e o banco de
dados. As classes connection fazem parte da camada do
Data Provider e, portanto, uma classe connection
separada é oferecida para cada tipo de Data Provider.

Funcionalidade de conexão
As classes connections tratam tarefas de baixo nível
como autenticação de usuário, rede, identificação de
banco de dados, pool de conexões e processamento de
transação. Cada objeto connection implementa a
interface IDbConnection definida no namespace
System.Data.
As Tabelas 18.1 e 18.2 listam as várias propriedades
e métodos da interface IDbConnection, respectivamente.

TABELA 18.1 Propriedades IDbConnection


Propriedade Descrição
ConnectionString Contém configurações de conexão no
formato par nome/valor. Essas
informações poderiam incluir informações
sobre a autenticação de usuário,
identificação de banco de dados e
configurações específicas do data provider.
ConnectionTimeout A quantidade de tempo que precisa
transcorrer ao conectar antes de levantar
uma exceção específica do data provider.
Database Contém o banco de dados atual ou o
nome que será utilizado quando o banco
de dados for aberto.
State Contém o estado atual da conexão.
Atualmente, os estados válidos são Open e
Closed.
450 Capítulo 18 Utilizando o objeto connection

A Tabela 18.2 lista os métodos de IdbConnection.

TABELA 18.2 Métodos de IDbConnection


Método Descrição
BeginTransaction( ) Inicia uma transação de banco de dados e retorna uma instância
IDbTransaction.
ChangeDatabase( ) Altera o nome atual do banco de dados.
Close( ) Fecha a conexão com o banco de dados.
CreateCommand( ) Cria um objeto command (um objeto que implementa a interface
IDbCommand) associado com essa conexão.
Open( ) Abre a conexão com o banco de dados.

Observe que as conexões específicas do Data Provider poderiam conter métodos ou


propriedades adicionais não listadas nessas tabelas.

Configurando a propriedade ConnectionString


Ao configurar uma conexão, você deve especificar certas informações sobre a conexão na
propriedade ConnectionString. Em geral, seriam informações sobre a autenticação de usuá-
rio, o nome de banco de dados e sua localização. A ConnectionString para o provedor OLE
DB também requer um parâmetro informando o provedor. Da mesma forma, se utilizar
uma conexão ODBC, você deverá especificar um nome de driver ODBC. Outras configu-
rações são específicas ao Data Provider que é utilizado.

Especificando uma SqlConnection.ConnectionString


A Tabela 18.3 lista os vários parâmetros para a string de conexão. Esses parâmetros, em-
bora específicos ao SQL Server, são semelhantes aos parâmetros de outros data providers.
Essa tabela mostra o nome do parâmetro e nomes alternativos. Consulte a documenta-
ção para configurações específicas dos parâmetros do data provider que você utilizaria.

TABELA 18.3 Parâmetros SqlConnection.ConnectionString


Parâmetro Descrição
AttachDBFilename, Initial File Name O caminho completo e nome de um arquivo de banco
de dados anexável.
Connect Timeout, Connection Timeout Número de segundos a transcorrer antes de abortar uma
tentativa de conexão e levantar um erro.
Data Source, Server, Address, Addr, O nome do servidor que hospeda o SQL Server na rede.
Network Address
Database, Initial Catalog O nome do banco de dados dentro do SQL Server
especificado pelo Data Source.
Integrated Security, Trusted Connection Se True, utiliza a autenticação do Windows. Caso
contrário, utiliza autenticação do SQL Server.
Configurando a propriedade ConnectionString 451

TABELA 18.3 Continuação


Parâmetro Descrição
User ID O ID de usuário no banco de dados.
Packet Size O tamanho do pacote que se comunica com o SQL
Server pela rede. Isso assume o padrão de 8192.
Password, Pwd A senha do usuário.

Ao configurar uma conexão com o SQL Server utilizando a classe SqlConnection, você
deve especificar pelo menos os parâmetros Data Source, Initial Catalog e as informações
sobre a autenticação. Um exemplo da atribuição de uma string de conexão é mostrado
aqui:

sqlcn := SqlConnection.Create('Data Source=XWING;Initial


➥Catalog=ugly_bug;Trusted_Connection=Yes');

Observe o uso da configuração Trusted Connection que instrui o SQL Server a utilizar as
configurações de segurança do Windows para autenticação de usuário.

Especificando uma OleDbConnection.ConnectionString


A ConnectionString para uma OleDbConnection que tem por alvo um banco de dados SQL Ser-
ver 7 (ou superior) é semelhante a

oledbcn := OleDbConnection.Create('Data Source=XWING;


➥Database=ugly_bug;User id=sa;Password=mypw;Provider=SQLOLEDB');

Nessa configuração, observe que você deve fornecer o provedor OLE DB necessário
específico, que irá variar dependendo do provedor OLE DB. O exemplo a seguir ilustra
como essa string de conexão poderia ser semelhante à conexão com um banco de dados
Microsoft Access:

oledbcn := OleDbConnection.Create('Data Source=XWING;


➥Database=c:\data\UglyBug.mdb;Provider=Microsoft.Jet.OLEDB.4.0');

De maneira semelhante ao exemplo anterior, você deve fornecer o provedor OLE DB


específico para o mecanismo Microsoft Jet.

Especificando uma OdbcConnection.ConnectionString


A conexão ODBC é semelhante às conexões previamente mencionadas; entretanto, os
parâmetros requeridos dependerão do driver ODBC específico que é utilizado. Por exem-
plo, a configuração a seguir ilustra uma conexão fornecida por um driver MySQL ODBC:

odbccn := OdbcConnection.Create('Driver={MySQL ODBC 3.51


➥Driver};Database=ACMEDB;UID=user1;PWD=MyPwd;Options=3');

Observe as diferenças nos pares nome/valor específicos ao parâmetro utilizado e o


formato do parâmetro de driver ao especificar a string de driver. Isso irá diferir ainda
452 Capítulo 18 Utilizando o objeto connection

mais entre drivers ODBC. É necessário consultar as informações específicas ao driver


para configurações de strings de conexão.

Especificando uma OracleConnection.ConnectionString


As configurações de conexão do Oracle são semelhantes à configuração discutida anteri-
ormente com um pequeno subconjunto de parâmetros. Uma típica ConnectionString é
ilustrada aqui:

oraclecn := OracleConnection.Create('Data Source=Oracle8i;


➥Integrated Security=true');

Abrindo e fechando conexões


Para abrir uma conexão de banco de dados, você simplesmente invoca o método
SqlConnection.Open( ). Da mesma forma, para fechar a conexão, invoque o método
SqlConnection.Close( ).

NOTA
Se o pool de conexões for suportado, a abertura de uma conexão tentará utilizar uma conexão do
pool; do contrário, uma nova conexão é estabelecida com banco de dados. Além disso, fechar
uma conexão no pool adiciona a conexão ao pool em vez de na verdade fechá-la. O pool de cone-
xões é discutido mais adiante neste capítulo.

A Listagem 18.1 demonstra a abertura e fechamento de uma conexão SQL Server.

LISTAGEM 18.1 Abrindo e fechando uma conexão


1: program test;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Data.SQLClient;
5: const
6: CNSTR = 'server=localhost;database=Northwind;Trusted_Connection=Yes';
7: var
8: sqlcn: SqlConnection;
9: begin
10: sqlcn := SqlConnection.Create(CNSTR);
11: sqlcn.Open( );
12: try
13: Console.WriteLine(System.String.Format('Connected to: {0}',
14: sqlcn.ConnectionString));
15: finally
16: sqlcn.Close( );
17: end;
18: end.
Eventos de conexão 453

u Localize o código no CD: \Code\Chapter 18\Ex01\.

Observe o uso da construção try..finally. O bloco finally executará se ocorrer um


erro entre as linhas 12 e 15, o que irá assegurar que a Connection será fechada e os recursos
de banco de dados serão liberados.

Eventos de conexão
Esta seção ilustra como você pode utilizar os eventos que pertencem à classe SqlConnection.
Há dois eventos que SqlConnection expõe. Esses eventos são mostrados na Tabela 18.4.

TABELA 18.4 Eventos no objeto Connection


Evento de conexão Descrição
InfoMessage Dispara quando um aviso ou mensagem informativa é enviada pelo
servidor de banco de dados.
StateChange Dispara sempre que o estado passar de aberto para fechado ou vice-versa.

A Listagem 18.2 demonstra como processar as informações contidas nos eventos da


classe SQLConnection.

LISTAGEM 18.2 Eventos no objeto SQLConnection


1: program events;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\v1.1.4322\
➥System.Data.dll'}
6: uses
7: SysUtils,
8: System.Data,
9: System.Data.SQLClient,
10: System.Collections,
11: System.Reflection;
12:
13: procedure OnStateChangeEvent(sender: System.Object;
14: args: StateChangeEventArgs);
15: begin
16: Console.WriteLine(System.String.Format(
17: 'State changed from {0} to {1}.', args.CurrentState,
18: args.OriginalState));
19: end;
20:
21: procedure OnInfoMessageEvent(sender: System.Object;
22: args: SqlInfoMessageEventArgs);
23: var
454 Capítulo 18 Utilizando o objeto connection

LISTAGEM 18.2 Continuação


24: se: System.Data.SqlClient.SqlError;
25: erEnum: IEnumerator;
26: begin
27: erEnum := args.Errors.GetEnumerator;
28: while erEnum.MoveNext do
29: begin
30: se := erEnum.Current as SqlError;
31: Console.WriteLine('Server: '+se.Server);
32: Console.WriteLine('Source: '+se.Source);
33: Console.WriteLine('Class: '+se.&Class.ToString);
34: Console.WriteLine('State: '+se.State.ToString);
35: Console.WriteLine('Number: '+se.Number.ToString);
36: Console.WriteLine('LineNumber: '+se.LineNumber.ToString);
37: Console.WriteLine('Procedure: '+se.&Procedure);
38: Console.WriteLine('Message: '+se.Message);
39: end;
40: end;
41:
42: const
43: CNSTR = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
44: var
45: sqlcn: SqlConnection;
46: cmd: SqlCommand;
47: begin
48: sqlcn := SqlConnection.Create(CNSTR);
49: Include(sqlcn.InfoMessage, OnInfoMessageEvent);
50: Include(sqlcn.StateChange, OnStateChangeEvent);
51: sqlcn.Open( );
52: try
53: System.Console.WriteLine(System.String.Format('Connected to: {0}',
54: sqlcn.ConnectionString));
55: cmd := sqlcn.CreateCommand;
56: cmd.CommandText := 'PRINT ''hello''';
57: cmd.ExecuteNonQuery;
58: finally
59: sqlcn.Close;
60: end;
61: Console.ReadLine;
62: end.

u Localize o código no CD: \Code\Chapter 18\Ex02\.

O evento InfoMessage é invocado quando um evento informativo ocorre. Esse evento


pode ser utilizado para obter e exibir informações para o usuário como mostrado na Lis-
tagem 18.2 e na Figura 18.1. Esse evento não é utilizado para processar erros. Quando um
erro ocorre, uma exceção é levantada.
Pool de conexão 455

FIGURA 18.1 Eventos SqlConnection.

O evento StateChange dispara sempre que SqlConnection é aberta. Também é disparado


quando SqlConnection é fechada. A Listagem 18.2 e a Figura 18.1 ilustram como gravar as
informações no console quando SqlConnection é aberta/fechada.

Pool de conexão
Ao construir aplicações baseadas em dados, especialmente aplicações distribuídas, o pool
de conexões pode ser valioso. Considere as aplicações distribuídas com base em serviços
que hospedam conexões de banco de dados que são criadas e destruídas freqüentemen-
te. Ter de restabelecer uma conexão poderia influenciar adversa e drasticamente a escala-
bilidade e o desempenho. Embora isso talvez seja adequado a poucos usuários que execu-
tam muitas transações, não seria suficiente para muitos usuários que executam poucas
transações.
O pool de conexões não é uma função do .NET Framework, mas, em vez disso, dis-
ponibilizado pelo provedor .NET.
A função do pool de conexões é reciclar as conexões previamente abertas, economi-
zando assim tempo quando uma nova conexão é solicitada. A razão dessa economia é
que uma solicitação de conexão não invocará ações de baixo nível como autenticação de
credenciais.
O pool de conexões não é fornecido nativamente pelo ADO.NET. Entretanto, tanto
o Oracle como o SQL Server fornecem seus próprios mecanismos de pool de conexões,
que são ativados por padrão.
NESTE CAPÍTULO
— Executando comandos CAPÍTULO 19
— Comandos Non-Query

— Recuperando valores únicos Utilizando os objetos


— Executando comandos Data
Definition Language (DDL)
Command e
— Especificando parâmetros
com IDbParameter
DataReader
— Executando stored
procedures
— Derivando parâmetros O capítulo anterior discute como conectar-se a um
banco de dados. Este capítulo discute como consultar um
— Consultando resultsets com
banco de dados obtendo dados de leitura, uma maneira
DataReaders
de alto desempenho. Especificamente, ele discute as
— Consultando um resultset classes Command e DataReader. Este capítulo utiliza as classes
— Consultando múltiplos SqlCommand e SqlDataReader do Microsoft SQL Provider, que
resultsets com DataReaders implementam as interfaces IDbCommand e IDataReader,
respectivamente. Lembre-se de que outros provedores
— Utilizando DataReader para
como o OLE DB e BDP (Borland Data Providers)
recuperar dados BLOB
apresentarão ligeiras diferenças na utilização.
— Utilizando DataReader para
recuperar informações sobre
estrutura Executando comandos
A execução de comandos contra um banco de dados é
feito utilizando uma implementação da interface
IDbCommand, como os objetos SqlCommand, OleDBCommand e
BdpCommand. Em geral, esse objeto envia instruções SQL
para o banco de dados executar por meio da propriedade
IDbCommand.CommandText. Isso poderia ser um comando
SELECT, INSERT, UPDATE, DELETE ou outros comandos SQL
semelhantes. Ao utilizar o objeto IDbCommand contra um
data provider não-SQL, o CommandText pode armazenar
instruções não-SQL, contanto que as informações sendo
enviadas possam ser representadas como uma String.

A Interface IDbCommand
As propriedades e métodos da interface IDbCommand são
discutidos nas tabelas 19.1 e 19.2, respectivamente.
Executando comandos 457

TABELA 19.1 Propriedades de IDbCommand


Propriedade Descrição
CommandText Contém o texto do comando que será executado contra o banco de dados.
Para bancos de dados SQL, normalmente são comandos SQL. Para banco de
dados não-SQL, isso talvez seja texto proprietário formatado como uma
String.
CommandTimeout Especifica o tempo que se deve esperar antes de terminar um comando
não-executado e levantar um erro. O padrão é 30 segundos.
CommandType Especifica o tipo de comando contido na propriedade CommandText. Este tipo
pode ser configurado como um de três tipos: StoredProcedure, TableDirect
ou Text. Esses tipos representam uma chamada a uma stored procedure, um
nome de tabela e uma instrução SQL, respectivamente. Observe que a opção
TableDirect não é recomendada já que poderia resultar em desempenho
pobre porque obtém todas as colunas da tabela especificada. TableDirect é
basicamente transformado em uma instrução TableName SELECT * FROM.
Além disso, ela só é suportada pelo OLE DB Provider de acordo com a
documentação da Microsoft.
Connection Referência a um objeto IDbConnection.
Parameters Obtém o IDataParameterCollection.
Transaction Mantém a transação em que o comando será executado.
UpdateRowSource Especifica como os resultados do comando são aplicados ao DataRow quando
usado pelo método DbDataAdaptor.Update( ). Isso é discutido em um
capítulo posterior.

TABELA 19.2 Métodos IDbCommand


Método Descrição
Cancel( ) Tenta cancelar a execução do objeto IDbCommand.
CreateParameter( ) Cria uma instância de uma classe IDbParameter.
ExecuteNonQuery( ) Executa um comando SQL contra o banco de dados e retorna o número
de linhas afetado pelo comando.
ExecuteReader( ) Executa o texto do comando SQL contra o banco de dados e retorna uma
instância IDataReader.
ExecuteScalar( ) Executa uma consulta e retorna a primeira coluna da primeira linha.
Prepare( ) Prepara a chamada a uma stored procedure pré-compilando o comando
no banco de dados. Isso é benéfico somente quando você for invocar o
comando múltiplas vezes. Nesse caso, você chamaria Prepare( ) uma vez
antes de invocar diversos comandos. Como causa um impacto no banco
de dados, ele não é utilizado ao invocar o comando uma vez, ou
raramente.

Você pode executar um comando usando um objeto IDbCommand chamando um dos


três diferentes métodos – ExecuteNonQuery( ), ExecuteScalar( ) e ExecuteReader( ). As im-
plementações IDbCommand específicas podem estender o número de métodos que pode
ser invocado para executar comandos. Por exemplo, o objeto SqlCommand tem um método
ExecuteXMLReader( )que retorna o resultado da consulta em um documento XML.
458 Capítulo 19 Utilizando os objetos Command e DataReader

Comandos Non-Query
Para executar instruções SQL que não retornam resultset (conjunto de resultado), você
utilizaria o método ExecuteNonQuery( ) para invocar o comando. As instruções SQL que en-
tram nessa categoria são, por exemplo, INSERT, UPDATE e DELETE. Além disso, você pode execu-
tar stored procedures, que também não retornam um resultset, utilizando ExecuteNonQuery.
A Listagem 19.1 ilustra o uso desse método para inserir um registro em uma tabela em
um banco de dados.

LISTAGEM 19.1 Utilização do método IDbCommand.ExecuteNonQuery( )


1: program nonquery;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_string = 'server=XWING;database=ugly_bug;Trusted_Connection=Yes';
14: c_ins = 'insert into dg_user (user_id, user_name, password) values '+
15: '(2, ''xpacheco'', ''password'')';
16: var
17: cmd: SqlCommand;
18: rowsChanged: Integer;
19: sqlcon: SqlConnection;
20: begin
21: sqlcon := SqlConnection.Create(c_string);
22: sqlcon.Open;
23: try
24: cmd := SqlCommand.Create(c_ins, sqlcon);
25: rowsChanged := cmd.ExecuteNonQuery;
26: System.Console.WriteLine('Rows Changed: '+rowsChanged.ToString);
27: finally
28: sqlcon.Close;
29: end;
30: Console.ReadLine;
31: end.

u Localize o código no CD: \Code\Chapter 19\Ex01\.

As linhas 14 e 15 correspondem à declaração da constante c_ins, que contém o co-


mando que é executado contra o banco de dados. Este exemplo mostra uma instrução
INSERT na tabela dg_user do banco de dados ugly_bug.
Recuperando valores únicos 459

A linha 24 mostra uma maneira de construir o objeto SqlCommand. Ele aceita dois parâ-
metros. O primeiro parâmetro é a instrução SQL que será executada. Nesse exemplo, a
constante c_ins é passada. O segundo parâmetro é a classe SqlConnection a ser associada a
esse SqlCommand.
Esse exemplo simplesmente executa o comando e exibe o número de linhas que são
afetadas, que nesse caso deve ser uma.
A classe Command específica pode ter construtores adicionais com parâmetros diferen-
tes. Por exemplo, a classe SqlCommand define os seguintes construtores:

Para criar um objeto SqlCommand sem especificar propriedades adicionais, utilize

SqlCommand.Create;

Para criar um objeto SqlCommand e especificar apenas o texto de comando a executar, utilize

SqlCommand.Create(cmdText: String);

Para criar um objeto SqlCommand e especificar o texto de comando a executar e o objeto SqlCon-
nection associado, utilize

SqlCommand.Create(cmdText: String, connection: SqlConnection);

Para criar um objeto SqlCommand e especificar o texto de comando a executar, o


SqlConnection associado e uma transação Transact-SQL, SqlTransaction, utilize

SqlCommand.Create(cmdText: String, connection: SqlConnection,


➥transaction: SqlTransaction);

Recuperando valores únicos


Com freqüência, você precisará recuperar um único valor a partir de uma consulta, particu-
larmente ao retornar um valor agregado. SqlCommand implementa o método ExecuteScalar( ),
que retorna esse valor.
Examine a seguinte instrução SQL:

SELECT Count(*) AS CanCust FROM customers WHERE country = 'Canada'

Essa instrução particular recuperará o número total de registros em uma tabela cha-
mada customers, indicando clientes que residem no Canadá. O valor retornado será
devolvido como um campo único chamado CanCust. A Listagem 19.2 ilustra a utilização
do método ExecuteScalar( ) para recuperar esse valor.

LISTAGEM 19.2 Utilização do método IDbCommand.ExecuteScalar( )


1: program ExScalar;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
460 Capítulo 19 Utilizando os objetos Command e DataReader

LISTAGEM 19.2 Continuação


➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_string = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
14: c_cmd = 'select Count(*) as CanCust from customers '+
15: 'where country = ''Canada''';
16: var
17: sqlCon: SqlConnection;
18: sqlCmd: SqlCommand;
19: CanCust: Integer;
20: begin
21: sqlCon := SqlConnection.Create(c_string);
22: sqlCon.Open;
23: try
24: sqlCmd := SqlCommand.Create(c_cmd, sqlCon);
25: CanCust := Integer(sqlCmd.ExecuteScalar);
26: Console.WriteLine(System.String.Format('Canadian Customers: {0}',
27: CanCust.ToString));
28: finally
29: sqlCon.Close;
30: end;
31: Console.ReadLine;
32: end.

u Localize o código no CD: \Code\Chapter 19\Ex02\.

A Listagem 19.2 de exemplo conecta-se ao banco de dados Northwind que é instala-


do com o Microsoft SQL Server ao quando optamos pela instalação dos bancos de dados
de exemplo. Essa instrução SQL é executada contra a tabela customers.
Grande parte desse exemplo é muito semelhante ao da Listagem 19.1. A linha 27
está onde o método ExecuteScalar( ) é chamado, que retorna o valor recuperado. Nesse
exemplo, o valor é do tipo Integer; portanto, ele é convertido em um Integer e atribuído à
variável CanCust.
Nesse exemplo, a instrução SQL retorna explicitamente um único valor. Em uma
consulta geral que possivelmente retorna múltiplas linhas, o método ExecuteScalar( ) re-
tornará o valor contido na primeira linha, primeira coluna.

Executando comandos Data Definition


Language (DDL)
A maioria dos comandos submetidos contra um banco de dados é composta de coman-
dos de recuperação e manipulação de dados. Mas, ocasionalmente, deve-se fazer algo di-
Executando comandos Data Definition Language (DDL) 461

namicamente em relação à estrutura do próprio banco de dados. Isso poderia adicionar


ou modificar uma tabela, stored procedure ou outras operações como essas.
Existem classes .NET não dedicadas que realizam essas operações explicitamente.
Entretanto, você ainda pode executar comandos DDL utilizando o método IDbCommand.
ExecuteNonQuery( ). A Listagem 19.3 ilustra essa técnica.

LISTAGEM 19.3 Executando comandos DDL


1: program ddlquery;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data.SqlClient;
10:
11: const
12: ub_proc = 'CREATE PROCEDURE ub_select_defect AS select '+
13: 'd.*, dt.title AS defect_type, ds.title AS defect_severity, '+
14: 'dst.title AS defect_status '+
15: 'FROM defect d left OUTER JOIN lu_defect_type dt '+
16: 'ON d.defect_type_id = dt.defect_type_id '+
17: 'LEFT OUTER JOIN lu_defect_severity ds '+
18: 'ON d.defect_severity_id = ds.defect_severity_id '+
19: 'LEFT OUTER JOIN lu_defect_status dst '+
20: 'ON d.defect_status_id = dst.defect_status_id';
21:
22: const
23: c_string = 'server=XWING;database=ugly_bug; Trusted_Connection=Yes';
24:
25: var
26: sqlcon: SqlConnection;
27: cmd: SqlCommand;
28: rowsChanged: Integer;
29: begin
30: sqlcon := SqlConnection.Create;
31: sqlcon.ConnectionString := c_string;
32: sqlcon.Open;
33: try
34: cmd := SqlCommand.Create(ub_proc, sqlcon);
35: rowsChanged := cmd.ExecuteNonQuery;
36: sqlcon.Close;
37: Console.WriteLine(System.String.Format('Rows Changed: {0}',
38: rowsChanged.ToString));
39: finally
40: sqlcon.Close;
462 Capítulo 19 Utilizando os objetos Command e DataReader

LISTAGEM 19.3 Continuação


41: end;
42: Console.ReadLine;
43: end.

u Localize o código no CD: \Code\Chapter 19\Ex03\.

De fato, o exemplo na Listagem19.3 não é a maneira ideal de criar uma stored proce-
dure no banco de dados. Entretanto, a intenção desse exemplo é ilustrar como executar
DDL contra um banco de dados e não impressioná-lo com o estilo elegante de codificação.
Você notará que a stored procedure foi atribuída à constante string ub_proc. Essa ins-
trução não é nada mais do que uma mesma instrução que você poderia executar pelo
Query Analyzer. Aqui, executamos essa instrução pelo objeto SqlCommand na linha 32. O
valor retornado realmente será -1.

Especificando parâmetros com IDbParameter


Ao executar o código SQL, é altamente improvável que os valores de instrução SQL se-
jam codificados diretamente no código como na Listagem19.2, que pesquisava clien-
tes canadenses. E se quiséssemos pesquisar sobre clientes alemães ou mexicanos? A
consulta teria de suportar de alguma maneira a especificação dinâmica dos critérios
de pesquisa. Isso é feito por meio de consultas parametrizadas. Em uma consulta para-
metrizada, um parâmetro é colocado na instrução da consulta, no local em que o crité-
rio de pesquisa manualmente codificado residia inicialmente. Considere a seguinte
instrução SQL:

c_cmd = 'SELECT * FROM customers WHERE country = @country

Nessa instrução SQL, em vez de escrever o nome de um país diretamente no código,


um parâmetro é especificado utilizando como prefixo o símbolo (@) para o nome de pa-
râmetro (@country). Para inserir realmente um valor no lugar do parâmetro, uma classe de
implementação IDbParameter é necessária.
Para o Microsoft SQL Data Provider, a classe SqlParameter implementa a interface
IDbParameter. Outros provedores têm suas próprias classes, como a OleDbParameter e
BdpParameter para o OLE DB Data Provider e o Borland Data Provider, respectivamente.
Para utilizar um parâmetro, você deve primeiro criá-lo e então associá-lo ao objeto
SqlCommand específico que executará o comando. Ao criar um SqlParameter, é provável que
você queira especificar o nome de parâmetro, tipo de dados e tamanho no construtor.
Existem seis possíveis construtores que você pode utilizar, que permitem especificar mais
ou menos informações como a coluna de origem, direção de parâmetro e assim por dian-
te. Consulte a documentação sobre SqlParameter para obter mais informações sobre esses
construtores adicionais.
A Listagem 19.4 ilustra como utilizar o objeto SqlParameter para especificar um valor
de parâmetro em uma instrução de consulta parametrizada.
Especificando parâmetros com IDbParameter 463

LISTAGEM 19.4 Utilizando SqlParameter em uma consulta parametrizada


1: program Params;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11: const
12: c_cmd = 'select * from customers where country = @country';
13:
14: var
15: sqlcon: SqlConnection;
16: cmd: SqlCommand;
17: param: SqlParameter;
18: rowsChanged: Integer;
19: begin
20: sqlcon := SqlConnection.Create;
21: sqlcon.ConnectionString := 'server=XWING;database=Northwind;'+
22: 'Trusted_Connection=Yes';
23: cmd := SqlCommand.Create(c_cmd, sqlcon);
24: param := cmd.Parameters.Add('@country', SqlDbType.NVarChar, 15);
25: param.Value := 'USA';
26:
27: sqlcon.Open;
28: try
29: rowsChanged := cmd.ExecuteNonQuery;
30: finally
31: sqlcon.Close;
32: end;
33:
34: Console.WriteLine(System.String.Format('Rows Changed: {0}',
35: rowsChanged.ToString));
36: Console.ReadLine;
37: end.

u Localize o código no CD: \Code\Chapter 19\Ex04\.

Nesse exemplo, um parâmetro é requerido. A linha 24 invoca o método SqlCommand.


Parameters.Add( ), que cria, adiciona e retorna uma instância IDbParameter. A propriedade
Parameters é uma classe de coleção (collection class SqlParameterCollection) que mantém
instâncias de IDbParameter, utilizadas internamente quando a consulta é invocada. O mé-
todo Add( ) aceita o nome de parâmetro, o tipo de dados do parâmetro e a largura da co-
luna. Depois de adicionar e obter uma referência ao parâmetro, um valor é atribuído ao
464 Capítulo 19 Utilizando os objetos Command e DataReader

parâmetro; nesse caso, estamos atribuindo a string 'USA' para indicar uma pesquisa sobre
clientes dos EUA. Normalmente, algum outro meio dinâmico seria utilizado para especi-
ficar os critérios de pesquisa como um combobox ou algo semelhante.
Ao especificar os tipos de dados para o parâmetro, você deve utilizar os tipos defini-
dos pelo banco de dados específico do data provider utilizado. Para o data provider SQL
Server, você deve utilizar os tipos definidos pelo tipo enumerado System.Data.SqlDbType.
Para o data provider OLE DB, você utilizaria os tipos de dados definidos pelo tipo enume-
rado System.Data.OleDB.OleDbType. O mesmo é verdadeiro para outros data providers.

Executando stored procedures


Os passos para executar uma stored procedure são tão simples como executar outros co-
mandos não relacionados à consulta. A Listagem 19.5 ilustra esses passos.

LISTAGEM 19.5 Exemplo de Stored Procedure


1: program sp_addco;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
14: c_ddn_add_company = 'ddn_add_company';
15: var
16: sqlcon: SqlConnection;
17: cmd: SqlCommand;
18: param: SqlParameter;
19: begin
20: sqlcon := SqlConnection.Create(c_cnstr);
21: cmd := SqlCommand.Create(c_ddn_add_company, sqlcon);
22: cmd.CommandType := CommandType.StoredProcedure;
23:
24: param := cmd.Parameters.Add('@company_name', SqlDbType.VarChar, 100);
25: param.Value := 'Xapware Technologies Inc.';
26:
27: param := cmd.Parameters.Add('@address_1', SqlDbType.VarChar, 100);
28: param.Value := '4164 Austin Bluffs Parkway';
29:
30: param := cmd.Parameters.Add('@address_2', SqlDbType.VarChar, 100);
31: param.Value := 'Ste 363';
Executando stored procedures 465

LISTAGEM 19.5 Continuação


32:
33: param := cmd.Parameters.Add('@city', SqlDbType.VarChar, 50);
34: param.Value := 'Colorado Springs';
35:
36: param := cmd.Parameters.Add('@state_abbr', SqlDbType.Char, 2);
37: param.Value := 'CO';
38:
39: param := cmd.Parameters.Add('@zip', SqlDbType.VarChar, 20);
40: param.Value := '80918';
41:
42: param := cmd.Parameters.Add('@company_id', SqlDbType.Int);
43: param.Direction := ParameterDirection.Output;
44:
45: sqlcon.Open;
46: try
47: cmd.ExecuteNonQuery;
48: param := cmd.Parameters['@company_id'];
49: finally
50: sqlcon.Close;
51: end;
52:
53: Console.WriteLine(System.String.Format('New company id: {0}',
54: param.Value));
55:
56: Console.ReadLine;
57: end.

u Localize o código no CD: \Code\Chapter 19\Ex05\.

Esse exemplo executa uma stored procedure simples que insere um registro em uma
tabela de empresas. A stored procedure é mostrada na Listagem19.6. Examinando a Lis-
tagem 19.6, você verá que esse procedimento declara tanto os parâmetros de entrada
como os de saída.

LISTAGEM 19.6 Stored procedure para adicionar uma empresa


1. CREATE PROCEDURE ddn_add_company
2. (
3. @company_name varchar(100),
4. @address_1 varchar(100),
5. @address_2 varchar(100),
6. @city varchar(50),
7. @state_abbr char(2),
8. @zip varchar(20),
9. @company_id int output
10. )
11. AS
466 Capítulo 19 Utilizando os objetos Command e DataReader

LISTAGEM 19.6 Continuação


12. INSERT INTO company (company_name, address_1, address_2, city, state_abbr,
13. zip)
14. VALUES (@company_name, @address_1, @address_2, @city, @state_abbr, @zip)
15. SELECT @company_id = @@Identity
16. GO

Voltando à Listagem19.5, linha 21, você nota que em vez de especificar a execução
da instrução SQL, você especifica o nome da stored procedure. Nesse exemplo, o nome
da stored procedure é contido na constante string c_ddn_add_company. Além disso, o valor
CommandType.StoredProcedure é atribuído à propriedade SqlCommand.CommandType para especifi-
car que o texto do comando contém uma stored procedure e não uma instrução SQL.
As linhas restantes, 24–40, simplesmente adicionam parâmetros e atribuem seus res-
pectivos valores esperados pela stored procedure. As linhas 42–43 lidam com o parâme-
tro de saída. Em vez de especificar um valor, a direção de um parâmetro é indicada atri-
buindo ParameterDirection.Output à propriedade Direction de SqlParameter.
Depois de executar o comando, o parâmetro de saída pode ser recuperado (linha 51)
e seu valor acessado. O exemplo aqui retorna o valor do campo auto-incremento do re-
gistro.

Derivando parâmetros
A Listagem 19.5 mostra como invocar uma chamada que requer múltiplos parâmetros.
Entretanto, esse tipo de código pode ser complicado de manter, porque especificidades
do banco de dados como os nomes dos parâmetros e os tipos de dados são escritos direta-
mente no seu código-fonte (hard-code).
Você pode utilizar a classe CommandBuilder, que essencialmente consulta o banco de
dados quanto aos metadados sobre os parâmetros e preenche o objeto SqlCommand com es-
sas informações. Portanto, você só tem de especificar os valores. A Listagem 19.7 mostra
um código alternativo ao da Listagem 19.5.

LISTAGEM 19.7 Especificando parâmetros com o CommandBuilder


1: program storedproc_cb;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
Consultando resultsets com DataReaders 467

LISTAGEM 19.7 Continuação


13: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
14: c_ddn_add_company = 'ddn_add_company';
15: var
16: sqlcon: SqlConnection;
17: cmd: SqlCommand;
18: begin
19: sqlcon := SqlConnection.Create(c_cnstr);
20: cmd := SqlCommand.Create(c_ddn_add_company, sqlcon);
21: cmd.CommandType := CommandType.StoredProcedure;
22:
23: sqlcon.Open;
24: try
25:
26: SqlCommandBuilder.DeriveParameters(cmd);
27: cmd.Parameters[1].Value := 'Xapware Technologies Inc.';
28: cmd.Parameters[2].Value := '4164 Austin Bluffs Parkway';
29: cmd.Parameters[3].Value := 'Ste 363';
30: cmd.Parameters[4].Value := 'Colorado Springs';
31: cmd.Parameters[5].Value := 'CO';
32: cmd.Parameters[6].Value := '80918';
33: cmd.Parameters[7].Value := DBNull.Value;
34: cmd.ExecuteNonQuery;
35: finally
36: sqlcon.Close;
37: end;
38:
39: Console.WriteLine(System.String.Format('New company id: {0}',
40: cmd.Parameters[7].Value));
41: Console.ReadLine;
42:
43: end.

u Localize o código no CD: \Code\Chapter 19\Ex06\.

A linha 26 é onde se faz a chamada CommandBuilder.DeriveParameters( ) para recuperar me-


tadados de parâmetro, que são injetados na classe SqlCommand passada como um parâmetro.

ATENÇÃO
A utilização do objeto CommandBuilder pode não ser a melhor opção, porque adiciona o overhead
de uma chamada adicional ao banco de dados. Embora conveniente do um ponto de vista de co-
dificação, quando você conhecer as informações sobre metadados, utilize-as e evite armadilhas de
desempenho.

Consultando resultsets com DataReaders


Até este ponto, este capítulo discutiu somente como invocar comandos SQL que retor-
nam um ou nenhum valor do banco de dados. Quando precisar retornar um resultset de
468 Capítulo 19 Utilizando os objetos Command e DataReader

múltiplos registros, você pode utilizar o objeto específico do data provider em questão
que implementa a interface IDataReader.

A interface IDataReader
A interface IDataReader define a capacidade de ler fluxos de resultsets somente para frente
(forward-only). A importância disso é o desempenho.
DataReaders não recuperam imediatamente e colocam na memória todo o resultset.
Em vez disso, os resultados são armazenados em cache em um buffer de rede no cliente.
Quando necessário, os registros são buscados e carregados na memória via o método IDa-
taReader.Read( ). Busca-se um registro por vez, reduzindo assim o overhead de sistema.
Você não pode criar um DataReader explicitamente. Uma instância IDataReader é retor-
nada a partir do método SqlCommand.ExecuteReader( ).
Como outras classes, existem DataReaders para cada data provider específico.

Consultando um resultset
A classe SQLDataReader pode ser utilizada para consultar um único ou diversos resultsets. A
Listagem 19.8 ilustra a técnica de um único resultset.

LISTAGEM 19.8 Utilizando SqlDataReader em um único resultset


1: program rsltset;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
14: c_select_company = 'select company_id, company_name from company';
15: var
16: sqlcon: SqlConnection;
17: cmd: SqlCommand;
18: rdr: SqlDataReader;
19: begin
20: sqlcon := SqlConnection.Create(c_cnstr);
21: cmd := SqlCommand.Create(c_select_company, sqlcon);
22: sqlcon.Open;
23: try
24: rdr := cmd.ExecuteReader;
Consultando múltiplos resultsets com DataReaders 469

LISTAGEM 19.8 Continuação


25: while rdr.Read do
26: Console.WriteLine(System.String.Format('{0}-{1}',
27: rdr['company_id'], rdr['company_name']));
28: finally
29: sqlcon.Close;
30: end;
31: Console.ReadLine;
32: end.

u Localize o código no CD: \Code\Chapter 19\Ex07\.

As linhas 24–27 ilustram o uso da classe SqlDataReader. Primeiro, o método Execute-


Reader( ) é invocado para retornar uma instância de SqlDataReader na variável rdr. Em
seguida, Read( ) é invocado para iterar pelo resultset. Read( ) retorna um boolean indi-
cando se um registro foi recuperado.

Consultando múltiplos resultsets com DataReaders


Você pode utilizar data readers (leitores de dados) para iterar por múltiplos resultsets,
como mostrado na Listagem 19.9.

LISTAGEM 19.9 Consultando múltiplos resultsets


1: program multrsltset;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
14: c_select_company = 'select company_id, company_name from company;'+
15: 'select state_abbr, state_name from lu_state;';
16: var
17: sqlcon: SqlConnection;
18: cmd: SqlCommand;
19: rdr: SqlDataReader;
20: begin
21: sqlcon := SqlConnection.Create(c_cnstr);
22: cmd := SqlCommand.Create(c_select_company, sqlcon);
23: sqlcon.Open;
470 Capítulo 19 Utilizando os objetos Command e DataReader

LISTAGEM 19.9 Continuação


24: try
25: rdr := cmd.ExecuteReader;
26: repeat
27: while rdr.Read do
28: Console.WriteLine(System.String.Format('{0}-{1}',rdr[0], rdr[1]));
29: until not rdr.NextResult;
30: finally
31: sqlcon.Close;
32: end;
33: Console.ReadLine;
34: end.
35:

u Localize o código no CD: \Code\Chapter 19\Ex08\.

Nesse exemplo, o texto do comando contém múltiplas instruções SELECT, retornando


assim múltiplos resultsets. Além disso, o SqlDataReader é utilizado diferentemente do mos-
trado na listagem anterior. Aqui, o método SqlDataReader.NextResult( ) é utilizado para
avançar para o próximo resultset.

Utilizando DataReader para recuperar dados BLOB


Ocasionalmente, você precisará recuperar um BLOB de um banco de dados. Um BLOB é
uma massa de espaço externo que aumenta proporcionalmente com o número das pes-
soas que ele devora. Ele também é um bloco de dados que, dependendo do banco de da-
dos, não contém estrutura. O acrônimo BLOB significa Binary Large OBject. Em geral,
imagens, arquivos e outros dados são armazenados em BLOBs. DataReaders podem ser
utilizados para recuperar BLOBs como ilustra a Listagem 19.10.

LISTAGEM 19.10 Recuperando dados BLOB de um banco de dados


1: program readblob;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient,
11: System.IO;
12:
13: const
14: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
Utilizando DataReader para recuperar dados BLOB 471

LISTAGEM 19.10 Continuação


15: c_sel = 'select employeeID, Photo from employees';
16: c_bufferSize = 100;
17: var
18: sqlcon: SqlConnection;
19: cmd: SqlCommand;
20: rdr: SqlDataReader;
21:
22: barray: array[0..c_bufferSize-1] of byte;
23: bytesRead: Integer;
24: idx: Integer;
25:
26: fName: String;
27: fStream: FileStream;
28: bWriter: BinaryWriter;
29:
30: begin
31: sqlcon := SqlConnection.Create(c_cnstr);
32: cmd := SqlCommand.Create(c_sel, sqlcon);
33: sqlcon.Open;
34: try
35: rdr := cmd.ExecuteReader(CommandBehavior.SequentialAccess);
36: rdr.Read;
37: fName := 'emp_'+rdr.GetSqlInt32(0).ToString+'.bmp';
38: fStream := FileStream.Create(fName, FileMode.OpenOrCreate,
39: FileAccess.Write);
40: try
41: bWriter := BinaryWriter.Create(fStream);
42: try
43: idx := 0;
44: repeat
45: bytesRead := Integer(rdr.GetBytes(1, idx, barray, 0,
46: c_bufferSize));
47: bWriter.Write(barray, 0, bytesRead);
48: idx := idx+c_bufferSize;
49: until bytesRead < c_buffersize;
50: finally
51: bWriter.Close;
52: end;
53: finally
54: fStream.Close;
55: end;
56: finally
57: sqlcon.Close;
58: end;
59: end.

u Localize o código no CD: \Code\Chapter 19\Ex09\.


472 Capítulo 19 Utilizando os objetos Command e DataReader

Esse exemplo utiliza as classes FileStream e BinaryWriter e ilustra como ler incremen-
talmente o campo BLOB baseado em um tamanho de buffer. O processo é a abordagem
padrão de ler seções do BLOB individualmente e colocá-las em um buffer de arquivo que,
por fim, é salvo em um arquivo.
A linha 35 merece comentários adicionais. Ao executar o método SqlCommand.Execu-
teReader( ), o parâmetro CommandBehavior é SequentialAccess. Esse valor é específico aos da-
dos no formatado BLOB uma vez que faz com que o data reader carregue os dados como
um fluxo de bytes (stream), em vez de carregar o resultset inteiro. Você então pode uti-
lizar os métodos GetBytes( ) ou GetChars( ) para recuperar dados em fragmentos. Um re-
quisito de utilizar SequentialAccess é que as colunas devem ser lidas na ordem que são so-
licitadas. Você pode pular colunas; entretanto, você não será capaz de voltar e ler uma
coluna pulada.

Utilizando DataReader para recuperar


informações sobre estrutura
O DataReader pode ser utilizado para obter informações de estrutura utilizando o méto-
do SqlDataReader.GetSchemaTable( ). Esse método retorna um DataTable que é preenchido
com informações sobre colunas de um resultset. Portanto, cada linha nesse resultset de
estrutura representa uma coluna no resultset real. A linha do resultset de estrutura con-
tém informações sobre uma coluna, como seu nome, unicidade, tipo de dados, entre ou-
tras. A Listagem 19.11 ilustra a utilização desse método.

LISTAGEM 19.11 Obtendo informações de estrutura


1: program schemaquery;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DotNetAssemblyCompiler 'C:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient,
11: System.Collections;
12: const
13: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
14: c_select_employees = 'select * from Employees';
15: var
16: sqlcon: SqlConnection;
17: cmd: SqlCommand;
18: rdr: SqlDataReader;
19: schema: DataTable;
20: row: DataRow;
21: col: DataColumn;
Utilizando DataReader para recuperar informações sobre estrutura 473

LISTAGEM 19.11 Continuação


22: enRow: IEnumerator;
23: enCol: IEnumerator;
24: begin
25: sqlcon := SqlConnection.Create(c_cnstr);
26: cmd := SqlCommand.Create(c_select_employees, sqlcon);
27: sqlcon.Open;
28: try
29: rdr := cmd.ExecuteReader(CommandBehavior.SequentialAccess);
30: schema := rdr.GetSchemaTable;
31: finally
32: sqlcon.Close;
33: end;
34:
35: enRow := schema.Rows.GetEnumerator;
36: while enRow.MoveNext do
37: begin
38: row := enRow.Current as DataRow;
39: enCol := schema.Columns.GetEnumerator;
40: while enCol.MoveNext do
41: begin
42: col := enCol.Current as DataColumn;
43: Console.WriteLine(col.ColumnName+'='+row[col].ToString);
44: end;
45: Console.WriteLine;
46: end;
47: Console.ReadLine;
48: end.

u Localize o código no CD: \Code\Chapter 19\Ex10\.

A Listagem 19.11 utiliza o tipo enumerado CommandBehavior.SequentialAccess no cons-


trutor de SqlCommand.ExecuteReader( ) para lermos somente um campo por vez. A instrução
while nas linhas 40–44 obtém informações da estrutura a partir do resultado e produz a
seguinte saída para cada coluna.

ColumnName=EmployeeID
ColumnOrdinal=0
ColumnSize=4
NumericPrecision=10
NumericScale=255
IsUnique=False
IsKey=
BaseServerName=
BaseCatalogName=
BaseColumnName=EmployeeID
BaseSchemaName=
BaseTableName=
474 Capítulo 19 Utilizando os objetos Command e DataReader

DataType=System.Int32
AllowDBNull=False
ProviderType=8
IsAliased=
IsExpression=
IsIdentity=True
IsAutoIncrement=True
IsRowVersion=False
IsHidden=
IsLong=False
IsReadOnly=True
NESTE CAPÍTULO
CAPÍTULO 20 — DataAdapters

— Trabalhando com DataSets


DataAdapters e — Trabalhando com DataTables

DataSets

Este capítulo discute as classes que permitem trabalhar


com dados na memória, os quais você poderia ter
obtido de uma origem de dados ou simplesmente estar
utilizando na lógica da sua aplicação. As classes
desconectadas primárias dentro do .NET Framework são
DataAdapters, DataSets, DataColumns e DataRows.

DataAdapters
A classe DataAdapter é uma ponte entre a origem de
dados e os objetos desconectados que permitem
manipular dados. DataAdapters obtêm informações a
partir do banco de dados e preenchem DataSets
desconectados com esses dados. Além disso, DataAdapters
submetem quaisquer atualizações, que são armazenadas
em cache, de volta para o banco de dados. A classe
DataAdapter implementa a interface IDBDataAdapter. Como
outras classes de banco de dados, cada data provider
contém sua própria classe DataAdapter. Portanto, o SQL
Data Provider utiliza uma SQLDataAdapter e o Borland
Data Provider utiliza uma BDPDataAdapter. A DataAdapter
específica de cada data provider descende da classe
DBDataAdapter, que descende da classe DataAdapter. Este
capítulo demonstra o uso da classe SQLDataAdapter, mas
os mesmos conceitos podem ser aplicados às
DataAdapters de outros data providers.

Composição de DataAdapter
A Figura 20.1 ilustra como a classe DataAdapter é
composta.
Os vários objetos que compõem a classe DataAdapter
são utilizados para preencher o conteúdo de uma classe
DataSet. A classe DataAdapter é composta de quatro
objetos Command: SelectCommand, InsertCommand,
476 Capítulo 20 DataAdapters e DataSets

UpdateCommand e DeleteCommand. Como seus nomes sugerem, esses objetos Command contêm a
instrução SQL que você utilizaria para realizar a operação especificada contra o banco de
dados. Além disso, a DataAdapter contém uma coleção para mapear nomes entre as tabelas
de origem e as tabelas DataSet. A Figura 20.2 retrata a estrutura da coleção TableMappings.
Considerando que uma classe DataSet esteja inteiramente desconectada da origem de
dados, ela não sabe nada sobre como as tabelas que ela contém são mapeadas para a ori-
gem de dados. A classe DataAdapter trata isso pelo membro TableMappings, uma coleção.
Como mostrado na Figura 20.2, a coleção TableMappings contém objetos DataTable-
Mapping. Cada objeto DataTableMapping mapeia uma única DataTable para uma tabela na
origem de dados. Sua propriedade DataSetTable obtém e configura o nome da tabela em
um DataSet. A SourceTable refere-se ao nome da tabela, com distinção de maiúsculas/mi-
núsculas, na origem de dados. O número desses objetos depende de quantas tabelas fo-
ram obtidas a partir da origem de dados. Na maioria dos casos, isso seria uma única ta-
bela. Cada objeto DataTableMapping contém uma coleção de objetos DataColumnMapping. O
objeto DataColumnMapping contém dois membros. O DataSetColumn é um nome, sem distin-
ção de maiúsculas/minúsculas, para um nome de coluna dentro do DataSet. O SourceCo-
lumn é um nome, com distinção de letras maiúsculas e minúsculas, de coluna como ela
existe na origem de dados.

Criando um DataAdapter
A classe DataAdapter tem quatro construtores – cada um com uma combinação diferente
de parâmetros para especificar um comando de seleção ou conexão. Por exemplo, dadas
as seguintes strings de conexão e instrução select

c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
c_sel_emp = 'select * from Employees';

SelectCommand TableMappings
Parâmetros

InsertCommand
Parâmetros

UpdateCommand
Parâmetros

DeleteCommand
Parâmetros

FIGURA 20.1 Composição de um DataAdapter.


DataAdapters 477

FIGURA 20.2 Coleção TableMappings.

Um DataAdapter pode ser criado sem especificar nenhum parâmetro – caso em que a
propriedade de comando apropriada deve ser especificada:

var
sqlDA: SqlDataAdapter;
sqlCMD: SQLCommand;
sqlCN: SQLConnection;
begin
sqlCN := SQLConnection.Create(c_cnstr);
sqlCmd := SQLCommand.Create(c_sel_emp, sqlCN);
sqlDA := SQLDataAdapter.Create;
sqlDA.SelectCommand := sqlCmd;
end.

Caso contrário, a instrução select pode ser passada como um parâmetro para o cons-
trutor da DataAdapter:
478 Capítulo 20 DataAdapters e DataSets

sqlDA := SQLDataAdapter.Create(sqlCmd);

Você também pode passar uma instrução select e um objeto Connection para o cons-
trutor da DataAdapter:
c_sel_emp = 'select * from Employees';
var
sqlDA: SqlDataAdapter;
sqlCN: SQLConnection;
begin
sqlCN := SQLConnection.Create(c_cnstr);
sqlDA := SQLDataAdapter.Create(c_sel_emp, sqlCN);

Por fim, você pode construir uma DataAdapter especificando instrução select e a string
de conexão:
const
c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
c_sel_emp = 'select * from Employees';
var
sqlDA: SqlDataAdapter;
begin
sqlDA := SQLDataAdapter.Create(c_sel_emp, c_cnstr);
end.

Ao passar um Connection para o construtor de DataAdapter, o DataAdapter abrirá o


Connection, se ele já não estiver aberto. Em um cenário típico, você utilizará múltiplas
DataAdapters para obter e realizar atualizações em diferentes tabelas na origem de dados.
Ao passar uma string de conexão, em oposição a um objeto Connection para o construtor
DataAdapter, o DataAdapter o cria internamente e o conecta à origem de dados. É melhor
passar um objeto Connection porque múltiplas DataAdapters podem compartilhar o mesmo
Connection.

Obtendo resultados de consultas


O objeto DataAdapter.SelectCommand é utilizado para submeter e obter resultados de uma
consulta. Esses resultados então podem ser utilizados para preencher um objeto DataSet
ou DataTable. Além disso, o DataAdapter pode ser utilizado para executar consultas parame-
trizadas ou stored procedures para obter resultsets a partir de um banco de dados.

Preenchendo um DataSet
A Listagem 20.1 ilustra como utilizar DataAdapter para preencher um objeto DataSet.

LISTAGEM 20.1 Preenchendo um objeto DataSet


1: program FillDataSet;
2:
3: {$APPTYPE CONSOLE}
4:
DataAdapters 479

LISTAGEM 20.1 Continuação


5: {%DelphiDotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: System.Data,
9: System.Data.SqlClient;
10:
11: const
12: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
13: c_sel_emp = 'select * from Employees; select * from Customers;';
14: var
15: sqlDA: SqlDataAdapter;
16: nwDS: DataSet;
17: begin
18: sqlDA := SQLDataAdapter.Create(c_sel_emp, c_cnstr);
19: nwDS := DataSet.Create;
20:
21: sqlDA.Fill(nwDS);
22: end.

u Localize o código no CD: \Code\Chapter 20\Ex01\.

A classe DataAdapter na Listagem 20.1 obtém duas tabelas na origem de dados: Emplo-
yees e Customers (empregados e clientes) (linha 13). A forma utilizada para obter os re-
sultados dessa consulta e preencher um DataSet é pelo método Fill( ) como mostrado na
linha 21.
Nesse exemplo, o método Fill( ) realiza internamente vários passos. Primeiro, ele
cria um objeto Connection e se conecta à origem de dados especificada na constante
c_cnstr. Em seguida, ele executa a instrução select contida no objeto SelectCommand contra
a origem de dados. O comando é a constante c_sel_emp que foi passada para o construtor
na linha 19. Por fim, ele obtém tanto os dados como os nomes e tipos de coluna, que ele
utiliza para preencher o objeto DataSet.

Preenchendo um DataTable
Quando consultamos uma única tabela, a classe DataAdapter pode ser utilizada para
preencher um objeto DataTable. Por exemplo, dado um SelectCommand com a seguinte ins-
trução select

c_sel_emp = 'select * from Employees';

Você poderia passar o DataTable para o método DataAdapter.Fill( ) em vez de um


DataSet.

empDT := DataTable.Create;
sqlDA.Fill(empDT);

u Localize o código no CD: \Code\Chapter 20\Ex02\.


480 Capítulo 20 DataAdapters e DataSets

Obtendo dados por meio de uma consulta parametrizada


A Listagem 20.2 ilustra como obter dados por meio de uma consulta parametrizada.

LISTAGEM 20.2 Utilizando o DataAdapter com uma consulta parametrizada


1: program FillFromParamQry;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: SysUtils,
9: System.Data,
10: System.Data.SqlClient;
11:
12: const
13: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
14: c_sel_cst = 'select * from customers where country = @Country';
15: var
16: sqlCn: SqlConnection;
17: sqlDA: SqlDataAdapter;
18: sqlCmd: SqlCommand;
19: nwDS: DataSet;
20: begin
21: sqlCn := SqlConnection.Create(c_cnstr);
22: sqlCmd := SqlCommand.Create(c_sel_cst, sqlCn);
23: sqlCmd.Parameters.Add('@Country', SqlDBType.NChar, 15);
24: sqlCmd.Parameters['@Country'].Value := 'Germany';
25:
26: sqlDA := SqlDataAdapter.Create(sqlCmd);
27: nwDS := DataSet.Create;
28:
29: sqlDA.Fill(nwDS);
30: end.

u Localize o código no CD: \Code\Chapter 20\Ex03\.

O Capítulo 19 ilustra como utilizar o objeto SQLCommand com parâmetros. Isso não é di-
ferente. Como mostrado na Listagem 20.2, você simplesmente adiciona os parâmetros,
atribui seus valores e então cria a classe DataAdapter com o objeto Command contendo o parâ-
metro.

Mapeando resultados de consultas


Ao preencher um DataSet a partir de um DataAdapter, os nomes de tabela no DataSet não
correspondem aos nomes de tabela no DataSource por padrão. Em vez disso, por padrão,
DataAdapters 481

elas são chamadas Table, Table1, Table2 e assim por diante. Mas os nomes das colunas
correspondem aos nomes das colunas na origem de dados. Isso pode ser confuso ao tra-
balhar com a tabela; portanto, você vai querer da um jeito nisso. Primeiro, você pode
querer nomear a tabela no DataSet com algo mais significativo. Muito provavelmente,
você dará à tabela o mesmo nome que ela tem na origem de dados. Além disso, você vai
querer remover qualquer obscuridade nos nomes de campo. Por exemplo, se tiver um
campo na origem de dados chamado FName ou SCode, você pode nomeá-los como FirstName
ou StateCode no DataSet. O DataAdapter irá reter o relacionamento na coleção TableMappings.
A Listagem 20.3 ilustra esse conceito.

LISTAGEM 20.3 Exemplo de DataMapping


1: program mappings;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\v1.1.4322\
➥System.Data.dll'}
6:
7: uses
8: System.Data,
9: System.Data.SqlClient,
10: System.Data.Common,
11: System.Collections;
12:
13: const
14: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
15: c_sel_emp = 'select * from Employees; select * from Customers;';
16: var
17: sqlDA: SqlDataAdapter;
18: nwDS: DataSet;
19: dt: DataTable;
20: dtEnum: IEnumerator;
21: coEnum: IEnumerator;
22: dc: DataColumn;
23:
24: tmEnum: IEnumerator;
25: dtm: DataTableMapping;
26:
27: cmEnum: IEnumerator;
28: dcm: DataColumnMapping;
29: begin
30: sqlDA := SQLDataAdapter.Create(c_sel_emp, c_cnstr);
31: nwDS := DataSet.Create;
32:
33: // Mapeia as tabelas
34:
35: // Remove os caracteres de comentário aqui para realizar mapeamento
482 Capítulo 20 DataAdapters e DataSets

LISTAGEM 20.3 Continuação


36: dtm := sqlDA.TableMappings.Add('Table', 'Employees');
37: dtm.ColumnMappings.Add('EmployeeID', 'EmpID');
38: dtm := sqlDA.TableMappings.Add('Table1', 'Customers');
39: dtm.ColumnMappings.Add('CustomerID', 'CustID');
40:
41: sqlDA.Fill(nwDS);
42:
43: tmEnum := sqlDA.TableMappings.GetEnumerator;
44:
45: while tmEnum.MoveNext do
46: begin
47: dtm := tmEnum.Current as DataTableMapping;
48: Console.WriteLine('DataSet Table: {0}, Data source table: {1}',
49: [dtm.DataSetTable, dtm.SourceTable]);
50:
51: cmEnum := dtm.ColumnMappings.GetEnumerator;
52: while cmEnum.MoveNext do
53: begin
54: dcm := cmEnum.Current as DataColumnMapping;
55: Console.WriteLine(' DataSet Column: {0}, Data source column: {1}',
56: dcm.DataSetColumn, dcm.SourceColumn);
57: end;
58: end;
59:
60: // Escreve as tabelas.
61: dtEnum := nwDS.Tables.GetEnumerator;
62: while dtEnum.MoveNext do
63: begin
64: dt := dtEnum.Current as DataTable;
65: Console.WriteLine(dt.TableName);
66:
67: coEnum := dt.Columns.GetEnumerator;
68:
69: while coEnum.MoveNext do
70: begin
71: dc := coEnum.Current as DataColumn;
72: Console.WriteLine(' '+dc.ColumnName);
73: end;
74: Console.WriteLine('=================');
75: end;
76:
77: Console.ReadLine;
78: end.

u Localize o código no CD: \Code\Chapter 20\Ex04\.

Na Listagem 20.3, quando as linhas 36–39 são desativadas com caractere de comen-
tário, não há objetos de mapeamento contidos na coleção DataAdapter.TableMappings. Por-
tanto, a saída é esta mostrada aqui:
Trabalhando com DataSets 483

[n:\chapter 20\mappings]mappings.exe
Table
EmployeeID
LastName

Table1
CustomerID
CompanyName

Observe que os nomes das tabelas não têm nada a ver com os nomes reais das tabelas
obtidas, enquanto os nomes de coluna têm. Removendo o caractere de comentário das
linhas 36–39, a saída muda para

[n:\chapter 20\mappings]mappings.exe
DataSet Table: Employees, Data source table: Table
DataSet Column: EmpID, Data source column: EmployeeID
DataSet Table: Customers, Data source table: Table1
DataSet Column: CustID, Data source column: CustomerID
Employees
EmpID
LastName
Customers
CustID
CompanyName

Agora, você verá que as informações de mapeamento são impressas e os nomes de


tabela são aqueles especificados na chamada para a função TableMappings.Add( ). Essa fun-
ção retorna um objeto DataTableMapping. As linhas 37 e 39 então utilizam esse objeto para
mapear diferentes nomes de coluna para as colunas EmployeeID e CustomerID chamando sua
função ColumnMappings.Add( ). De certo modo, a função Add( )para os objetos TableMappings
e ColumnMappings cria nomes de alias para aqueles que são retidos no objeto DataSet.

Trabalhando com DataSets


O DataSet é uma representação na memória da estrutura de dados na forma de tabelas, re-
lacionamentos e constraints como unique constraint (chaves únicas) e foreign keys (cha-
ves estrangeiras).
O DataSet é desconectado da origem de dados. Em geral, ele obtém seus dados a partir
do DataAdapter e nunca se comunica diretamente com um Connection. Ao manipular da-
dos no DataSet, ele mantém as versões de cada linha alterada. Isso permite que as altera-
ções sejam armazenadas de volta na origem de dados por um DataAdapter ou canceladas.
DataSets não são específicos de um data provider. Eles são capazes de obter e armazenar
dados por meio de um DataAdapter específico de qualquer data provider.
Você irá trabalhar com dois tipos de DataSets – o não-tipificado e o fortemente tipifi-
cado. Um DataSet não-tipificado refere-se à classe base, DataSet, enquanto um DataSet for-
484 Capítulo 20 DataAdapters e DataSets

temente tipificado é descendente de um DataSet cujas classes internas, como DataTable e


DataRow, são nomeadas e contêm propriedades adicionais de acordo com a estrutura den-
tro do DataSet. Este capítulo focaliza DataSets não-tipificados. Um capítulo posterior dis-
cute como criar DataSets fortemente tipificados. As operações discutidas aqui se aplicarão
aos dois tipos de DataSets.

Composição de DataSet
Talvez algumas pessoas digam que o único objeto Delphi nativo que se assemelha ao Da-
taSet seja o ClientDataSet. Entretanto, o TClientDataSet só é semelhante ao DataSet porque é
um objeto desconectado que pode manipular um resultset na memória. O DataSet, por
outro lado, é capaz de conter múltiplos resultsets, seus relacionamentos e constraints.
Em outras palavras, o DataSet pode conter múltiplos elementos de um banco de dados re-
lacional.
A Figura 20.3 ilustra a composição de um DataSet.

O DataTableCollection e DataTables
O DataSet contém uma coleção de DataTables, que é acessada pela propriedade DataSet.Tables.
DataSet.Tables é do tipo DataTableCollection. Anteriormente, mencionou-se que um DataSet é
semelhante ao TClientDataSet. De fato, o DataTable é muito semelhante ao TclientDataSet.

FIGURA 20.3 As classes que compõem um Dataset.


Trabalhando com DataSets 485

O DataTable contém objetos DataColumn que encapsulam colunas e objetos Constraint


que encapsulam constraints como unicidade, exclusões em cascata e assim por diante.
Esses objetos são acessados pelas propriedades Columns e Constraints dos tipos DataColumn-
Collection e ConstraintCollection, respectivamente.
A propriedade DataSet.Rows permite acessar uma linha dentro do DataTable. A proprie-
dade Rows é do tipo DataRowCollection e contém uma coleção de objetos DataRow.
Cada DataTable contém um objeto DataView, DefaultDataView. O Capítulo 21 entra em
mais detalhes sobre DataViews; portanto, eles não serão discutidos aqui, exceto para dizer
que esses objetos fornecem o mecanismo para exibição e manipulação interativa de dados.
DataTables são semelhantes a DataReaders, mas diferem porque um DataReader é um ob-
jeto conectado aos dados e com acesso de leitura e somente para frente (forward-only e
read-only). Um DataTable é um contêiner, em memória, para dados com poucas restrições
em modificar, navegar, pesquisar e filtrar .

O DataRelationCollection
O DataSet é capaz de manter links entre tabelas pai e tabelas filhas. Esses links ou relacio-
namentos são mantidos por objetos DataRelation. Esses objetos são acessíveis por uma
propriedade DataRelationCollection, DataSet.Relations.
O objeto DataRelation é útil na manutenção da integridade de dados dentro do Data-
Set e para navegação nas tabelas pais e tabelas filhas. Isso pode ser utilizado, por exem-
plo, para conectar logicamente uma tabela de cliente com a tabela de pedidos.

O DataViewManager
Exibir os dados contidos em DataSets requer que os objetos de interface com o usuário
sejam conectados de algum modo com os objetos DataSet reais. Esse conceito é chama-
do de vinculação de dados (data binding). O objeto que reside entre os objetos de exibi-
ção e objetos DataTable é um DataView. Portanto, um DataView representa a exibição de da-
dos de uma DataTable. O DataViewManager gerencia configurações de visualização padrão
para todas as tabelas em um DataSet. O Capítulo 21 discute a vinculação de dados em
mais detalhes.

Operações de DataSet
Você já viu várias operações na classe DataSet nas ilustrações sobre o DataAdapter. Especifi-
camente, você viu como um DataSet pode ser preenchido tanto com a estrutura como
com os dados de uma origem de dados. Esse é o cenário típico que você provavelmente
vai precisar para utilizar o DataSet. Entretanto, é possível tanto construir como manipular
um DataSet sem nunca tê-lo preenchido a partir de uma origem de dados. Em ambos os
cenários, as operações são as mesmas. Este capítulo focaliza principalmente a utilização
de DataSet isoladamente. O Capítulo 22 discute como criar aplicações que obtêm, mani-
pulam e salvam dados de volta em um banco de dados.
486 Capítulo 20 DataAdapters e DataSets

Criando DataSets
Há dois construtores para a classe DataSet. Um não aceita parâmetros e é utilizado como

MyDataSet := DataSet. Create( );

Esse construtor cria a instância DataSet com um nome padrão de NewDataSet. Um se-
gundo construtor aceita o nome como um parâmetro e é invocado como mostrado aqui:

MyDataSet := DataSet.Create('DataSetName');

Alguns outros métodos utilizados para criar um DataSet são


— Copy( ) – Cria um DataSet a partir de outro, copiando tanto a estrutura como os dados.
— Clone( ) – Cria um DataSet a partir de outro, copiando somente as informações es-
truturais.
— GetChanges( ) – Cria um DataSet a partir de outro, copiando somente as linhas que
foram modificadas.

Adicionando, removendo e indexando tabelas


Você já viu como DataTables podem ser adicionadas a um DataSet pelo método DataAdap-
ter.Fill( ). Você também pode adicionar uma tabela diretamente ao DataSet chamando
o método Add( ) do objeto DataTableCollection.

dsNW := DataSet.Create('BusinessApp');
dsNW.Tables.Add('Employee');

Você também pode adicionar várias tabelas em uma chamada passando um array de
tabelas para o método AddRange( ).
dsNW := DataSet.Create('BusinessApp');
dtEmp := DataTable.Create('Employee');
dtCust := DataTable.Create('Customer');
dsNW.Tables.AddRange([dtEmp, dtCust]);

Para remover uma tabela do DataSet, chame o método Remove( ) ou RemoveAt( ). Remo-
ve( ) remove o DataTable especificado:

var
dsNW: DataSet;
dtTemp: DataTable;
begin
dtTemp := dsNW.Tables.Add('TempTable');
// utiliza dtTemp
dsNW.Tables.Remove(dtTemp);

O método RemoteAt( ) remove uma tabela a partir do índice especificado:

dsNW.Tables.RemoveAt(2);

Para remover todas as tabelas do DataSet, utilize o método Clear( ):

dsNW.Tables.Clear;
Trabalhando com DataTables 487

Você pode determinar se um DataSet contém uma tabela utilizando o método Con-
tains( ), que retorna um valor Booleano, ou o método IndexOf( ), que retorna o índice da
tabela ou -1 se a tabela não existir.

Trabalhando com DataTables


Um dos componentes mais versáteis que Delphi continha era o TClientDataSet. Enquanto
originalmente incluído para utilização na tecnologia DataSnap do Delphi, o TClientDataSet
revelou-se um objeto útil para manipular tabelas na memória.
Da mesma forma, o uso típico do DataSet é trabalhar em dados a partir de uma ori-
gem de dados que foram obtidos por um DataAdapter. Entretanto, DataSet e DataTables tam-
bém são estruturas na memória que fornecem o mesmo nível de versatilidade oferecido
por TClientDataSet. De fato, o DataTable assemelha-se mais estritamente ao TClientDataSet.
Esta seção focaliza como utilizar o DataTable.

Definindo colunas
Para definir colunas em um DataTable, você pode utilizar o método Add( ) ou AddRange( )
do DataColumnCollection. O código a seguir ilustra como utilizar o método Add( ):

dtEmp := DataTable.Create('Employee');
dtEmp.Columns.Add('FirstName', TypeOf(String));
dtEmp.Columns.Add('LastName', TypeOf(String));
dtEmp.Columns.Add('Age', TypeOf(Integer));

Nos exemplos anteriores, você especifica o nome e tipo de coluna. Note que os atri-
butos de coluna, como comprimento de campo, permitir nulo, somente leitura e assim
por diante, não são especificados. Você teria de se referir a cada instância de coluna para
configurar esses valores. Outra abordagem seria criar instâncias DataColumn diretamente, o
que permitiria especificar esses vários atributos e então passar cada DataColumn para o mé-
todo Add( ) como mostrado aqui:

dtEmp := DataTable.Create('Employee');
dcFName := DataColumn.Create('FirstName', TypeOf(String));
dcFName.MaxLength := 30;
dcFName.AllowDBNull := False;
dcLName := DataColumn.Create('LastName', TypeOf(String));
dcLName.MaxLength := 30;
dcFName.AllowDBNull := False;

dcAge := DataColumn.Create('Age', TypeOf(Integer));


dcAge.AllowDBNull := True;
dtEmp.Columns.Add(dcFName);
dtEmp.Columns.Add(dcLName);
dtEmp.Columns.Add(dcAge);
488 Capítulo 20 DataAdapters e DataSets

Utilizando o método AddRange( ), você pode passar um array de Columns, que realiza o
mesmo que as três últimas linhas mostradas anteriormente:

dtEmp.Columns.AddRange([dcFName, dcLName, dcAge]);

Alguns dos atributos mais comuns para um DataColumn são listados na Tabela 20.1.

TABELA 20.1 Atributos DataColumn


Propriedade DataColumn Descrição
AllowDBNull Especifica se valores nulos são permitidos nessa coluna.
AutoIncrement Especifica se a coluna é um campo com auto-incremento, que ocorre
quando novas linhas são adicionadas à tabela.
AutoIncrementSeed Especifica o valor inicial para campos com auto-incremento.
AutoIncrementStep Especifica o valor em que os campos de auto-incremento são
incrementados.
Caption Especifica o nome do campo como ele aparecerá em controles
vinculados a dados.
DataType Especifica o tipo de dados para a coluna.
DefaultValue Especifica um valor padrão para a coluna quando uma nova linha é
adicionada à tabela.
MaxLength Especifica um comprimento máximo para a coluna.
ReadOnly Especifica se a coluna é somente leitura (read-only).
Unique Especifica se os valores na coluna devem ser únicos.

Para localizar se existe uma coluna em um DataTable, você pode utilizar o método
Contains( ) do DataColumnsCollection. O seguinte código ilustra como obter uma referência
para um DataColumn e depois remover a DataColumn do DataTable:

if dtEmp.Columns.Contains('Age') then
begin
dcAge := dtEmp.Columns['Age'];
dtEmp.Columns.Remove(dcAge);
end;

Uma maneira alternativa de escrever essa instrução seria

dcAge := dtEmp.Columns['Age'];
if Assigned(dcAge ) then
dtEmp.Columns.Remove(dcAge);

Você também pode especificar o nome da coluna para removê-la:

dtEmp.Columns.Remove('Age');
Trabalhando com DataTables 489

Definindo chaves primárias


Uma chave primária (primary key) é composta de uma ou mais colunas que definem
uma linha única no DataTable. Se uma única coluna é definida como uma chave primária,
essa linha contém uma unique constraint. Se múltiplas linhas forem definidas como a
chave primária, a combinação dessas colunas deve ser única entre linhas no DataTable.
A propriedade DataTable.PrimaryKey é definida como um array de DataColumns. Para cri-
ar uma chave primária em uma tabela, simplesmente atribua esse array a essa proprieda-
de. Esse array poderia conter uma única ou múltiplas referências DataColumn. O seguinte
código ilustra a atribuição de uma chave primária de uma única coluna:

var
pkAry: array[0..0] of DataColumn;

begin
dtEmp := DataTable.Create('Employee')
dcEmpNo := DataColumn.Create('EmpNo', TypeOf(Integer));
dcFName := DataColumn.Create('FirstName', TypeOf(String));
dtEmp.Columns.AddRange([dcFName, dcLName, dcAge]);
pkAry[0] := dcEmpNo;
dtEmp.PrimaryKey := pkAry;

Trabalhando com constraints


As constraints permitem que você mantenha a integridade de dados dentro do DataSet.
Como o nome indica, as constraints (restrições) impõem uma restrição naquilo que
pode ser adicionado a uma tabela de dados. Certas constraints, como as de unicidade e
exclusões em cascata, foram discutidas anteriormente neste capítulo, bem como as
constraints que desativam um valor nulo ou tornam uma coluna única. ADO.NET tam-
bém tem a classe Constraint da qual descendem duas classes derivadas: UniqueConstraint e
ForeignKeyConstraint.

UniqueConstraint – Impedem que múltiplas linhas dentro de um DataTable tenham valores idên-
ticos nas colunas específicas que compõem a unique constraint.

ForeignKeyConstraint – Estabelece uma regra em um DataSet baseado em um relacionamento


pai/filho de duas DataTables.

Adicionando um objeto UniqueConstraint


O objeto UniqueConstraint impõe integridade impedindo a duplicação de valores em uma
única ou múltiplas colunas de um DataTable. Os seguintes trechos de código ilustram
como criar um UniqueConstraint em um DataTable:

uc := UniqueConstraint.Create('EmpNo', dtEmp.Columns['EmpNo'], True);


dtEmp.Constraints.Add(uc);
490 Capítulo 20 DataAdapters e DataSets

O primeiro parâmetro para o construtor UniqueConstraint é o nome da constraint. O


segundo parâmetro é uma referência à coluna em que a constraint deve ser aplicada. O
parâmetro final indica se essa é uma constraint de chave primária. Uma vez que a cons-
traint foi criada, ela é adicionada à coleção DataTable.Constraints. Para adicionar uma uni-
que constraint em múltiplas colunas, simplesmente passe um array de DataColumns como
o segundo parâmetro para o construtor UniqueConstraint:
dcAry[0] := dtEmp.Columns['EmpNo'];
dcAry[1] := dtEmp.Columns['FirstName'];

uc := UniqueConstraint.Create('EmpNo', dcAry, True);


dtEmp.Constraints.Add(uc);

Utilizando objeto ForeignKeyConstraint


Um objeto ForeignKeyConstraint permite impor regras de integridade referencial em Data-
Tables dentro de um DataSet. Além disso, permite especificar uma ação a tomar quando o
pai de um relacionamento pai/filho tiver sido excluído, atualizado ou aceito. O seguinte
código ilustra como criar e adicionar uma ForeignKeyConstraint para uma DataTable dentro
de um DataSet. Para propósitos ilustrativos, esse código assume uma tabela pai, Employee,
com uma tabela filha, Address.
fc := ForeignKeyConstraint.Create('EmpAddress',
dsCompany.Tables['Employee'].Columns['EmpNo'],
dsCompany.Tables['Address'].Columns['EmpNo']);
dsCompany.Tables['Employee'].Constraints.Add(fc);

Colocando essa constraint na tabela Employee, as colunas dentro da tabela filha de-
vem referenciar uma linha válida na tabela pai.
Cada uma das três propriedades da ForeignKeyConstraint permitem especificar o que
deve acontecer quando forem feitas alterações à linha pai. Essas propriedades são
— AcceptRejectRule – Especifica uma ação a tomar na constraint quando AcceptChan-
ges( ) for chamado.
— DeleteRule – Especifica uma ação a tomar na constraint quando a linha pai for ex-
cluída.
— UpdateRule – Especifica a ação que ocorre quando a linha pai for atualizada.

As tabelas 20.2, 20.3 e 20.4 descrevem os valores válidos para as propriedades


AcceptRejectRule, DeleteRule e UpdateRules, respectivamente.

TABELA 20.2 Valores de AcceptRejectRule


Valor Descrição Padrão
Nenhum Nenhuma ação é tomada nas linhas filhas. Sim
Cascade Faz com que o método AcceptChanges( ) seja chamado nas linhas filhas
quando AcceptChanges( ) é chamado na linha pai. Isso talvez cause
resultados indesejáveis, como configurar o flag RowState nas linhas filhas
como Unchanged e, portanto, impedindo atualizações na origem de dados.
Trabalhando com DataTables 491

TABELA 20.3 Valores de DeleteRule


Valor Descrição Padrão
Cascade Faz com que as linhas filhas sejam excluídas. Sim
Nenhum Nenhuma ação é tomada. Se a integridade referencial for violada, uma
exceção será levantada.
SetDefault Configura o valor da linha filha com o padrão especificado.
SetNull Configura o valor da linha filha como NULL, se permitido.

TABELA 20.4 Valores de UpdateRule


Valor Descrição Padrão
Cascade Quando uma alteração é feita no valor pai vinculado, a mesma Sim
alteração é feita nos filhos.
Nenhum Nenhuma ação é tomada nas linhas filhas. Uma exceção é
levantada se a integridade referencial for violada.
SetDefault Configura o valor da linha filha com um padrão especificado.
SetNull Configura o valor da linha filha como NULL, se permitido.

Obtendo as constraints de uma origem de dados


Utilizando o método FillSchema( ) para preencher um DataSet, UniqueConstraints são auto-
maticamente criadas para o DataSet. Isso não é verdadeiro ao utilizar simplesmente o mé-
todo Fill( ). A Listagem 20.4 ilustra esse ponto.

LISTAGEM 20.4 Utilizando FillSchema( ) para obter UniqueContraints


1: program FillConstraints;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler 'c:\windows\microsoft.net\framework\
➥v1.1.4322\System.Data.dll'}
6:
7: uses
8: System.Data,
9: System.Data.SqlClient,
10: System.Collections;
11:
12: const
13: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
14: c_sel_ord = 'select * from orders';
15:
16: var
17: cnNW: SqlConnection;
18: dsNW: DataSet;
19: daNW: SqlDataAdapter;
492 Capítulo 20 DataAdapters e DataSets

LISTAGEM 20.4 Continuação


20: cmdOrd: SqlCommand;
21:
22: enumConst: IEnumerator;
23: uc: UniqueConstraint;
24:
25: begin
26: cnNW := SqlConnection.Create(c_cnstr);
27: dsNW := DataSet.Create;
28: cmdOrd := SqlCommand.Create(c_sel_ord, cnNW);
29: daNW := SqlDataAdapter.Create(cmdOrd);
30:
31: cnNW.Open;
32: try
33: daNW.FillSchema(dsNW, SchemaType.Mapped, 'Orders');
34: daNW.Fill(dsNW, 'Orders');
35: finally
36: cnNW.Close;
37: end;
38:
39: enumConst := dsNW.Tables['Orders'].Constraints.GetEnumerator;
40: while enumConst.MoveNext do
41: begin
42: uc := enumConst.Current as UniqueConstraint;
43: Console.WriteLine('{0}, PrimaryKey: {1}', [uc.Columns[0].ColumnName,
44: uc.IsPrimaryKey.ToString]);
45: end;
46: Console.ReadLine;
47: end.

u Localize o código no CD: \Code\Chapter 20\Ex05\.

Note que apenas as UniqueConstraints são criadas utilizando FillSchema( ) (linha 33).
ForeignKeyConstraints não são automaticamente criadas.

Trabalhando com DataRelations


DataRelations são semelhantes às ForeignContraints, porque formam um link entre uma ta-
bela pai e uma tabela filha pela qual integridade referencial pode ser imposta. De fato, ao
criar DataRelations, eles implicitamente criam a instância ForeignConstraint na coleção
Constraints. DataRelations vão além das capacidades de ForeignConstraints porque também
suportam a navegabilidade entre tabelas pai e filhas. Isso é particularmente útil com con-
troles vinculados aos dados (data-bound controls). Por meio de DataRelations, você tam-
bém pode criar colunas calculadas adicionais a partir de expressões.
A Listagem 20.5 mostra o método Create( ) de uma aplicação WinForm. O formulá-
rio nessa aplicação contém os componentes SqlConnection, SqlAdapter, SqlCommand e DataSet.
Essa Listagem ilustra como criar e utilizar DataRelations consultando duas tabelas a partir
do banco de dados Northwind que são relacionadas e criando programaticamente os ob-
Trabalhando com DataTables 493

jetos DataRelation tanto para a tabela pai (Suppliers) como para a tabela filha (Products).
As linhas 19–25 realizam a configuração dos componentes de dados. O resto do exemplo
lida com a criação e utilização dos objetos DataRelation.

LISTAGEM 20.5 Exemplo de DataRelations


1: constructor TWinForm.Create;
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'select * from suppliers; select * from products;';
5: var
6: pEnum: IEnumerator;
7: pDr: DataRow;
8: pCol: DataColumn;
9: cCol: DataColumn;
10: dr: DataRelation;
11: tn: TreeNode;
12: drAry: array of DataRow;
13: i: integer;
14:
15: begin
16: inherited Create;
17: InitializeComponent;
18:
19: SqlConnection1.ConnectionString := c_cnstr;
20: SqlCommand1.CommandText := c_sel;
21: SqlCommand1.Connection := SqlConnection1;
22: SqlDataAdapter1.SelectCommand := SqlCommand1;
23: SqlDataAdapter1.TableMappings.Add('Table', 'Suppliers');
24: SqlDataAdapter1.TableMappings.Add('Table1', 'Products');
25: SqlDataAdapter1.Fill(DataSet1);
26:
27: pCol := DataSet1.Tables['Suppliers'].Columns['SupplierID'];
28: cCol := DataSet1.Tables['Products'].Columns['SupplierID'];
29:
30: dr := DataRelation.Create('Sup_Prod', pCol, cCol);
31: DataSet1.Relations.Add(dr);
32:
33: // Cria colunas de expressão.
34: DataSet1.Tables['Products'].Columns.Add('ContactName', TypeOf(String),
35: 'Parent(Sup_Prod).ContactName');
36:
37: DataSet1.Tables['Suppliers'].Columns.Add('AvgPrice', TypeOf(Decimal),
38: 'AVG(Child(Sup_Prod).UnitPrice)');
39:
40: pEnum := DataSet1.Tables['Suppliers'].Rows.GetEnumerator;
41: while pEnum.MoveNext do
42: begin
43: pDr := pEnum.Current as DataRow;
494 Capítulo 20 DataAdapters e DataSets

LISTAGEM 20.5 Continuação


44: tn := TreeView1.Nodes.Add(pDr['CompanyName'].ToString);
45: drAry := pDr.GetChildRows(dr);
46: for i := Low(drAry) to High(drAry) do
47: tn.Nodes.Add(drAry[i]['ProductName'].ToString)
48: end;
49: end;

u Localize o código no CD: \Code\Chapter 20\Ex06\.

O construtor da classe DataRelation mostrado nessa Listagem (linha 30) aceita um nome
de string para a relação, uma referência DataColumn para tabela pai e uma referência DataColumn
para a tabela filha. Há outros construtores sobrecarregados (overloaded) que aceitam arrays
de colunas e um parâmetro Booleano adicional para especificar se cria ou não restrições.
As linhas 40–48 navegam pelas linhas pai/filhas e preenchem um controle TreeView. Essa
aplicação mostrada na Figura 20.4 também tem um grid (data bound) conectado a esse data-
set. As linhas 40–48 são fornecidas para mostrar como navegar pelas linhas baseadas no ob-
jeto DataRelation. Na linha 45, as linhas filhas são obtidas a partir da linha pai. Isso é feito
chamando o método DataRow.GetChildRow( ), que aceita DataRelation como o parâmetro.
As linhas 34–38 ilustram como criar colunas calculadas tanto nas DataTables pai
como nas filhas utilizando o DataRelation. Isso é feito passando uma expressão como o
terceiro parâmetro para o método DataColumns.Add( ). A sintaxe para essa expressão é
Parent(RelationName).ColumnName ou Child(RelationName).ColumnName. Nas linhas 34–35, uma
nova coluna, 'ContactName', é adicionada às linhas da tabela filha. Nas linhas 37–38, uma nova
coluna, 'AvgPrice', é adicionada à tabela pai. Note como somos capazes de passar expres-
sões para uma função de agregação (AVG nesse caso), a qual irá operar em todas as linhas
filhas.

FIGURA 20.4 Exemplo de DataRelation.


Trabalhando com DataTables 495

Manipulando dados – trabalhando com DataRow


As modificações nos dados contidos em uma DataTable são realizadas no nível de linha. A
classe DataRow contém os métodos que você utilizaria para realizar atualizações, exclusões
e adições em uma DataTable.

Adicionando linhas
Para adicionar uma linha a uma DataTable, primeiro chame o método DataTable.NewRow( ),
que retorna uma instância DataRow contendo as informações estruturais para essa linha.
Então, atribua valores para as colunas utilizando a instância DataRow obtida. O código a se-
guir ilustra o uso dessa função:

drCust := dtCust.NewRow;
drCust['CustomerID'] := 'XAWPA';
drCust['CompanyName'] := 'Xapware';
drCust['ContactName'] := 'Xavier Pacheco';
drCust['ContactTitle'] := 'Author';
dtCust.Rows.Add(drCust);

No código anterior, dtCust é uma instância de DataTable. drCust é uma instância de


DataRow. Uma vez que se atribuiu valores às colunas da DataRow, adicione a instância DataRow
à DataTable passando-a para o método Rows Add( ). Observe que quando o NewRow( ) é invo-
cado, as colunas da DataRow serão preenchidas com valores NULL ou padrão, dependendo
da estrutura da DataTable.

u Localize o código no CD: \Code\Chapter 20\Ex07\.

Modificando linhas
Para modificar uma linha, você pode simplesmente modificar diretamente os valores das
colunas como mostrado aqui:

drCust := dtCust.Rows[0];
drCust['ContactName'] := 'Mickey Mouse';

Outra maneira de modificar uma linha é armazenar as alterações em um buffer. Faça


isso chamando DataRow.BeginEdit( ) e DataRow.EndEdit( ), como mostrado aqui:

drCust := dtCust.Rows[0];
drCust.BeginEdit;
drCust['ContactName'] := 'Mickey Mouse';
drCust.EndEdit;

Chamando BeginEdit( ) impede-se que as constraints e regras de validação ocorram


até EndEdit( ) ser invocado. Uma vez que o EndEdit( ) é chamado, as alterações propostas
são verificadas por todas as regras de validação. BeginEdit( ) é implicitamente chamado
sempre que o conteúdo de um controle vinculado a dados é modificado. EndEdit( ) é cha-
mado pelo método DataRow.AcceptChanges( ).

u Localize o código no CD: \Code\Chapter 20\Ex07\.


496 Capítulo 20 DataAdapters e DataSets

Excluindo linhas
Para excluir uma linha, simplesmente chame o método DataRow.Delete( ) como mostrado
aqui:

dtCust.Rows[0].Delete;

u Localize o código no CD: \Code\Chapter 20\Ex07\.

Cancelando alterações
Quando DataRow.BeginEdit( ) for chamado, você pode invocar DataRow.CancelEdit( ), o que
descarta qualquer modificação armazenada em buffer.

Examinando RowState
DataSets, DataTables e DataRows são todos os objetos desconectados que estão operando em
dados na memória. O caso comum é aquele que você irá recuperar dados a partir de uma
origem de dados, modificá-los offline e, em seguida, salvar essas alterações novamente
no banco de dados. Para isso funcionar, é necessário que o DataSet armazene em cache
tanto o tipo de alteração como as alterações feitas para que ele possa saber o que precisa
ser enviado de volta à origem de dados. Essas alterações são armazenadas em cache na
DataRow. O tipo de alterações é mantido na propriedade DataRow.RowState. Alterações cor-
rentes são tratadas por meio do controle de versão da linha de um dataset, o qual é discu-
tido logo adiante.
A Tabela 20.5 mostra os vários valores RowState.

TABELA 20.5 Valores de DataRow.RowState


Valor de RowState Descrição
Unchanged A linha não foi modificada.
Detached A linha não foi atribuída à DataTable.
Added A linha foi adicionada à DataTable, mas não foi salva na origem de dados.
Modified A linha foi modificada e nenhuma alteração foi postada à origem de dados.
Deleted A linha foi excluída e nenhuma alteração foi postada à origem de dados.

Em qualquer ponto, você pode avaliar a propriedade RowState.

Versões de linha
Assim como ADO.NET precisa conhecer o tipo de alterações, ele também precisa ter tan-
to os dados originais como os alterados. DataRow mantém uma cópia dos dados originais e
atuais de uma linha. Os valores de coluna, de cada versão, podem ser lidos a partir da li-
nha especificando uma das opções do tipo enumerado DataRowVersion mostradas na Tabe-
la 20.6.
Trabalhando com DataTables 497

TABELA 20.6 Tipo enumerado DataRowVersion


DataRowVersion Descrição
Current O valor que está atualmente na coluna incluindo as últimas alterações, mas
não as alterações armazenadas em buffer.
Original O valor original da coluna quando obtido a partir da origem de dados.
Proposed A alteração armazenada em buffer para a coluna.
Default O valor Current da coluna, se ela não estiver sendo editada. O valor
Proposed da coluna se a linha está sendo editada.

O seguinte código ilustra como verificar a existência de uma versão em uma linha e
como obter uma versão específica de uma coluna por meio do índice informado:

if drCust.HasVersion(DataRowVersion.Proposed) then
begin
Console.WriteLine('Proposed Row – – ');
Console.WriteLine(' {0} : {1} : {2} : {3}',
[drCust['CustomerID', DataRowVersion.Proposed],
drCust['CompanyName', DataRowVersion.Proposed],
drCust['ContactName', DataRowVersion.Proposed],
drCust['ContactTitle', DataRowVersion.Proposed]]);
end;

u Localize o código no CD: \Code\Chapter 20\Ex08\.

Pesquisar, classificar e filtrar dados


Ser capaz de manipular DataSets na memória é apenas parcialmente útil. Também é neces-
sário ser capaz de classificar, pesquisar e filtrar dados para realmente adicionar utilidade a
esses objetos. Esta seção demonstra os vários meios pelos quais realizam-se essas tarefas.

Pesquisa utilizando o método Find( )


Freqüentemente, você precisará procurar um único registro. A classe DataRowCollection
contém um método Find( ) que pesquisa em uma ou mais colunas que compõem uma
chave primária. O seguinte código ilustra como utilizar o método Find( ) para obter uma
linha baseada numa chave primária composta de uma coluna:

dt := dsNW.Tables['Customers'];
pkAry[0] := dt.Columns['CustomerID'];
dt.PrimaryKey := pkAry;

dr := dt.Rows.Find('THEBI');
if dr < > nil then
Console.WriteLine(dr['CompanyName'])
else
Console.WriteLine('Record not found');
498 Capítulo 20 DataAdapters e DataSets

Nesse exemplo, dt é a DataTable para a tabela Costumers no banco de dados


Northwind. pkAry é um array de uma única dimensão de DataColumn declarado como

pkAry: array[0..0] of DataColumn;

Ele é utilizado para configurar a chave primária na DataTable. Por fim, o método Da-
taTable.Rows.Find( ) é invocado passando-se um valor pelo qual pesquisar.
O método Find( ) também pode ser utilizado em uma chave primária composta de
duas ou mais colunas. O seguinte código ilustra isso:

dt := dsNW.Tables['Order Details'];
pkAry2[0] := dt.Columns['OrderID'];
pkAry2[1] := dt.Columns['ProductID'];

dt.PrimaryKey := pkAry2;

dr := dt.Rows.Find([10251, 22]);
if dr < > nil then
Console.WriteLine('Quantity: {0} : Unit Price {1}',
[dr['Quantity'], dr['UnitPrice']])
else
Console.WriteLine('Record not found');

u Localize o código no CD: \Code\Chapter 20\Ex09\.

Nesse exemplo, a única diferença é que um array é utilizado com mais de um item.
Além disso, um array de inteiros é passado para o método Find( ). Observe que o parâme-
tro array do método Find( ) talvez contenha dados heterogêneos – isto é, dados de tipos
misturados (uma string, um inteiro e assim por diante).

Pesquisa utilizando instruções Select


Você também pode querer filtrar informações com base em alguns critérios de filtro. Isso
é possível utilizando o método DataTable.Select( ). Esse método retorna um array de ob-
jetos DataRow com base nos critérios passados para o método Select( ). O seguinte código
ilustra isso selecionando somente clientes na Alemanha:

dt := dsNW.Tables['Customers'];
drAry := dt.Select('Country = ''Germany''');
for i := Low(drAry) to High(drAry) do
Console.WriteLine('{0} : {1} : {2} : {3}', drAry[i]['ContactName'],
drAry[i]['City'], drAry[i]['Region'], drAry[i]['Country']);

u Localize o código no CD: \Code\Chapter 20\Ex10\.

Adicionando critérios de classificação


Você pode adicionar critérios de classificação ao método Select( ) de modo semelhante a
como classificaria utilizando SQL. O código a seguir mostra como isso é feito utilizando
Trabalhando com DataTables 499

uma versão sobrecarregada do método Select( ). O método Select( ) aceita um parâme-


tro string adicional que especifica o que classificar e em que ordem realizar a classifica-
ção.

drAry := dt.Select('Country = ''USA''', 'ContactName DESC');


for i := Low(drAry) to High(drAry) do
Console.WriteLine('{0} : {1} : {2} : {3}', drAry[i]['ContactName'],
drAry[i]['City'], drAry[i]['Region'], drAry[i]['Country']);

u Localize o código no CD: \Code\Chapter 20\Ex10\.

Filtrando RowState utilizando DataViewRowState


Em uma seção anterior, discutimos o conceito de RowState. Isto é, quando uma linha é
modificada, excluída ou adicionada, seu estado e versão são mantidos pela DataRow. Outra
versão sobrecarregada do método Select( ) aceita um parâmetro adicional, que é um tipo
enumerado DataViewRowState. O próximo código mostra como isso seria utilizado para fil-
trar somente aqueles registros que foram excluídos:

dt.Rows[0].Delete;
dt.Rows[1].Delete;

dvrs := DataViewRowState.Deleted;
drAry := dt.Select('', '', dvrs);
for i := Low(drAry) to High(drAry) do
Console.WriteLine(drAry[i]['CompanyName', DataRowVersion.Original]);

u Localize o código no CD: \Code\Chapter 20\Ex10\.


NESTE CAPÍTULO
— Exibindo dados com CAPÍTULO 21
DataView e
DataViewManager
— Data Binding
Trabalhando com
WinForms –
Visualização e
vinculação de dados

M uitos dos exemplos sobre o ADO.NET focalizaram


até aqui como trabalhar com as classes de ADO.NET e,
portanto, não abordaram como você poderia projetar
interfaces com o usuário em torno dessas classes.
Obviamente, não seria muito útil ter essa excelente
tecnologia de manipulação de dados sem a capacidade de
expô-la por uma GUI. Para construir efetivamente
interfaces práticas com o usuário, você deve oferecer ao
usuário não apenas a capacidade de visualizar os dados,
mas também de manipulá-los facilmente. O ADO.NET
conta com duas técnicas para realizar isso. A primeira é
fornecida pelas classes DataView e DataViewManager. Essas
classes fornecem uma camada entre os dados internos e
o consumidor desses dados – seja código personalizado
ou controles vinculados a dados. Elas também fornecem
a capacidade de modificar a maneira como os dados são
exibidos por meio da classificação, filtragem e pesquisa.
A segunda técnica é o método pelo qual os dados são
representados nos vários controles de interface com o
usuário. Essa técnica é chamada vinculação de dados.
Vários componentes entram em cena ao lidar com a
vinculação de dados. Este capítulo discute esses conceitos
em detalhe.

Exibindo dados com DataView


e DataViewManager
DataView e DataViewManager são as duas classes que
permitem a vinculação de dados para controles visuais.
Exibindo dados com DataView e DataViewManager 501

DataView pode ser utilizado para criar filtros personalizados que fornecem um subconjunto da Da-
taTable vinculada.

DataViewManager mantém as configurações padrão de DataView para cada DataTable.

A classe DataView
Uma classe DataView representa a visualização de um único DataTable. Uma boa maneira é
ver o DataView como sendo semelhante a uma VIEW em um banco de dados de SQL. O Da-
taView permite aos usuários não apenas classificar, filtrar e pesquisar linhas, mas também
manipulá-las. Portanto, o DataView permite especificar critérios que determinam como o
DataTable vinculado é representado para o usuário final. Você também pode ter múltiplos
DataViews aplicados a um único DataTable. Portanto, um DataView pode apresentar os dados
por ordem de classificação, por exemplo, enquanto o outro pode apresentar os dados com
certas linhas filtradas. Os dois DataViews estariam apresentando dados da mesma tabela
vinculada.

COMPARANDO AS CLASSES DATAVIEW E DATASOURCE


O DataView pode ser semelhante por natureza ao componente TDataSource do clássico Delphi.
Mas o DataView é inteiramente diferente. Por exemplo, no Delphi clássico, a classe TDataSet é mais
sinônimo da classe DataTable do .NET. O TDataSet clássico do Delphi mantinha seu próprio cur-
sor. Ao vincular múltiplos objetos TDataSource com um único objeto TDataSet, rolar um TData-
Source faria com que o cursor alterasse no nível do TDataSet. Isso, por sua vez, faria com que o se-
gundo TDataSource também exibisse a rolagem. Os DataViews não se comportam dessa maneira.
Você pode vincular duas classes DataView com um único DataTable. Como o DataTable não man-
tém um cursor (isso é tratado pelas classes de vinculação – informações adicionais sobre isso mais
adiante neste capítulo), rolar um DataView não afeta o segundo DataView. Isso é somente um
exemplo das muitas diferenças entre a arquitetura clássica de banco de dados do Delphi e a arqui-
tetura .NET.

DataViews são uma maneira poderosa de personalizar a maneira como dados são exi-
bidos para o usuário final sem passar pela camada de SQL para recuperar múltiplas repre-
sentações dos dados.
A Tabela 21.1 lista algumas propriedades chave da classe DataView.

TABELA 21.1 Propriedades de DataView


Propriedade Descrição
AllowDelete Especifica se permite a exclusão de linhas dentro do DataView.
AllowEdit Especifica se permite a edição de valores no DataView.
AllowNew Especifica se permite a inserção de linhas dentro do DataView.
AllowDefaultSort Especifica se utiliza ou não a classificação padrão para o DataView.
Count Retorna o número de linhas contido pelo DataView depois de a filtragem ser
aplicada.
DataViewManager Retorna o DataViewManager associado com esse DataView. Um DataViewManager
seria nulo para DataSets criados manualmente.
502 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

TABELA 21.1 Continuação


Propriedade Descrição
Item Retorna um DataRowView a partir da tabela associada para um índice
especificado de linha.
RowFilter Armazena a expressão utilizada para filtrar linhas na tabela associada.
RowStateFilter Armazena um conjunto de valores do tipo enumerado DataViewRowState para
filtragem.
Sort Armazena a coluna e ordem de classificação. Também suporta a classificação
em múltiplas tabelas separando com vírgulas as instruções de ordem de
classificação.
Table Refere-se ao DataTable sendo representado pelo DataView.

A Tabela 21.2 lista métodos-chave da classe DataView.

TABELA 21.2 Métodos de DataView


Método Descrição
AddNew( ) Adiciona um DataRowView ao DataView.
CopyTo( ) Copia os itens contidos no DataView para um Array.
Delete( ) Exclui uma linha do DataView.
Find( ) Localiza uma linha de acordo com o valor de uma chave de classificação
especificada.
FindRows( ) Retorna um Array de objetos DataRowView de acordo com o valor de uma chave de
classificação especificada.

A classe DataViewManager
A classe DataViewManager é basicamente a mesma em função que a classe DataView, exceto
que ela fornece essas capacidades por todo o DataSet. Você também pode utilizar DataView-
Manager para criar uma classe DataView para uma tabela dentro do DataSet contido pelo Data-
ViewManager. A Tabela 21.3 lista as propriedades-chave da classe DataViewManager.

TABELA 21.3 Propriedades de DataViewManager


Propriedade Descrição
DataSet Obtém ou configura o DataSet que o DataViewManager representa.
DataViewSettings Retorna um DataViewSettingCollection que contém o DataViewSetting
para cada DataTable no DataSet associado.

A classe DataViewManager contém o método CreateDataView( ), que cria uma instância


de um DataView para uma tabela especificada no DataSet.
Exibindo dados com DataView e DataViewManager 503

Projetos de exemplo com DataView e DataViewManager


As seções a seguir ilustram algumas técnicas que utilizam as classes DataView e DataViewMa-
nager. Você utilizará muitas dessas técnicas ao projetar suas próprias aplicações.

Vinculação de DataView simples


A Listagem 21.1 ilustra como vincular um componente de interface com o usuário a um
DataView e DataViewManager.

LISTAGEM 21.1 Exemplo de DataView simples


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM customers; SELECT * FROM employees; '+
4: ' SELECT * FROM suppliers';
5:
6: constructor TWinForm.Create;
7: begin
8: inherited Create;
9: InitializeComponent;
10: SqlConnection1.ConnectionString := c_cnstr;
11: SqlCommand1.Connection := SqlConnection1;
12: SqlCommand1.CommandText := c_sel;
13: SqlConnection1.Open;
14: try
15: SqlDataAdapter1.SelectCommand := SqlCommand1;
16: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
17: SqlDataAdapter1.TableMappings.Add('Table1', 'Employees');
18: SqlDataAdapter1.TableMappings.Add('Table2', 'Suppliers');
19:
20: SqlDataAdapter1.Fill(DataSet1);
21: DataGrid1.DataSource := DataSet1;
22: DataGrid2.DataSource := DataSet1.Tables['Employees'].DefaultView;
23: finally
24: SqlConnection1.Close;
25: end;
26: end;
27:
28: procedure TWinForm.RadioButton3_Click(sender: System.Object;
➥e: System.EventArgs);
29: begin
30: DataGrid2.DataSource := DataSet1.Tables['Suppliers'].DefaultView;
31: end;
32:
33: procedure TWinForm.RadioButton2_Click(sender: System.Object;
➥e: System.EventArgs);
34: begin
35: DataGrid2.DataSource := DataSet1.Tables['Customers'].DefaultView;
36: end;
504 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

LISTAGEM 21.1 Continuação


37:
38: procedure TWinForm.RadioButton1_Click(sender: System.Object;
➥e: System.EventArgs);
39: begin
40: DataGrid2.DataSource := DataSet1.Tables['Employees'].DefaultView;
41: end;

u Localize o código no CD: \Code\Chapter 21\Ex01\.

A Listagem 21.1 é um bloco de código parcial de um dos projetos de exemplo. O pro-


pósito desse exemplo é ilustrar como um DataGrid pode estar vinculado tanto a um Data-
Set como a um DataTable pelo DefaultView de um DataTable.

NOTA
Embora muitas das atribuições de propriedade mostradas nessa Listagem possam ser feitas em
tempo de projeto no IDE, aqui elas estão mostradas no código para propósitos ilustrativos.

Grande parte do código nesse construtor (linhas 10–20) realiza a configuração


do DataSet pelo DataAdapter. Na linha 21, DataGrid1 é vinculado ao DataSet; e na linha 22,
DataGrid2 é vinculado ao DefaultView da tabela Employees no DataSet. A propriedade Data-
Table.DefaultView retorna o DataView padrão para esse DataTable. Os vários handlers de
evento ilustram como o DataGrid2 poder ser vinculado a diferentes DataViews dentro do
DataSet por meio de uma atribuição simples. Mesmo que o código para realizar isso seja
simples como o nome dessa seção poderia sugerir, essa técnica de vinculação é referida
como uma vinculação de dados complexos. Essa aparente contradição em termos é dis-
cutida mais adiante neste capítulo.
A Figura 21.1 ilustra o formulário resultante dessa aplicação. Note como o primeiro
DataGrid permite examinar múltiplas tabelas dentro do DataSet, enquanto o segundo Da-
taGrid visualiza apenas um único DataTable por vez.

Filtrando um DataView
A filtragem de um DataView para exibir apenas certo conjunto de linhas é feita especifican-
do uma expressão de filtro para a propriedade DataView.RowFilter.
A Listagem 21.2 mostra a configuração do código de exemplo utilizado nesta seção.

LISTAGEM 21.2 Exemplo que mostra a filtragem de DataView


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers; SELECT * FROM Employees; '+
4: 'SELECT * FROM Products;';
5:
6: constructor TWinForm.Create;
7: begin
Exibindo dados com DataView e DataViewManager 505

FIGURA 21.1 Um exemplo de DataView simples.

LISTAGEM 21.2 Continuação


8: inherited Create;
9: InitializeComponent;
10: SqlConnection1.ConnectionString := c_cnstr;
11: SqlCommand1.Connection := SqlConnection1;
12: SqlCommand1.CommandText := c_sel;
13: SqlConnection1.Open;
14: try
15: SqlDataAdapter1.SelectCommand := SqlCommand1;
16: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
17: SqlDataAdapter1.TableMappings.Add('Table1', 'Employees');
18: SqlDataAdapter1.TableMappings.Add('Table2', 'Products');
19: SqlDataAdapter1.Fill(DataSet1);
20: DataGrid1.DataSource := DataSet1.Tables['Customers'];
21: finally
22: SqlConnection1.Close;
23: end;
24: end;

u Localize o código no CD: \Code\Chapter 21\Ex02\.

Basicamente, esse código configura um DataSet com os dados de três tabelas no ban-
co de dados Northwind. No projeto de exemplo, o WinForm contém um DataGrid para
506 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

exibir os dados e um Label para exibir o filtro. Vários botões permitem que o usuário sele-
cione um filtro diferente. Ao fazer isso, o filtro é aplicado e exibido no Label com o código
semelhante ao mostrado a seguir:

lblFilter.Text := System.String.Format('Table: {0}, Filter = {1}',


['Customers', DataSet1.Tables['Customers'].DefaultView.RowFilter]);

Filtrando por valores de coluna É possível realizar um filtro de coluna semelhan-


te àquele da cláusula WHERE em uma instrução SQL, como mostrado aqui:

DataSet1.Tables['Customers'].DefaultView.RowFilter := 'Country = ''Mexico'''+


' OR Country = ''Spain''';

Filtrando com operadores de comparação Vários operadores de filtro podem ser


utilizados contra o DataView. Alguns operadores comuns são apresentados na Tabela 21.4.

TABELA 21.4 Operadores de filtro


Operador Descrição
+ Realiza uma adição numérica ou uma concatenação de string.
- Realiza uma subtração numérica.
* Realiza uma multiplicação numérica.
/ Realiza uma divisão numérica.
% Retorna o módulo de uma operação de divisão.
AND Realiza lógica AND para combinar duas cláusulas de filtro. Exemplo:
Filter Country = 'USA' AND State = 'Colorado'
IN A forma abreviada do operador lógico OR. Exemplo:
Column IN ('Spain', 'France', 'China')
LIKE Realiza correspondência padrão.
NOT Inverte a lógica da expressão. Exemplo:
NOT (Country = 'Canada')
OR Operação lógica OR. Exemplo:
Country = 'USA' OR Country = 'Canada'

A Tabela 21.5 apresenta a lista completa de operadores de comparação.

TABELA 21.5 Utilizando operadores de comparação na filtragem de DataViews


Operador de comparação Descrição
= Realiza uma avaliação de igualdade.
< Realiza uma avaliação menor que.
> Realiza uma avaliação maior que.
< > Realiza uma avaliação de desigualdade.
<= Realiza uma avaliação menor que ou de igualdade.
>= Realiza uma avaliação maior que ou de igualdade.
Exibindo dados com DataView e DataViewManager 507

Um exemplo de filtragem de linha seria semelhante ao seguinte código:

DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country = ''Mexico'' OR Country = ''Spain''';

Esse exemplo ilustra o operador de igualdade bem como o operador OR para combi-
nar duas condições.
O seguinte código ilustra o uso do operador IN:

DataSet1.Tables['Customers'].DefaultView.RowFilter := 'Country IN '+


'(''Spain'', ''China'', ''France'', ''Mexico'')';

Para negar o filtro, você utilizaria o operador NOT como esse:

DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country NOT IN (''Spain'', ''China'', ''France'', ''Mexico'')';

Para filtrar registros que contêm um valor de coluna menor do que a quantidade em
outra coluna, utilize o operador menor que (<) como ilustrado a seguir:

DataSet1.Tables['Products'].DefaultView.RowFilter :=

'UnitsInStock < UnitsOnOrder';

O seguinte trecho de código ilustra como filtrar itens entre certos valores utilizando
os operadores maior que (>), menor que (<) e o lógico AND:

DataSet1.Tables['Products'].DefaultView.RowFilter :=
'UnitPrice > 50 AND UnitPrice < 100';

O seguinte código ilustra como utilizar os operadores matemáticos dentro da expres-


são de filtro:

DataSet1.Tables['Products'].DefaultView.RowFilter :=
'UnitsOnOrder * UnitPrice > 500';

Para eliminar um filtro, simplesmente atribua uma string vazia à propriedade RowFilter:

DataSet1.Tables['Customers'].DefaultView.RowFilter := '';

Filtrando com o operador padrão LIKE O filtro de correspondência padrão em


ADO.NET suporta a utilização dos caracteres (%) e (*) como um curinga para um ou mais
caracteres no começo ou final de um padrão de caracteres. Os dois próximos exemplos
de código ilustram como ele é utilizado. O código

DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'Country LIKE ''F%''';

filtra todos os nomes de países que iniciam com o caractere 'F'. Isso, por exemplo, retor-
naria os países França e Finlândia. O próximo exemplo pesquisa os caracteres especifica-
dos dentro de uma string:
508 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'ContactName LIKE ''%ta%''';

Ao contrário da palavra-chave “LIKE” na SQL, a palavra-chave LIKE de ADO.NET não


suporta a pesquisa de caractere ou intervalo com variável simples.

u Localize o código no CD: \Code\Chapter 21\Ex03\.

Funções na filtragem O ADO.NET também suporta certas funções SQL na expressão


de filtro. Por exemplo, para filtrar com base no comprimento de um valor de coluna,
você pode utilizar a função LEN( )como mostrado aqui:
DataSet1.Tables['Employees'].DefaultView.RowFilter := 'Len(PhotoPath) <= 35';

A função SubString( ) retorna a substring de uma string especificada, enquanto o se-


gundo parâmetro é um índice inicial baseado em 1 e o terceiro parâmetro é o compri-
mento da string a retornar. O seguinte código ilustra essa técnica:

DataSet1.Tables['Customers'].DefaultView.RowFilter :=
'SubString(Country, 1, 2) = ''fr''';

A função IFF( ) é um avaliador booleano que pode retornar um de dois resultados.


Isso é ilustrado no seguinte código:

DataSet1.Tables['Products'].DefaultView.RowFilter :=
'IIF(UnitPrice <= 50, UnitsInStock < 10, UnitsInStock >=10)';

O código anterior resultará nas linhas com menos de 10 unidades no estoque sendo
exibido se o UnitPrice for menor que ou igual a 50. Caso contrário, as linhas com 10 ou
mais UnitsInStock são retornadas.

u Veja o exemplo no CD: \Code\Chapter 21\Ex04\.

Filtragem baseada em DataViewRowState A propriedade DataView.RowStateFilter


pode armazenar um ou uma combinação dos valores do tipo enumerado apresentada na
Tabela 21.6. Esses valores permitem filtrar linhas com base em seu estado.

TABELA 21.6 Valores do tipo enumerado DataViewRowState


Valor Descrição
Added Exibe linhas adicionadas recentemente.
CurrentRows Exibe linhas atuais, inclusive aquelas que são inalteradas, novas e modificadas.
Deleted Exibe linhas excluídas.
ModifiedCurrent Uma linha modificada atualmente com a linha original existente no DataSet.
ModifiedOriginal A linha original desde que foi modificada e, portanto, está disponível como
ModifiedCurrent.
Nenhum Nenhum filtro DataViewRowState é aplicado.
OriginalRows Exibe linhas inalteradas e excluídas.
Unchanged Exibe linhas inalteradas.
Exibindo dados com DataView e DataViewManager 509

O seguinte código ilustra como você teria configurado um filtro na propriedade Da-
taView.RowStateFilter:

DataSet1.Tables['Customers'].DefaultView.RowStateFilter :=
DataSet1.Tables['Customers'].DefaultView.RowStateFilter or
DataViewRowState.Added;

u Veja o exemplo no CD: \Code\Chapter 21\Ex05\.

Note como o valor de tipo enumerado DataViewRowState.Added é logicamente combi-


nado, usando o operador or, com o valor atualmente atribuído a essa propriedade.
Para ilustrar ainda mais a maneira como você poderia utilizar o RowStateFilter, exa-
mine a Listagem 21.3, que mostra como você utilizaria um DataView para remover regis-
tros a partir de um DataTable.

LISTAGEM 21.3 Exemplo de RowStateFilter


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers';
4:
5: constructor TWinForm.Create;
6: begin
7: inherited Create;
8: InitializeComponent;
9: SqlConnection1.ConnectionString := c_cnstr;
10: SqlCommand1.Connection := SqlConnection1;
11: SqlCommand1.CommandText := c_sel;
12: SqlConnection1.Open;
13: try
14: SqlDataAdapter1.SelectCommand := SqlCommand1;
15:
16: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
17: SqlDataAdapter1.Fill(DataSet1);
18: DataGrid1.DataSource := DataSet1.Tables['Customers'].DefaultView;
19: finally
20: SqlConnection1.Close;
21: end;
22: end;
23:
24: procedure TWinForm.Button1_Click(sender: System.Object;
25: e: System.EventArgs);
26: var
27: dvEnum: IEnumerator;
28: drvCustomers: DataRowView;
29: begin
30: DataView1.Table := DataSet1.Tables['Customers'];
31: DataView1.RowFilter := 'Country = ''USA''';
32: // Também deve configurar RowStateFilter para visualizar linhas excluídas.
510 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

LISTAGEM 21.3 Continuação


33: DataView1.RowStateFilter := DataViewRowState.Deleted or
34: DataViewRowState.CurrentRows;
35: dvEnum := DataView1.GetEnumerator;
36:
37: while dvEnum.MoveNext do
38: begin
39: drvCustomers := dvEnum.Current as DataRowView;
40: drvCustomers.Delete;
41: end;
42: end;

u Veja o exemplo no CD: \Code\Chapter 21\Ex06\.

A Listagem 21.3 é uma listagem parcial da aplicação que demonstra essa técnica. A
configuração no construtor é semelhante àquela dos exemplos anteriores. Note na linha
18 que esse DataGrid1 é vinculado ao DataView padrão da tabela Customers no DataSet.
O handler de evento Button1_Click( ) obtém um DataView separado para tabela Custo-
mers do DataSet. Esse DataView não é vinculado a qualquer controle de interface com o
usuário como um DataGrid. Em vez disso, um filtro é aplicado para listar apenas aqueles
clientes no país, 'USA' (linha 31). O propósito desse handler de evento é utilizar esse Data-
View para remover seus registros do DataTable. Um DataGrid vinculado ao DataView padrão só
exibirá as linhas atuais ou aquelas linhas com o DataViewRowState de CurrentRows. Portanto,
quando o segundo DataView remover seus registros, o DataGrid1 não exibirá mais nenhum
registro cuja coluna Country contém 'USA'. Isso ilustra como as duas visualizações podem
ter visibilidade separada em um único DataTable, ainda que as modificações no DataTable
sejam realizadas pelos dois DataViews.

Suporte para funções agregadas A propriedade DataView.RowFilter também suporta


funções agregadas, permitindo filtrar linhas com base nos critérios de linhas filhas. Para
realizar isso, o DataSet dever ter um objeto DataRelation para definir o relacionamento
pai/filho. A Listagem 21.4 ilustra essa técnica.

LISTAGEM 21.4 Exemplo de função agregada


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Suppliers; SELECT * FROM Products;';
4:
5: constructor TWinForm.Create;
6: begin
7: inherited Create;
8: InitializeComponent;
9: SqlConnection1.ConnectionString := c_cnstr;
10: SqlCommand1.Connection := SqlConnection1;
11: SqlCommand1.CommandText := c_sel;
12: SqlConnection1.Open;
Exibindo dados com DataView e DataViewManager 511

LISTAGEM 21.4 Continuação


13: try
14: SqlDataAdapter1.SelectCommand := SqlCommand1;
15:
16: SqlDataAdapter1.TableMappings.Add('Table', 'Suppliers');
17: SqlDataAdapter1.TableMappings.Add('Table1', 'Products');
18: SqlDataAdapter1.Fill(DataSet1);
19:
20: DataSet1.Relations.Add('SupProd',
21: DataSet1.Tables['Suppliers'].Columns['SupplierID'],
22: DataSet1.Tables['Products'].Columns['SupplierID']);
23:
24: DataSet1.Tables['Suppliers'].DefaultView.RowFilter :=
25: 'Count(Child(SupProd).SupplierID) <= 10';
26:
27: DataGrid1.DataSource := DataSet1.Tables['Products'];
28: finally
29: SqlConnection1.Close;
30: end;
31: end;

u Veja o exemplo no CD: \Code\Chapter 21\Ex07\.

A Listagem 21.4 é código parcial do exemplo de CD. Esse exemplo recupera dados de
duas tabelas a partir do banco de dados Northwind. A tabela Suppliers é a tabela pai da ta-
bela Products. Nesse exemplo, utilizamos a função Count( ) para obter uma lista de forne-
cedores que tenha 10 ou menos estoques na tabela Products.
As linhas 20–22 criam e adicionam o DataRelation entre a tabela Suppliers e Products.
Esse DataRelation é chamado SupProd. As linhas 24–25 atribuem a expressão de filtro à pro-
priedade DataView.RowFilter.
Ao utilizar funções agregadas, a sintaxe dessas funções dentro da expressão de filtro
é Child(RelationName).ChildColumnName. Além disso, em filtros simples (sem nenhuma fun-
ção de agregação), as expressões do formulário Parent(RelationName).ParentColumnName po-
dem ser utilizadas para filtrar tabelas filhas com restrições específicas nos valores de colu-
na do pai.

Classificando DataView
Para classificar um DataView, simplesmente atribua o par ColumnName SortOrder à proprieda-
de DataView.Sort. Por exemplo, o seguinte código especifica para classificar a tabela Custo-
mers do banco de dados Northwind pelo ContactName em ordem crescente:

FdvContactName := DataView.Create(DataSet1.Tables['Customers']);
FdvContactName.Sort := 'ContactName ASC';

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex08\.

Além disso, múltiplas colunas são suportadas pela propriedade Sort.


512 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

Pesquisando DataView
Para pesquisar em um DataView, você deve primeiro estabelecer uma ordem de classifica-
ção. Você pode então utilizar o método DataView.Find( ) ou o DataView.FindRows( ) para
conduzir sua pesquisa.
A função Find( ) retorna um índice da linha, se localizado. Caso contrário, retorna
-1. FindRows( ) retorna um Array de objetos DataRowView ou um array vazio.
O seguinte código é uma listagem parcial do exemplo no CD, que utiliza a função
Find( ) para procurar o conteúdo de um controle TextBox:

var
RowIdx: Integer;
RowBinding: System.Windows.Forms.BindingContext;
begin
RowIdx := DataSet1.Tables['Customers'].DefaultView.Find(TextBox1.Text);
if RowIdx = -1 then
MessageBox.Show('Not found')
else begin
RowBinding := Self.BindingContext;
RowBinding[DataSet1.Tables['Customers']].Position := RowIdx;
end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex09\.

Nesse exemplo, se a linha for localizada, a linha atual do DataView é posicionada nessa
linha. Isso é feito pelo objeto BindingContext, do formulário, que é discutido mais adiante
neste capítulo.
O seguinte código ilustra como utilizar o método FindRows( ) no mesmo DataView:
var
drvArray: Array of DataRowView;
i: integer;
begin
drvArray := DataSet1.Tables['Customers'].DefaultView.FindRows(TextBox1.Text);
if High(drvArray) = 0 then
MessageBox.Show('Not found')
else
for i := Low(drvArray) to High(drvArray) do
MessageBox.Show(drvArray[i]['ContactName'].ToString);

Nesse exemplo, o método FindRows( ) retornará o array de objetos DataRowView. Se o ar-


ray não estiver vazio, o valor de coluna ContactName de cada linha de correspondência é
exibido.

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex09\.

Simulando um join
Utilizando a classe DataRelation, é possível simular um join SQL. A Listagem 21.5 ilustra
como isso é feito.
Exibindo dados com DataView e DataViewManager 513

LISTAGEM 21.5 Exemplo de join


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM EmployeeTerritories; SELECT * FROM Employees;';
4:
5: constructor TWinForm.Create;
6: begin
7: inherited Create;
8: InitializeComponent;
9: SqlConnection1.ConnectionString := c_cnstr;
10: SqlCommand1.Connection := SqlConnection1;
11: SqlCommand1.CommandText := c_sel;
12: SqlConnection1.Open;
13: try
14: SqlDataAdapter1.SelectCommand := SqlCommand1;
15:
16: SqlDataAdapter1.TableMappings.Add('Table', 'EmployeeTerritories');
17: SqlDataAdapter1.TableMappings.Add('Table1', 'Employees');
18: SqlDataAdapter1.Fill(DataSet1);
19:
20: DataSet1.Relations.Add('TerritoryEmp',
21: DataSet1.Tables['Employees'].Columns['EmployeeID'],
22: DataSet1.Tables['EmployeeTerritories'].Columns['EmployeeID']);
23:
24: DataSet1.Tables['EmployeeTerritories'].Columns.Add('EmployeeName',
25: TypeOf(System.String),
26: 'Parent(TerritoryEmp).FirstName + '' ''+
➥Parent(TerritoryEmp).LastName');
27:
28: DataGrid1.DataSource := DataSet1.Tables['EmployeeTerritories'];
29: finally
30: SqlConnection1.Close;
31: end;
32: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex10\.

Utilizando o mesmo banco de dados Northwind, recuperamos as tabelas Employees e


EmployeeTerritories no DataSet. Em seguida, estabelecemos um DataRelation com a tabela
Employees sendo o pai da tabela EmployeeTerritories vinculada pela coluna EmployeeID (li-
nhas 20–22). Esse DataRelation é chamado TerritoryEmp.
Por fim, para simular o JOIN, criamos uma coluna na tabela filha (EmployeeTerrito-
ries). Esse exemplo utiliza o método Add( ) sobrecarregado da propriedade DataTa-
ble.Columns, que aceita uma expressão como o último parâmetro. A sintaxe dessa expres-
são é semelhante à sintaxe de expressão agregada (Parent(RelationName).ParentColumnName).
Nesse exemplo, a expressão é uma concatenação das colunas FirstName e LastName na tabe-
la pai Employees.
514 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

Data Binding
A vinculação de dados tem a capacidade de sincronizar controles de interface com o
usuário com os dados subjacentes. A vinculação de dados não é apenas para classes rela-
cionadas com banco de dados. Os desenvolvedores podem vincular controles a qualquer
origem de dados se ela for um DataSet ou algum outro contêiner de dados como um
Collection ou um Array. Este capítulo focaliza a vinculação na WinForms, usando o Delp-
hi for .NET. WebForms são discutidos na Parte V.

As interfaces Data Binding


Nas versões anteriores do Delphi, a tecnologia equivalente de vinculação de dados foi
chamada de “data aware” (ciente dos dados), e sob esse esquema você tinha controles
data aware. Freqüentemente, os fornecedores de componente teriam duas versões de
seus controles – uma delas seria a versão data aware. Além disso, a habilidade de um con-
trole ser data aware foi vinculada, em sua maioria, apenas às classes relacionadas com
banco de dados.
Em .NET, a tecnologia data aware acontece como resultado da vinculação do contro-
le a uma origem de dados de modo desacoplado. Por exemplo, os controles data aware
do Delphi clássico tinham as propriedades DataSource e Fields que os conectavam a uma
origem de dados e ao campo específico dentro dessa origem de dados. Em .NET, os con-
troles são vinculados com origens de dados por classes que residem entre eles e estabele-
cem a conexão e sincronização de dados. Para um controle ser capaz de vincular-se, ele
deve implementar as interfaces que permitem essa capacidade. Essas interfaces são lista-
das na Tabela 21.7.

TABELA 21.7 Interfaces de vinculação de dados


Interface Descrição
Ilist A interface IList é implementada por classes como Array e Collection.
Fornece suporte para vincular a propriedade de um controle a uma lista de
dados homogêneos.
IbindingList Fornece suporte de notificação. As notificações ocorrem quando o conteúdo
de lista ou o valor do elemento de uma lista muda. Esta interface é
implementada por classes como a classe DataView e DataViewManager.
IEditableObject Fornece um controle de atualização simples do tipo transação para os dados
internos. As alterações dentro do controle vinculado podem ser revertidas
(rolled back) ou confirmadas (committed) para o objeto que representa os
dados.
IDataErrorInfo Fornece suporte para oferecer informações personalizadas de erro, às quais a
interface com o usuário pode ser vinculada.

Os WinForms também contêm seu próprio conjunto de classes especificamente pro-


jetado para trabalhar dentro desse esquema de vinculação. Os exemplos por todo este ca-
pítulo ilustram as classes de vinculação do WinForm.
Data Binding 515

Data Binding simples versus complexa


Há basicamente dois tipos de vinculação – simples e complexa.
Com a vinculação simples, um controle que contém um único valor, como o con-
trole TextBox, poder ser vinculado a um único valor a partir da origem de dados. Por
exemplo, você pode vincular a propriedade Text do controle TextBox a um valor de coluna
de um DataSet.
Com a vinculação complexa, o controle contém múltiplos valores. O DataGrid é o típi-
co controle que você verá em uso nesse esquema. Outros exemplos são ListBox e ComboBox.

Classes WinForm Data Binding


Várias classes interagem para fornecer essa capacidade de vinculação. Essas classes são re-
presentadas na Figura 21.2.
Cada WinForm (e o descendente Control) tem um objeto BindingContext. O objeto Bin-
dingContext gerencia uma coleção de objetos BindingManagerBase. A classe BindingManagerBase
(não mostrada na figura) é realmente uma classe abstrata. Ela tem dois descendentes, que
são o CurrencyManager e o PropertyManager. A Figura 21.2 mostra apenas o CurrencyManager
porque essa é a classe com a qual vamos lidar neste capítulo.
Existe um CurrencyManager por origem de dados em um Windows Form. O Currency-
Manager é o que gerencia a posição (linha atual) dentro da lista na origem de dados. Isso é
semelhante ao conceito curser com classes TDataSet do Delphi clássico. No ADO.NET, o
DataSet não mantém cursores, e, portanto, a posição atual da linha é tratada pelo Currency-
Manager.
Quando múltiplos controles são vinculados a uma única origem de dados, há um
CurrencyManager. Se alguns controles estiverem vinculados a uma origem de dados e outros
controles estiverem vinculados a uma origem de dados separada (como é o caso com
uma configuração de mestre/detalhe), há dois CurrencyManagers presentes.
Você utiliza o BindingContext para criar um novo CurrencyManager ou recuperar um dos
CurrencyManagers existentes.

Origem de dados 1
(Data Table 1)

Origem de dados 2
(Data Table 2)

BindingContext CurrencyManager

FIGURA 21.2 Vinculando classes.


516 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

NOTA
Em geral, você vai utilizar o BindingContext do Windows Form. Entretanto, cada controle que
pode ser um contêiner (descendente de Control) tem seu próprio objeto BindingContext. Portan-
to, é possível criar um Windows Form com objetos BindingContext específicos dos controles con-
têiner no formulário.

Criando Windows Forms de vinculação de dados


Os seguintes exemplos ilustram as várias técnicas que podem ser utilizadas com as capa-
cidades de vinculação de dados.

Vinculando listas
Certos controles de listas (descendentes da classe ListControl), como o ListBox e ComboBox,
têm a capacidade de se vincular a uma única coluna de um DataView e exibir a lista de valo-
res nessa coluna. Esses controles fornecem exibição de leitura desses valores. A Listagem
21.6 ilustra como você vincularia o conteúdo de uma coluna na tabela Customers a um
controle ListBox.

LISTAGEM 21.6 Vinculação de ListBox


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers';
4:
5: constructor TWinForm.Create;
6: begin
7: inherited Create;
8: InitializeComponent;
9: SqlConnection1.ConnectionString := c_cnstr;
10: SqlCommand1.Connection := SqlConnection1;
11: SqlCommand1.CommandText := c_sel;
12: SqlConnection1.Open;
13: try
14: SqlDataAdapter1.SelectCommand := SqlCommand1;
15:
16: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
17: SqlDataAdapter1.Fill(DataSet1);
18: ListBox1.DataSource := DataSet1.Tables['Customers'].DefaultView;
19: ListBox1.DisplayMember := 'ContactName';
20: ListBox1.ValueMember := 'CustomerID';
21:
22: DataGrid1.DataSource := DataSet1.Tables['Customers'].DefaultView;
23: finally
24: SqlConnection1.Close;
25: end;
26: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex11\.


Data Binding 517

As linhas 18–20 estão onde realmente ocorre a vinculação. Primeiro, atribui-se à pro-
priedade DataSource do ListBox um DataView – nesse caso, o DefaultView a partir da DataTable
Customers. A propriedade DisplayMember recebe então o nome da coluna que ela deve exi-
bir. ListControls também pode armazenar um valor adicional da origem de dados que pode
ser utilizado como o valor selecionado. Isso é estabelecido pela propriedade ValueMember. Na
Listagem 21.6, o ValueMember recebe uma coluna CustomerID. Se o controle que está sendo
utilizado fosse um ComboBox, ele poderia ser usado para estabelecer uma lista de pesquisa.

Vinculação simples de valor único


Os controles da interface com o usuário descendem da classe Control. Control contém
uma propriedade ControlBindingsCollection chamada DataBindings. A coleção DataBindings
contém uma lista de objetos Binding. A classe Binding é que mantém o link de propriedade
de um Control para um valor de uma origem de dados.
É possível vincular várias propriedades de um único Control a diferentes valores a
partir da origem de dados. Por exemplo, você pode vincular a propriedade Text de um
controle TextBox a um valor como um campo CustomerName de um DataView. Ao mesmo tem-
po, você pode vincular a propriedade Tag do mesmo TextBox ao campo CustomerID. Dado
esse cenário, a coleção TextBox.DataBindings conteria dois objetos Binding.
Para adicionar uma vinculação a um Control, você criaria uma instância da classe Bin-
ding e passaria essa para o método DataBindings.Add( ) do método Control, como mostrado
aqui:

bnding := Binding.Create('Text', DataSet1.Tables['Customers'].DefaultView,


'CompanyName');
txtbxCompanyName.DataBindings.Add(bnding);

O primeiro parâmetro do construtor Binding.Create( ) é o nome da propriedade em


que você quer que o valor apareça. O segundo parâmetro é a origem de dados e o tercei-
ro parâmetro é o nome do valor de origem de dados a ser atribuído ao valor de proprie-
dade. Alternativamente, você pode simplesmente passar os mesmos parâmetros para o
método DataBindings.Add( ) sobrecarregado, que internamente criará a instância de ob-
jeto Binding:

txtbxCompanyName.DataBindings.Add('Text',
DataSet1.Tables['Customers'].DefaultView, 'CompanyName');

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex12\.

Vinculando navegação e tratamento de evento


A seção anterior discute a vinculação de controle de valor único simples. Esta seção es-
tende esse exemplo e fornece a capacidade de navegar programaticamente pelo DataView
utilizando a classe CurrencyManager. Lembre-se de que o CurrencyManager, um descendente
BindingManagerBase, é o que gerencia a posição do DataView. Basicamente, essa é a posição de
cursor. A Listagem 21.7 demonstra como reposicionar programaticamente a linha atual
de um DataView. Ela também ilustra como adicionar um dos quatro handlers de evento do
objeto CurrencyManager.
518 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

LISTAGEM 21.7 Exemplo de navegação e tratamento de eventos


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers';
4:
5: constructor TWinForm.Create;
6: var
7: dv: DataView;
8: begin
9: inherited Create;
10: InitializeComponent;
11: SqlConnection1.ConnectionString := c_cnstr;
12: SqlCommand1.Connection := SqlConnection1;
13: SqlCommand1.CommandText := c_sel;
14: SqlConnection1.Open;
15: try
16: SqlDataAdapter1.SelectCommand := SqlCommand1;
17:
18: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
19: SqlDataAdapter1.Fill(DataSet1);
20: DataGrid1.DataSource := DataSet1.Tables['Customers'].DefaultView;
21: dv := DataSet1.Tables['Customers'].DefaultView;
22:
23: // Vincula os controles individuais
24: lblCustomerID.DataBindings.Add('Text', dv, 'CustomerID');
25: txtbxCompanyName.DataBindings.Add('Text', dv, 'CompanyName');
26: txtbxContactName.DataBindings.Add('Text', dv, 'ContactName');
27: txtbxContactTitle.DataBindings.Add('Text', dv, 'ContactTitle');
28: txtbxAddress.DataBindings.Add('Text', dv, 'Address');
29: txtbxCity.DataBindings.Add('Text', dv, 'City');
30: txtbxCountry.DataBindings.Add('Text', dv, 'Country');
31:
32: FCurrencyMgr := BindingContext[DataGrid1.DataSource] as
33: CurrencyManager;
34: Include(FCurrencyMgr.PositionChanged, FCurrency_PositionChanged);
35:
36: finally
37: SqlConnection1.Close;
38: end;
39: end;
40:
41: procedure TWinForm.btnPrior_Click(sender: System.Object;
42: e: System.EventArgs);
43: begin
44: FCurrencyMgr.Position := FCurrencyMgr.Position - 1;
45: end;
46:
47: procedure TWinForm.btnNext_Click(sender: System.Object;
48: e: System.EventArgs);
Data Binding 519

LISTAGEM 21.7 Continuação


49: begin
50: FCurrencyMgr.Position := FCurrencyMgr.Position + 1;
51: end;
52:
53: procedure TWinForm.btnLast_Click(sender: System.Object;
54: e: System.EventArgs);
55: begin
56: FCurrencyMgr.Position := FCurrencyMgr.Count - 1;
57: end;
58:
59: procedure TWinForm.btnFirst_Click(sender: System.Object;
60: e: System.EventArgs);
61: begin
62: FCurrencyMgr.Position := 0;
63: end;
64:
65: procedure TWinForm.Button1_Click(sender: System.Object;
66: e: System.EventArgs);
67: begin
68: Close;
69: end;
70:
71: procedure TWinForm.FCurrency_PositionChanged(sender: TObject;
72: e: System.EventArgs);
73: begin
74: btnFirst.Enabled := FCurrencyMgr.Position > 0;
75: btnPrior.Enabled := FCurrencyMgr.Position > 0;
76: btnLast.Enabled := FCurrencyMgr.Position < FCurrencyMgr.Count - 1;
77: btnNext.Enabled := FCurrencyMgr.Position < FCurrencyMgr.Count - 1;
78: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex13\.

A Figura 21.3 é o formulário dessa aplicação. Ele contém vários controles TextBox e al-
guns botões cujos eventos tratarão a navegação.
O construtor Create( ), semelhante a exemplos anteriores, configura a conexão e
preenche o DataSet. Além disso, ele estabelece a vinculação com os controles individuais.
O formulário define um campo, FCurrencyMgr, que é do tipo CurrencyManager. O CurrencyMa-
nager desse formulário é obtido a partir do BindingContext do formulário na linha 32. Note
como isso pode ser obtido a partir da propriedade DataSource de um dos controles. A linha
34 atribui um handler de evento ao evento FCurrencyMgr.PositionChanged.
Cada um dos handlers de evento de Button altera a propriedade FCurrencyMgr.Position
de maneira correspondente. Toda vez que isso ocorrer, o handler de evento PositionChan-
ged é invocado. O código nesse handler de evento (linhas 74–77) ativa/desativa os botões
com base no valor FCurrencyMgr.Position.
O CurrencyManager tem quatro eventos, todos listados na Tabela 21.8.
520 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

FIGURA 21.3 Formulário de exemplo de navegação.

TABELA 21.8 Eventos de CurrencyManager


Evento Descrição
CurrentChanged Invocado quando o valor de um controle vinculado foi alterado.
ItemChanged Invocado quando o item atual foi modificado.
MetaDataChanged Invocado quando os metadados foram modificados.
PositionChanged Invocado quando a posição do CurrencyManager é movida.

Modificando dados
Embora não seja típico, você pode modificar dados pelo CurrencyManager e então ter esses
dados confirmados para o DataSet associado. Normalmente, as modificações são tratadas
pelas classes DataTable ou DataRowView. Contudo, uma técnica que ilustra as modificações
tratadas pelo CurrencyManager é mostrada aqui. A Tabela 21.9 lista os vários métodos da
classe CurrencyManager.

TABELA 21.9 Métodos da CurrencyManager


Método Descrição
AddNew( ) Adiciona um item à lista interna.
CancelCurrentEdit( ) Cancela uma operação de edição e aborta quaisquer modificações.
EndCurrentEdit( ) Termina uma operação de edição e confirma modificações à lista
interna.
Refresh( ) Preenche novamente os controles vinculados.
RemoveAt( ) Remove o item do índice especificado de parâmetro.
ResumeBinding( ) Retoma a vinculação e a validação anteriormente suspensas a partir da
chamada SuspendBinding( ).
SuspendBinding( ) Suspende a vinculação e validação até ResumeBinding( ) reativá-las.
Data Binding 521

Ao modificar dados pela classe CurrencyManager, você deve certificar-se de que o usuá-
rio realiza os passos seqüenciais adequados. Por exemplo, uma vez que um usuário come-
ça a digitar dados em um controle, EndCurrentEdit( ) ou CancelCurrentEdit( ) deve ser cha-
mado para confirmar ou cancelar as alterações feitas. Isso não é tratado automaticamen-
te. Portanto, uma maneira de fazer isso é forçar explicitamente o usuário a colocar o for-
mulário no “modo de edição” antes que as alterações possam ser feitas dentro dos con-
troles. Da mesma forma, o usuário deve salvar ou cancelar essas alterações para que se-
jam confirmadas na lista interna. A Listagem 21.8 é código parcial do exemplo de CD
que ilustra essa técnica.

LISTAGEM 21.8 Exemplo de modificação


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers';
4:
5: constructor TWinForm.Create;
6: var
7: dv: DataView;
8: begin
9: inherited Create;
10: InitializeComponent;
11: SqlConnection1.ConnectionString := c_cnstr;
12: SqlCommand1.Connection := SqlConnection1;
13: SqlCommand1.CommandText := c_sel;
14: SqlConnection1.Open;
15: try
16: SqlDataAdapter1.SelectCommand := SqlCommand1;
17: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
18: SqlDataAdapter1.Fill(DataSet1);
19: DataGrid1.DataSource := DataSet1.Tables['Customers'].DefaultView;
20: dv := DataSet1.Tables['Customers'].DefaultView;
21:
22: // Vincula os controles individuais
23: lblCustomerID.DataBindings.Add('Text', dv, 'CustomerID');
24: txtbxCompanyName.DataBindings.Add('Text', dv, 'CompanyName');
25: txtbxContactName.DataBindings.Add('Text', dv, 'ContactName');
26: txtbxContactTitle.DataBindings.Add('Text', dv, 'ContactTitle');
27: txtbxAddress.DataBindings.Add('Text', dv, 'Address');
28: txtbxCity.DataBindings.Add('Text', dv, 'City');
29: txtbxCountry.DataBindings.Add('Text', dv, 'Country');
30:
31: FCurrencyMgr := BindingContext[DataGrid1.DataSource] as
32: CurrencyManager;
33: Include(FCurrencyMgr.PositionChanged, FCurrency_PositionChanged);
34: SetMode(dmBrowse);
35:
36: finally
522 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

LISTAGEM 21.8 Continuação


37: SqlConnection1.Close;
38: end;
39: end;
40:
41: procedure TWinForm.btnDelete_Click(sender: System.Object;
42: e: System.EventArgs);
43: begin
44: FCurrencyMgr.RemoveAt(FCurrencyMgr.Position);
45: end;
46:
47: procedure TWinForm.btnSave_Click(sender: System.Object;
48: e: System.EventArgs);
49: begin
50: if FDataMode = dmEdit then
51: FCurrencyMgr.EndCurrentEdit;
52:
53: SetMode(dmBrowse);
54: end;
55:
56: procedure TWinForm.btnEdit_Click(sender: System.Object;
57: e: System.EventArgs);
58: begin
59: SetMode(dmEdit);
60: end;
61:
62: procedure TWinForm.btnCancel_Click(sender: System.Object;
63: e: System.EventArgs);
64: begin
65: if FDataMode = dmAdd then
66: FCurrencyMgr.RemoveAt(FCurrencyMgr.Position)
67: else
68: FCurrencyMgr.CancelCurrentEdit;
69:
70: SetMode(dmBrowse);
71: end;
72:
73: procedure TWinForm.btnAdd_Click(sender: System.Object;
74: e: System.EventArgs);
75: begin
76: FCurrencyMgr.AddNew;
77: SetMode(dmAdd);
78: end;
79:
80: procedure TWinForm.SetMode(aDataMode: TDataMode);
81: var
82: i: integer;
83: begin
84: FDataMode := aDataMode;
Data Binding 523

LISTAGEM 21.8 Continuação


85: btnAdd.Enabled := aDataMode = dmBrowse;
86: btnEdit.Enabled := aDataMode = dmBrowse;
87: btnDelete.Enabled := aDataMode = dmBrowse;
88: btnCancel.Enabled := aDataMode < > dmBrowse;
89: btnSave.Enabled := aDataMode < > dmBrowse;
90:
91: for i := 0 to Controls.Count - 1 do
92: if Controls.Item[i] is System.Windows.Forms.TextBox then
93: (Controls[i] as TextBox).Readonly := aDataMode = dmBrowse;
94:
95: DataGrid1.ReadOnly := aDataMode = dmBrowse;
96: end;
97:
98: procedure TWinForm.FCurrency_PositionChanged(sender: TObject;
99: e: System.EventArgs);
100: begin
101: if (FDataMode = dmAdd) or (FDataMode = dmEdit) then
102: SetMode(dmBrowse);
103: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex14\.

A chave para esse exemplo é o método SetMode( ) (linhas 80–96). Esse método pri-
meiro ativa ou desativa os botões no formulário baseado no valor de um parâmetro TDa-
taMode. TDataMode é definido como

TDataMode = (dmBrowse, dmEdit, dmAdd);

Em seguida, SetMode( ) itera pelos vários controles TextBox e configura a propriedade Re-
adOnly desses controles como True se o formulário estiver no modo de navegação, indi-
cando que o usuário não configurou explicitamente o formulário no modo de adição ou
edição. Portanto, o usuário não será capaz de digitar nos controles até configurar explici-
tamente o modo apropriado.
Os outros handlers de evento simplesmente invocam os métodos respectivos do
membro CurrencyManager.
De fato, essa abordagem, embora utilizada em muitas aplicações, não é a mais efici-
ente para o usuário final. Alternativamente, você poderia fornecer uma maneira de con-
figurar o formulário automaticamente no modo de edição fornecendo um handler de
evento ao evento CurrentChanged. Esse evento poderia forçar o formulário a entrar em
modo de edição. Então, você só terá de assegurar que o usuário confirme ou aborte suas
modificações.

Formatando e analisando sintaticamente por meio da classe Binding


Lembre-se de que a classe Binding é aquela que armazena a vinculação entre propriedade
de um controle e um valor de origem de dados. Essa classe também contém dois eventos
– Format e Parse.
524 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

Considere que os dados na origem de dados podem não estar em um formato de exi-
bição mais amigável ao usuário final. Da mesma forma, a maneira como os usuários finais
inserem dados em um controle poderia ser formatada incorretamente para a origem de
dados. Esses dois eventos permitem converter os dados no formato requerido nas duas
extremidades da vinculação.

O evento Format oferece a oportunidade de formatar os dados da origem de dados antes de eles
serem exibidos no controle vinculado.

O evento Parse oferece a oportunidade de modificar os dados quando inseridos pelo usuário final
antes de serem armazenados na origem de dados.

A Listagem 21.9 ilustra essa técnica utilizada para um caso típico. Valores monetários
são armazenados na origem de dados com o tipo Decimal. Entretanto, a exibição e até a
entrada dos dados podem assumir a forma de uma string Currency.

LISTAGEM 21.9 Exemplo de Format e Parse


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT ProductName, UnitPrice FROM Products';
4:
5: constructor TWinForm.Create;
6: var
7: bndUnitPrice: Binding;
8: begin
9: inherited Create;
10: InitializeComponent;
11: SqlConnection1.ConnectionString := c_cnstr;
12: SqlCommand1.Connection := SqlConnection1;
13: SqlCommand1.CommandText := c_sel;
14: SqlConnection1.Open;
15: try
16: SqlDataAdapter1.SelectCommand := SqlCommand1;
17:
18: SqlDataAdapter1.TableMappings.Add('Table', 'Products');
19: SqlDataAdapter1.Fill(DataSet1);
20: DataGrid1.DataSource := DataSet1.Tables['Products'].DefaultView;
21:
22: // Cria o objeto Binding
23: bndUnitPrice := Binding.Create('Text',
24: DataSet1.Tables['Products'].DefaultView, 'UnitPrice');
25: // Atribui os dois handlers de evento.
26: Include(bndUnitPrice.Format, Self.DecimalToCurrencyString);
27: Include(bndUnitPrice.Parse, Self.CurrencyStringToDecimal);
28: // Adiciona Binding à propriedade DataBindings do controle TextBox
29: txbxUnitPrice.DataBindings.Add(bndUnitPrice);
30:
Data Binding 525

LISTAGEM 21.9 Continuação


31: finally
32: SqlConnection1.Close;
33: end;
34: end;
35:
36: procedure TWinForm.DecimalToCurrencyString(sender: TObject;
37: e: ConvertEventArgs);
38: begin
39: if e.Value = DBNull.Value then
40: e.Value := Decimal(0.00).ToString('c')
41: else
42: e.Value := Decimal(e.Value).ToString('c');
43: end;
44:
45: procedure TWinForm.CurrencyStringToDecimal(sender: TObject;
46: e: ConvertEventArgs);
47: begin
48: if e.DesiredType < > typeof(Decimal) then
49: Exit
50: else
51: e.Value := Decimal.Parse(e.Value.ToString,
52: System.Globalization.NumberStyles.Currency, nil)
53: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex15\.

Dois métodos para servir como os handlers de evento Format e Parse são definidos
em DecimalToCurrencyString( ) e CurrencyStringToDecimal( ), respectivamente. DecimalToCur-
rencyString( ) aceita o valor decimal da origem de dados e o converte em representação
de string de seu tipo Currency. O método CurrencyStringToDecimal( ) faz o inverso, utilizan-
do o tipo enumerado System.Globalization.NumberStyles para analisar sintaticamente a
string a fim de recuperar seu equivalente Decimal.

C A M P O S M O N E T Á R I O S E CO N F I G U R A Ç Õ E S L O C A I S P O R H A L L V A R D
Em relação ao projeto de banco de dados e campos monetários, com muita freqüência, a moeda
adotada dos valores é exatamente assim, adotada. Os bancos de dados de exemplo normalmente
assumem que as quantias são em dólares americanos, enquanto os programas de exemplo, que
formatam os valores utilizando um estilo de moeda, as mostrarão na moeda do local atual. Em
uma aplicação real poderia não ser uma má idéia especificar o tipo de moeda e ajustar sua exibição
de maneira correspondente.

As linhas 22–29 no construtor Create( ) tratam da criação do objeto Binding e da pro-


dução das atribuições de handler de evento.
526 Capítulo 21 Trabalhando com WinForms – Visualização e vinculação de dados

Estabelecendo uma vinculação mestre/detalhe


Utilizando o evento PositionChanged do CurrencyManager, você pode estabelecer uma confi-
guração mestre/detalhe. Isso é muito semelhante a adicionar um handler de evento ao
evento TDataSource.OnChange no Delphi clássico.
Quando o evento PositionChanged mestre é invocado, ele simplesmente utiliza o valor
de chave primária a partir do mestre para filtrar a tabela de detalhe baseado nessa chave
(que é um valor da chave estrangeira no detalhe). A Listagem 21.10 ilustra essa técnica.

LISTAGEM 21.10 Exemplo de mestre/detalhe


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'SELECT * FROM Customers; select * from Orders';
4:
5: constructor TWinForm.Create;
6: begin
7: inherited Create;
8: InitializeComponent;
9: SqlConnection1.ConnectionString := c_cnstr;
10: SqlCommand1.Connection := SqlConnection1;
11: SqlCommand1.CommandText := c_sel;
12: SqlConnection1.Open;
13: try
14: SqlDataAdapter1.SelectCommand := SqlCommand1;
15:
16: SqlDataAdapter1.TableMappings.Add('Table', 'Customers');
17: SqlDataAdapter1.TableMappings.Add('Table1', 'Orders');
18:
19: SqlDataAdapter1.Fill(DataSet1);
20: dgrdCustomers.DataSource := DataSet1.Tables['Customers'].DefaultView;
21: dgrdOrders.DataSource := DataSet1.Tables['Orders'].DefaultView;
22: Include(BindingContext[dgrdCustomers.DataSource].PositionChanged,
23: Currency_PositionChanged);
24: finally
25: SqlConnection1.Close;
26: end;
27: end;
28:
29: procedure TWinForm.Currency_PositionChanged(sender: TObject;
30: e: System.EventArgs);
31: var
32: MastRow: DataRowView;
33: begin
34: MastRow := BindingContext[dgrdCustomers.DataSource].Current as
35: DataRowView;
36: lblFilter.Text := System.String.Format('CustomerID = ''{0}''',
37: [MastRow['CustomerID'].ToString]);
38: DataSet1.Tables['Orders'].DefaultView.RowFilter := lblFilter.Text;
39: end;
Data Binding 527

u Veja o exemplo inteiro no CD: \Code\Chapter 21\Ex16\.

Neste exemplo, vamos utilizar novamente tabelas do banco de dados Northwind. A


tabela mestra é a tabela Customers e a tabela de detalhe é a tabela Orders. Quando o evento
PositionChanged (linha 29) for invocado, seu handler, Currency_PositionChanged, utiliza o
campo CustomerID do mestre para criar uma expressão de filtro que é atribuída à proprie-
dade RowFilter das tabelas de detalhe DefaultView (linhas 36–38). Esse é um meio simples
mas efetivo de exibir o relacionamento mestre/detalhe dentro de um Windows Form.
NESTE CAPÍTULO
— Atualizando a origem de CAPÍTULO 22
dados com
SQLCommandBuilder
— Atualizando a origem de
Salvando dados na
dados com a lógica de
atualização personalizada
origem de dados

A té agora, a discussão sobre ADO.NET esteve restrita a


trabalhar com dados separadamente da origem de dados.
Isso é verdadeiro pelo menos quanto à persistência de
dados. Este capítulo discute como salvar alterações de
dados na origem de dados. Além disso, discute como
agrupar atualizações relacionadas em uma única
operação chamada transação a fim de assegurar que o
agrupamento seja bem-sucedido ou falhe coletivamente.
No capítulo, discuto as classes SQLDataAdapter,
SQLCommandBuilder e SQLCommand. Entretanto, o conteúdo
aqui também se aplica, na maioria, às classes
equivalentes em outros data providers.
Do ponto de vista de utilização de classes ADO.NET,
há basicamente duas maneiras de atualizar uma origem
de dados com suas alterações. Uma maneira envolve
utilizar uma classe SQLCommandBuilder. Esse método é muito
fácil do ponto de vista da codificação, mas há limitações.
Uma técnica mais poderosa é utilizar lógica
personalizada, que oferece maior controle sobre o
processo de atualização. Ambas as técnicas são ilustradas
neste capítulo.

Atualizando a origem de dados


com SQLCommandBuilder
SQLDataAdapter não cria automaticamente as instruções
SQL para reconciliar alterações de volta à origem de
dados. A classe SQLCommandBuilder realizará essa tarefa para
você. SQLCommandBuilder é fácil de utilizar e requer pouca
codificação.
A classe SQLCommandBuilder utiliza a propriedade
SelectCommand de SQLDataAdapter para recuperar metadados
sobre o DataSet. A partir dessas informações,
SQLCommandBuilder constrói as classes apropriadas
Atualizando a origem de dados com SQLCommandBuilder 529

InsertCommand, UpdateCommand e DeleteCommand. Essas classes contêm as instruções SQL apro-


priadas necessárias para realizar suas funções na origem de dados. Entretanto, existem li-
mitações com SQLCommandBuilder, que são listadas aqui:
— SQLCommandBuilder funciona apenas com uma única tabela.

— SQLCommandBuilder requer uma coluna de chave primária seja incluída no re-


sultset originado.
— SQLCommandBuilder não fornece um ótimo desempenho em tempo de execu-
ção porque ele deve recuperar e processar metadados antes ser capaz de realizar
atualizações.
— Você não pode controlar a lógica de atualização modificando a SQL que SQL-
CommandBuilder utiliza para realizar atualizações.

— Você não pode utilizar SQLCommandBuilder com stored procedures.

— Você não pode especificar um tipo de concorrência otimista utilizando SQL-


CommandBuilder.

Para atualizações simples que não requerem personalização e em que um ótimo de-
sempenho não é uma questão, SQLCommandBuilder é uma boa opção.
Para ilustrar como o SQLCommandBuilder funciona, considere o seguinte código que as-
socia um objeto SQLCommandBuilder com um objeto SqlDataAdapater.

sqlCn := SqlConnection.Create(c_cnstr);
sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
sqlCB := SqlCommandBuilder.Create(sqlDA);

A classe SqlCommandBuilder registra a si próprio como um handler do SqlDataAdap-


ter.RowUpdating evento. SqlCommandBuilder constrói as instruções SQL INSERT, UPDATE e DELETE
utilizando a propriedade SelectCommand de SqlDataAdapter para recuperar metadados sobre a
tabela com a qual ela está funcionando.

ATENÇÃO
Se fizer qualquer alteração em SelectCommand ou nas propriedades Connection do DataAdapter
com as quais SqlCommandBuilder está associado, você deve chamar o método SqlCommandBuil-
der.RefreshSchema( ) para que SqlCommandBuilder possa reconstruir seus comandos.

Quando o método Update( )de uma classe SqlDataAdapter for invocado, SqlCommand-
Builder gera as várias instruções SQL necessárias para atualizar a origem de dados. Dada a
propriedade SelectCommand de

select ProductID, ProductName, UnitPrice, ReorderLevel from Products;

o comando InsertCommand gerado por SqlCommandBuilder seria

INSERT INTO products(ProductName, UnitPrice, ReorderLevel)


VALUES (@p1, @p2, @p3)
530 Capítulo 22 Salvando dados na origem de dados

Você verá que a instrução é baseada em um comando parametrizado. As instruções


UpdateCommand e DeleteCommand são construídas de maneira semelhante.
A Listagem 22.1 é um exemplo que demonstra a atualização de uma linha para o
banco de dados Northwind utilizando a classe SqlCommandBuilder. Observe que algumas li-
nhas foram removidas do exemplo real nessa Listagem.

LISTAGEM 22.1 Salvando atualizações com SqlCommandBuilder


1: program CmdBldr;
2:
3: {$APPTYPE CONSOLE}
4: {%DelphiDotNetAssemblyCompiler 'c:\windows\microsoft.net\
➥framework\v1.1.4322\System.Data.dll'}
5:
6: uses
7: System.Data,
8: System.Data.SqlClient;
9: var
10: sqlCn: SqlConnection;
11: sqlDA: SqlDataAdapter;
12: sqlCB: SqlCommandBuilder;
13: dsNorthWind: DataSet;
14: tblProduct: DataTable;
15:
16: const
17: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
18: c_sel_cst = 'SELECT * FROM Products';
19:
20: begin
21: sqlCn := SqlConnection.Create(c_cnstr);
22: sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
23: sqlCB := SqlCommandBuilder.Create(sqlDA);
24: dsNorthWind := DataSet.Create('NorthWind');
25: sqlDA.TableMappings.Add('Table', 'Products');
26: sqlDA.Fill(dsNorthWind);
27:
28: tblProduct := dsNorthWind.Tables['Products'];
29:
30: tblProduct.Rows[0]['ReorderLevel'] := TObject(10);
31: sqlDA.Update(dsNorthWind);
32:
33: Console.WriteLine(sqlCB.GetUpdateCommand.CommandText);
34: Console.ReadLine;
35: end.

u Localize o código no CD: \Code\Chapter 22\Ex01\.


Atualizando a origem de dados com a lógica de atualização personalizada 531

A maior parte do código nesta Listagem é semelhante ao dos exemplos anteriores que
você viu. A primeira parte dessa Listagem basicamente configura a conexão e recupera os
dados em um DataSet. A linha 30 altera o valor da coluna ReorderLevel e então salva essa al-
teração na origem de dados na linha 31.
Embora esse seja um exemplo muito simples, a maior parte das atualizações que
você realiza utilizando a classe SqlCommandBuilder aparecerá ao longo dessas linhas.
Você deve entender outra particularidade sobre SqlCommandBuilder. Ainda que você
forneça uma chave primária, SqlCommandBuilder, ao realizar uma operação UPDATE ou DELETE,
pesquise registros contendo uma correspondência exata de todas as colunas no registro
ou pelo menos todas as colunas que tiverem sido recuperadas na origem de dados, por-
que é possível recuperar um subconjunto de colunas. SqlCommandBuilder utiliza tanto os
valores atuais como os originais da linha ao realizar a atualização. Então, a SQL gerada
exige que um registro de correspondência seja localizado em todos os valores originais
de coluna; assim, o registro é atualizado utilizando os valores atuais de coluna. Ela faz
isso para impedir que a atualização de outro usuário seja sobrescrita. Quando você consi-
dera isso, você pode ver como a utilização de SqlCommandBuilder pode adicionar overhead
significativo. Em muitos casos, isso é bom. Entretanto, quando você exigir um desempe-
nho melhor, terá de fornecer sua própria lógica personalizada.

Atualizando a origem de dados com a lógica


de atualização personalizada
Utilizar a lógica de atualização personalizada significa que você simplesmente fornece
suas próprias instruções SQL INSERT, UPDATE ou DELETE em oposição a deixar SqlCommandBuil-
der fazer isso para você. Utilizando a lógica personalizada, você pode escrever instruções
SQL para abordar cenários em que SqlCommandBuilder é limitado. Por exemplo, você pode
escrever a lógica que atualiza a origem de dados por meio de uma stored procedure. Você
também pode escrever a lógica que atualiza a origem de dados em que a consulta original
era formada por um join entre tabelas. Nesse cenário, você só quer atualizar um subcon-
junto das colunas. Utilizando SqlCommandBuilder, você não tem nenhuma maneira de espe-
cificar isso. Por fim, utilizando lógica personalizada, você pode tratar a concorrência de
modo mais otimizado do que o SqlCommandBuilder.
A classe SqlDataAdapter tem a capacidade de enviar alterações pendentes para sua ori-
gem de dados baseada em instruções SQL que ela contém. Esta seção explica como confi-
gurar manualmente as propriedades InsertCommand, UpdateCommand e DeleteCommand do SqlDa-
taAdapter. Inicialmente, para entender como SqlDataAdapter funciona com essas várias
classes Command, vejamos como as atualizações ocorreriam utilizando somente uma classe
Command.

Utilizando uma classe Command


Uma maneira de poder atualizar a origem de dados e ter controle completo sobre a lógica
de atualização seria simplesmente utilizar a classe SqlCommand (ou uma equivalente de ou-
tro data provider). Para ilustrar essa técnica, considere a Listagem 22.2, que é um trecho
do exemplo completo localizado no CD que acompanha este livro.
532 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.2 Trecho da classe de Command


1: var
2: sqlCn: SqlConnection;
3: sqlDA: SqlDataAdapter;
4: dsNorthWind: DataSet;
5: tblProduct: DataTable;
6: dr: DataRow;
7: const
8: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
9: c_sel_cst = 'SELECT * FROM Products';
10: ...
11: begin
12: sqlCn := SqlConnection.Create(c_cnstr);
13: sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
14: dsNorthWind := DataSet.Create('NorthWind');
15: sqlDA.TableMappings.Add('Table', 'Products');
16: sqlDA.Fill(dsNorthWind);
17: tblProduct := dsNorthWind.Tables['Products'];
18:
19: // Insere uma Linha
20: dr := tblProduct.NewRow;
21: dr['ProductName'] := 'Second Burn Hot Sauce';
22: dr['SupplierID'] := System.Object(2);
23: dr['CategoryID'] := System.Object(2);
24: dr['QuantityPerUnit'] := '48 - 6 oz jars';
25: dr['UnitPrice'] := System.Object(23.00);
26: dr['UnitsInStock'] := System.Object(50);
27: dr['UnitsOnOrder'] := System.Object(0);
28: dr['ReorderLevel'] := System.Object(15);
29: dr['Discontinued'] := System.Object(0);
30: tblProduct.Rows.Add(dr);
31:
32: //Modifica uma Linha
33: dr := tblProduct.Rows[5];
34: dr['UnitsInStock'] := System.Object(10);
35:
36: tblPrduct.Rows[0].Delete;
37: SubmitUpdates;
38: end.

u Localize o código no CD: \Code\Chapter 22\Ex02\.

Neste exemplo, você vê que eu configuro um DataSet que contém uma única tabela
do banco de dados Northwind. Especificamente, essa é a tabela Products. As linhas
20–30 adicionam uma nova linha à tabela e as linhas 33–34 atualizam uma linha exis-
tente. A linha 36 exclui a primeira linha da tabela. A linha 37 chama uma procedure
SubmitUpdates( ), que é listada na Listagem 22.3.
Atualizando a origem de dados com a lógica de atualização personalizada 533

LISTAGEM 22.3 Procedure SubmitUpdates( )


1: procedure SubmitUpdates;
2: var
3: rowEnum: IEnumerator;
4: CurrRow: DataRow;
5: begin
6: sqlCn.Open;
7: try
8: rowEnum := tblProduct.Rows.GetEnumerator;
9: while rowEnum.MoveNext do
10: begin
11: CurrRow := (rowEnum.Current as DataRow);
12: case CurrRow.RowState of
13: DataRowState.Modified: SubmitModifiedRow(CurrRow);
14: DataRowState.Added: SubmitAddedRow(CurrRow);
15: DataRowState.Deleted: SubmitDeletedRow(CurrRow);
16: end; // case
17: end;
18: finally
19: sqlCn.Close;
20: end;
21: end;

u Localize o código no CD: \Code\Chapter 22\Ex02\.

Essa procedure é relativamente simples e direta. Ela assegura que a conexão com o
banco de dados está aberta e então itera a cada linha na tabela verificando o tipo de atua-
lização que foi realizada nessa linha. Quando uma linha localizada tiver sido modificada,
adicionada ou excluída da maneira indicada por sua propriedade RowState (linha 12), a
procedure respectiva é invocada para tratar essa atualização para o banco de dados (li-
nhas 13–15). Cada uma das três procedures contém a própria lógica de atualização. Essas
três procedures são discutidas individualmente com mais detalhes em breve.
A Listagem 22.4 é a instrução Transact-SQL INSERT que é utilizada na procedure
SubmitAddedRow( ). Observe que essa Listagem não contém números da linha para evitar
confusão.

LISTAGEM 22.4 Instrução Transact-SQL INSERT


INSERT INTO Products (
ProductName,
SupplierID,
CategoryID,
QuantityPerUnit,
UnitPrice,
UnitsInStock,
UnitsOnOrder,
ReorderLevel,
Discontinued)
534 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.4 Continuação


VALUES (
@ProductName,
@SupplierID,
@CategoryID,
@QuantityPerUnit,
@UnitPrice,
@UnitsInStock,
@UnitsOnOrder,
@ReorderLevel,
@Discontinued);

u Localize o código no CD: \Code\Chapter 22\Ex02\.

A Listagem 22.4 é realmente o conteúdo de um arquivo c_ins.sql que é utilizado no


método SubmitAddedRow( ). SubmitAddedRow( ) é mostrado na Listagem 22.5.

LISTAGEM 22.5 INSERT personalizada utilizando a classe SqlCommand


1: procedure SubmitAddedRow(aRow: DataRow);
2: var
3: sqlCmd: SqlCommand;
4: begin
5: // Cria o Command
6: sqlCmd := SqlCommand.Create(GetCommand('c_ins.sql'), sqlCn);
7: sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40);
8: sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int);
9: SqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int);
10: SqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20);
11: SqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money);
12: SqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt);
13: SqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt);
14: SqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt);
15: SqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit);
16:
17: // Adiciona Values a Command
18:
19: sqlCmd.Parameters['@ProductName'].Value := aRow['ProductName'];
20: sqlCmd.Parameters['@SupplierID'].Value := aRow['SupplierID'];
21: sqlCmd.Parameters['@CategoryID'].Value := aRow['CategoryID'];
22: sqlCmd.Parameters['@QuantityPerUnit'].Value := aRow['QuantityPerUnit'];
23: sqlCmd.Parameters['@UnitPrice'].Value := aRow['UnitPrice'];
24: sqlCmd.Parameters['@UnitsInStock'].Value := aRow['UnitsInStock'];
25: sqlCmd.Parameters['@UnitsOnOrder'].Value := aRow['UnitsOnOrder'];
26: sqlCmd.Parameters['@ReorderLevel'].Value := aRow['ReorderLevel'];
27: sqlCmd.Parameters['@Discontinued'].Value := aRow['Discontinued'];
28:
29: sqlCmd.ExecuteNonQuery;
30: end;
Atualizando a origem de dados com a lógica de atualização personalizada 535

u Localize o código no CD: \Code\Chapter 22\Ex02\.

A linha 6 na Listagem 22.5 cria a instância SqlCommand. O primeiro parâmetro é uma


chamada para uma função, GetCommand( ), que simplesmente carrega o arquivo especifica-
do e retorna seu conteúdo – nesse caso, o conteúdo que é mostrado na Listagem 22.4. As
linhas 7–15 criam os objetos de parâmetro para o objeto SqlCommand. As linhas 19–27 atri-
buem valores do DataRow passado para essa procedure para esses parâmetros. Observe que
os valores atribuídos a cada parâmetro são DataRowVersion padrão de DataRowVersion.Defa-
ult, que é o caso quando o DataRowVersion não é especificado no indexador. Lembre-se do
Capítulo 20 de que cada DataRow contém uma versão dos dados que ele contém. Os Data-
RowVersions de que trataremos aqui são Original e Current. Quando a linha 29 for executa-
da, o conteúdo desse DataRow é adicionado à origem de dados associada.
Você pode fornecer sua própria instrução SQL INSERT, utilizando uma classe SqlCom-
mand para realizar a persistência à origem de dados associada.
Essa operação INSERT é relativamente simples. A Listagem 22.6 mostra a operação
UPDATE, que é ligeiramente mais complexa.

LISTAGEM 22.6 UPDATE personalizada utilizando a classe SqlCommand


1: procedure SubmitModifiedRow(aRow: DataRow);
2: var
3: sqlCmd: SqlCommand;
4: begin
5: sqlCmd := sqlCommand.Create(GetCommand('c_upd.sql'), sqlCn);
6: sqlCmd.Parameters.Add('@C_ProductName', SqlDbType.NVarChar, 40);
7: sqlCmd.Parameters.Add('@C_SupplierID', SqlDbType.Int);
8: sqlCmd.Parameters.Add('@C_CategoryID', SqlDbType.Int);
9: sqlCmd.Parameters.Add('@C_QuantityPerUnit', SqlDbType.NVarChar, 20);
10: sqlCmd.Parameters.Add('@C_UnitPrice', SqlDbType.Money);
11: sqlCmd.Parameters.Add('@C_UnitsInStock', SqlDbType.SmallInt);
12: sqlCmd.Parameters.Add('@C_UnitsOnOrder', SqlDbType.SmallInt);
13: sqlCmd.Parameters.Add('@C_ReorderLevel', SqlDbType.SmallInt);
14: sqlCmd.Parameters.Add('@C_Discontinued', SqlDbType.Bit);
15:
16: sqlCmd.Parameters.Add('@O_ProductID', SqlDbType.Int);
17: sqlCmd.Parameters.Add('@O_ProductName', SqlDbType.NVarChar, 40);
18: sqlCmd.Parameters.Add('@O_SupplierID', SqlDbType.Int);
19: sqlCmd.Parameters.Add('@O_CategoryID', SqlDbType.Int);
20: sqlCmd.Parameters.Add('@O_QuantityPerUnit', SqlDbType.NVarChar, 20);
21: sqlCmd.Parameters.Add('@O_UnitPrice', SqlDbType.Money);
22: sqlCmd.Parameters.Add('@O_UnitsInStock', SqlDbType.SmallInt);
23: sqlCmd.Parameters.Add('@O_UnitsOnOrder', SqlDbType.SmallInt);
24: sqlCmd.Parameters.Add('@O_ReorderLevel', SqlDbType.SmallInt);
25: sqlCmd.Parameters.Add('@O_Discontinued', SqlDbType.Bit);
26:
27: // Adiciona Values a Command
28:
29: sqlCmd.Parameters['@C_ProductName'].Value := aRow['ProductName'];
536 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.6 Continuação


30: sqlCmd.Parameters['@C_SupplierID'].Value := aRow['SupplierID'];
31: sqlCmd.Parameters['@C_CategoryID'].Value := aRow['CategoryID'];
32: sqlCmd.Parameters['@C_QuantityPerUnit'].Value := aRow['QuantityPerUnit'];
33: sqlCmd.Parameters['@C_UnitPrice'].Value := aRow['UnitPrice'];
34: sqlCmd.Parameters['@C_UnitsInStock'].Value := aRow['UnitsInStock'];
35: sqlCmd.Parameters['@C_UnitsOnOrder'].Value := aRow['UnitsOnOrder'];
36: sqlCmd.Parameters['@C_ReorderLevel'].Value := aRow['ReorderLevel'];
37: sqlCmd.Parameters['@C_Discontinued'].Value := aRow['Discontinued'];
38:
39: sqlCmd.Parameters['@O_ProductID'].Value :=
40: aRow['ProductID', DataRowVersion.Original];
41: sqlCmd.Parameters['@O_ProductName'].Value :=
42: aRow['ProductName', DataRowVersion.Original];
43: sqlCmd.Parameters['@O_SupplierID'].Value :=
44: aRow['SupplierID', DataRowVersion.Original];
45: sqlCmd.Parameters['@O_CategoryID'].Value :=
46: aRow['CategoryID', DataRowVersion.Original];
47: sqlCmd.Parameters['@O_QuantityPerUnit'].Value :=
48: aRow['QuantityPerUnit', DataRowVersion.Original];
49: sqlCmd.Parameters['@O_UnitPrice'].Value :=
50: aRow['UnitPrice', DataRowVersion.Original];
51: sqlCmd.Parameters['@O_UnitsInStock'].Value :=
52: aRow['UnitsInStock', DataRowVersion.Original];
53: sqlCmd.Parameters['@O_UnitsOnOrder'].Value :=
54: aRow['UnitsOnOrder', DataRowVersion.Original];
55: sqlCmd.Parameters['@O_ReorderLevel'].Value :=
56: aRow['ReorderLevel', DataRowVersion.Original];
57: sqlCmd.Parameters['@O_Discontinued'].Value :=
58: aRow['Discontinued', DataRowVersion.Original];
59:
60: sqlCmd.ExecuteNonQuery;
61: end;

u Localize o código no CD: \Code\Chapter 22\Ex02\.

Diferentemente da Listagem 22.5, a Listagem 22.6 utiliza DataRowVersions tanto Origi-


nal como Current (linhas 39–58). Para ver por que, considere a instrução Transact-SQL
que é utilizada (ver Listagem 22.7). Essa instrução é carregada na linha 5 na Listagem
27.6 pelo método GetCommand( ).

LISTAGEM 22.7 Instrução Transact-SQL UPDATE


UPDATE Products
SET
ProductName = @C_ProductName,
SupplierID = @C_SupplierID,
CategoryID = @C_CategoryID,
Atualizando a origem de dados com a lógica de atualização personalizada 537

LISTAGEM 22.7 Continuação


QuantityPerUnit = @C_QuantityPerUnit,
UnitPrice = @C_UnitPrice,
UnitsInStock = @C_UnitsInStock,
UnitsOnOrder = @C_UnitsOnOrder,
ReorderLevel = @C_ReorderLevel,
Discontinued = @C_Discontinued
WHERE
ProductID = @O_ProductID AND
ProductName = @O_ProductName AND
SupplierID = @O_SupplierID AND
CategoryID = @O_CategoryID AND
QuantityPerUnit = @O_QuantityPerUnit AND
UnitPrice = @O_UnitPrice AND
UnitsInStock = @O_UnitsInStock AND
UnitsOnOrder = @O_UnitsOnOrder AND
ReorderLevel = @O_ReorderLevel AND
Discontinued = @O_Discontinued;

u Localize o código no CD: \Code\Chapter 22\Ex02\.

A instrução SQL UPDATE contém tanto a cláusula SET como a WHERE. A parte SET determi-
na quais valores atribuir às colunas especificadas. A parte WHERE especifica os critérios de
pesquisa que a origem de dados utiliza para localizar o registro específico a atualizar. A
cláusula SET atribui todos os valores novos ou DataRowVersion.Current. A cláusula WHERE pes-
quisa os registros utilizando os valores antigos ou DataRowVersion.Original.
Portanto, nas linhas 29–37 da Listagem 22.6, quando os valores são atribuídos aos
parâmetros utilizados na cláusula SET, DataRowVersion não precisa ser especificado no inde-
xador porque o padrão é o valor DataRowVersion.Current. Entretanto, nas linhas 39–58 da
Listagem 22.6, DataRowVersion.Original deve ser especificado para os valores sendo atribuí-
dos aos parâmetros na cláusula WHERE.
Vale a pena mencionar um aspecto sobre esse código. Você notará que a cláusula
WHERE utiliza todos os campos na tabela. Isso é certamente uma maneira de tratar a con-
corrência. Se os valores em qualquer uma das linhas alterarem, a atualização falhará por-
que o registro não será localizado. Essa não é a maneira mais eficiente de tratar a concor-
rência. Opções diferentes de tratamento de concorrência são discutidas mais adiante
neste capítulo. A Listagem 22.8 mostra a instrução Transact-SQL utilizada para excluir
um registro.

LISTAGEM 22.8 Instrução Transact-SQL DELETE


DELETE FROM Products
WHERE
ProductID = @ProductID AND
ProductName = @ProductName AND
SupplierID = @SupplierID AND
CategoryID = @CategoryID AND
538 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.8 Continuação


QuantityPerUnit = @QuantityPerUnit AND
UnitPrice = @UnitPrice AND
UnitsInStock = @UnitsInStock AND
UnitsOnOrder = @UnitsOnOrder AND
ReorderLevel = @Discontinued;

u Localize o código no CD: \Code\Chapter 22\Ex02\.

Essa é a mais simples das três instruções Transact-SQL. A Listagem 22.9 mostra o mé-
todo SubmitDeletedRow( ) que utiliza a instrução Transact-SQL DELETE.

LISTAGEM 22.9 DELETE personalizada utilizando a classe SqlCommand


1: procedure SubmitDeletedRow(aRow: DataRow);
2: var
3: sqlCmd: SqlCommand;
4: begin
5: // Cria o Command
6: sqlCmd := SqlCommand.Create(GetCommand('c_del.sql'), sqlCn);
7: sqlCmd.Parameters.Add('@ProductID', SqlDbType.Int);
8: sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40);
9: sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int);
10: SqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int);
11: SqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20);
12: SqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money);
13: SqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt);
14: SqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt);
15: SqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt);
16: SqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit);
17:
18: // Adiciona Values a Command
19:
20: sqlCmd.Parameters['@ProductID'].Value :=
21: aRow['ProductID', DataRowVersion.Original];
22: sqlCmd.Parameters['@ProductName'].Value :=
23: aRow['ProductName', DataRowVersion.Original];
24: sqlCmd.Parameters['@SupplierID'].Value :=
25: aRow['SupplierID', DataRowVersion.Original];
26: sqlCmd.Parameters['@CategoryID'].Value :=
27: aRow['CategoryID', DataRowVersion.Original];
28: sqlCmd.Parameters['@QuantityPerUnit'].Value :=
29: aRow['QuantityPerUnit', DataRowVersion.Original];
30: sqlCmd.Parameters['@UnitPrice'].Value :=
31: aRow['UnitPrice', DataRowVersion.Original];
32: sqlCmd.Parameters['@UnitsInStock'].Value :=
33: aRow['UnitsInStock', DataRowVersion.Original];
34: sqlCmd.Parameters['@UnitsOnOrder'].Value :=
Atualizando a origem de dados com a lógica de atualização personalizada 539

LISTAGEM 22.9 Continuação


35: aRow['UnitsOnOrder', DataRowVersion.Original];
36: sqlCmd.Parameters['@ReorderLevel'].Value :=
37: aRow['ReorderLevel', DataRowVersion.Original];
38: sqlCmd.Parameters['@Discontinued'].Value :=
39: aRow['Discontinued', DataRowVersion.Original];
40:
41: sqlCmd.ExecuteNonQuery;
42: end;

u Localize o código no CD: \Code\Chapter 22\Ex02\.

A Listagem 22.9 utiliza somente os valores DataRowVersion.Original nas linhas 20–39


porque ela só está passando os critérios de pesquisa para a origem de dados especificar o
registro a ser excluído.
Em qualquer uma dessas três operações, INSERT, UPDATE, DELETE, você pode personalizar
as instruções Transact-SQL para tratar suas necessidades especializadas. Por exemplo,
você pode ter critérios de pesquisa especializados ou tratamento de concorrência mais
eficiente. A idéia é que você possa escrever código personalizado, que não poderia escre-
ver utilizando a classe SqlCommandBuilder. A próxima seção ilustra como fazer o mesmo uti-
lizando as propriedades da classe SqlDataAdapter.

Utilizando a classe SqlDataAdapter


A classe SqlDataAdapter contém três propriedades SqlCommand, além da propriedade Select-
Command – InsertCommand, UpdateCommand e DeleteCommand.
Quando você invocar o método Update( ) do SqlDataAdapter para salvar as alterações
na origem de dados, o SqlDataAdapter determina qual das três propriedades SqlCommand ela
utilizará para atualizar a origem de dados. Para ilustrar como o SqlDataAdapter funciona,
utilizarei as mesmas instruções Transact-SQL que foram utilizadas para demonstrar a uti-
lização das classes SqlCommand para atualizar a origem de dados (consulte as listagens 22.4,
22.7 e 22.8).
A Listagem 22.10 mostra um fragmento do bloco principal de uma aplicação que
utiliza SqlDataAdapter a fim de fazer atualizações na origem de dados.

LISTAGEM 22.10 Exemplo do bloco principal do DataAdapter


1: program SqlDADemo;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses
6: System.Data,
7: System.Data.SqlClient,
8: System.IO,
9: System.Collections;
10:
540 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.10 Continuação


11: var
12: sqlCn: SqlConnection;
13: sqlDA: SqlDataAdapter;
14: dsNorthWind: DataSet;
15: tblProduct: DataTable;
16: dr: DataRow;
17: ...
18: const
19: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
20: c_sel_cst = 'SELECT * FROM Products';
21: begin
22: sqlCn := SqlConnection.Create(c_cnstr);
23: sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
24: dsNorthWind := DataSet.Create('NorthWind');
25: sqlDA.TableMappings.Add('Table', 'Products');
26: sqlDA.Fill(dsNorthWind);
27: tblProduct := dsNorthWind.Tables['Products'];
28:
29: sqlDA.InsertCommand := GetDataAdapterInsertCommand;
30: sqlDA.UpdateCommand := GetDataAdapterUpdateCommand;
31: sqlDA.DeleteCommand := GetDataAdapterDeleteCommand;
32:
33: // Insere uma Linha
34: dr := tblProduct.NewRow;
35: dr['ProductName'] := 'Second Burn Hot Sauce';
36: dr['SupplierID'] := System.Object(2);
37: dr['CategoryID'] := System.Object(2);
38: dr['QuantityPerUnit'] := '48 - 6 oz jars';
39: dr['UnitPrice'] := System.Object(23.00);
40: dr['UnitsInStock'] := System.Object(50);
41: dr['UnitsOnOrder'] := System.Object(0);
42: dr['ReorderLevel'] := System.Object(15);
43: dr['Discontinued'] := System.Object(0);
44: tblProduct.Rows.Add(dr);
45:
46: //Modifica uma Linha
47: dr := tblProduct.Rows[5];
48: dr['UnitsInStock'] := System.Object(10);
49:
50: tblProduct.Rows[0].Delete;
51:
52: sqlDA.Update(tblProduct);
53: end.

u Localize o código no CD: \Code\Chapter 22\Ex03\.

Talvez você tenha notado que isso não difere muito do exemplo anterior na Lista-
gem 22.2. As linhas 29–31 são diferentes. Essas três linhas chamam as funções GetDa-
Atualizando a origem de dados com a lógica de atualização personalizada 541

taAdapterInsertCommand( ), GetDataAdapterUpdateCommand( ) e GetDataAdapterDeleteCommand( ) e


atribuem o SqlCommand resultante às propriedades InsertCommand, UpdateCommand e Delete-
Command, respectivamente. Além disso, a linha 52 mostra a chamada para o método SqlDa-
taAdapter.Update( ). A versão do método intensamente sobrecarregado utilizado aqui
aceita a tabela que queremos atualizar como o parâmetro. Como mencionado anterior-
mente, as três propriedades SqlCommand são utilizadas pelo SqlDataAdapter para realmente
realizar as atualizações na origem de dados de maneira semelhante à maneira direta
como a técnica SqlCommand tratou isso. Vejamos o conteúdo das três funções que retornam
as instâncias da classe SqlCommand. A Listagem 22.11 mostra como o SqlCommand é construí-
do para a operação INSERT.

LISTAGEM 22.11 Função GetDataAdapterInsertCommand( )


1: function GetDataAdapterInsertCommand: SqlCommand;
2: var
3: sqlCmd: SqlCommand;
4: begin
5: // Cria o Command
6: sqlCmd := SqlCommand.Create(GetCommand('c_ins.sql'), sqlCn);
7: sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40);
8: sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int);
9: sqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int);
10: sqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20);
11: sqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money);
12: sqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt);
13: sqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt);
14: sqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt);
15: sqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit);
16:
17: Result := sqlCmd;
18: end;

u Localize o código no CD: \Code\Chapter 22\Ex03\.

Novamente, utilizamos uma função utilitária, GetCommand( ), para carregar o conteú-


do de um arquivo de texto contendo a instrução de Transact-SQL real mostrada na Lista-
gem 22.4. Essa função simplesmente configura os parâmetros para a classe SqlCommand.
Além disso, nada precisa ser feito para especificar qual DataRowVersion utilizar porque esse
é um comando INSERT e DataRowVersion.Current é o padrão quando nenhum é especificado.
A operação UPDATE requer os dois DataRowVersions, Current e Original, como mostrado na Lis-
tagem 22.12.

LISTAGEM 22.12 Função GetDataAdapterUpdateCommand( )


1: function GetDataAdapterUpdateCommand: SqlCommand;
2: var
3: sqlCmd: SqlCommand;
542 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.12 Continuação


4: begin
5: sqlCmd := sqlCommand.Create(GetCommand('c_upd.sql'), sqlCn);
6: sqlCmd.Parameters.Add('@C_ProductName', SqlDbType.NVarChar, 40);
7: sqlCmd.Parameters.Add('@C_SupplierID', SqlDbType.Int);
8: sqlCmd.Parameters.Add('@C_CategoryID', SqlDbType.Int);
9: sqlCmd.Parameters.Add('@C_QuantityPerUnit', SqlDbType.NVarChar, 20);
10: sqlCmd.Parameters.Add('@C_UnitPrice', SqlDbType.Money);
11: sqlCmd.Parameters.Add('@C_UnitsInStock', SqlDbType.SmallInt);
12: sqlCmd.Parameters.Add('@C_UnitsOnOrder', SqlDbType.SmallInt);
13: sqlCmd.Parameters.Add('@C_ReorderLevel', SqlDbType.SmallInt);
14: sqlCmd.Parameters.Add('@C_Discontinued', SqlDbType.Bit);
15:
16: sqlCmd.Parameters.Add('@O_ProductID', SqlDbType.Int);
17: sqlCmd.Parameters.Add('@O_ProductName', SqlDbType.NVarChar, 40);
18: sqlCmd.Parameters.Add('@O_SupplierID', SqlDbType.Int);
19: sqlCmd.Parameters.Add('@O_CategoryID', SqlDbType.Int);
20: sqlCmd.Parameters.Add('@O_QuantityPerUnit', SqlDbType.NVarChar, 20);
21: sqlCmd.Parameters.Add('@O_UnitPrice', SqlDbType.Money);
22: sqlCmd.Parameters.Add('@O_UnitsInStock', SqlDbType.SmallInt);
23: sqlCmd.Parameters.Add('@O_UnitsOnOrder', SqlDbType.SmallInt);
24: sqlCmd.Parameters.Add('@O_ReorderLevel', SqlDbType.SmallInt);
25: sqlCmd.Parameters.Add('@O_Discontinued', SqlDbType.Bit);
26:
27: sqlCmd.Parameters['@O_ProductID'].SourceVersion :=
28: DataRowVersion.Original;
29: sqlCmd.Parameters['@O_ProductName'].SourceVersion :=
30: DataRowVersion.Original;
31: sqlCmd.Parameters['@O_SupplierID'].SourceVersion :=
32: DataRowVersion.Original;
33: sqlCmd.Parameters['@O_CategoryID'].SourceVersion :=
34: DataRowVersion.Original;
35: sqlCmd.Parameters['@O_QuantityPerUnit'].SourceVersion :=
36: DataRowVersion.Original;
37: sqlCmd.Parameters['@O_UnitPrice'].SourceVersion :=
38: DataRowVersion.Original;
39: sqlCmd.Parameters['@O_UnitsInStock'].SourceVersion :=
40: DataRowVersion.Original;
41: sqlCmd.Parameters['@O_UnitsOnOrder'].SourceVersion :=
42: DataRowVersion.Original;
43: sqlCmd.Parameters['@O_ReorderLevel'].SourceVersion :=
44: DataRowVersion.Original;
45: sqlCmd.Parameters['@O_Discontinued'].SourceVersion :=
46: DataRowVersion.Original;
47:
48: Result := sqlCmd;
49: end;

u Localize o código no CD: \Code\Chapter 22\Ex03\.


Atualizando a origem de dados com a lógica de atualização personalizada 543

Novamente, utilizando a instrução Transact-SQL mostrada na Listagem 22.7, essa


função primeiro cria os parâmetros para a cláusula SET da instrução SQL. Esses parâme-
tros requerem a versão Current dos valores de linha (linhas 5–14). Então, as linhas 16–25
criam os parâmetros necessários à cláusula WHERE da instrução SQL. A cláusula WHERE requer
os valores de linha Original; portanto, as linhas 27–46 especificam isso atribuindo Data-
RowVersion.Original à propriedade SourceVersion dos parâmetros para a cláusula WHERE.
A Listagem 22.13 ilustra como o DeleteCommand é configurado .

LISTAGEM 22.13 Função GetDataAdapterDeleteCommand( )


function GetDataAdapterDeleteCommand: SqlCommand;
var
sqlCmd: SqlCommand;
begin
sqlCmd := SqlCommand.Create(GetCommand('c_del.sql'), sqlCn);
sqlCmd.Parameters.Add('@ProductID', SqlDbType.Int);
sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40);
sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int);
SqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int);
SqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20);
SqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money);
SqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt);
SqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt);
SqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt);
SqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit);

sqlCmd.Parameters['@ProductID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@ProductName'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@SupplierID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@CategoryID'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@QuantityPerUnit'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitPrice'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitsInStock'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@UnitsOnOrder'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@ReorderLevel'].SourceVersion :=
DataRowVersion.Original;
sqlCmd.Parameters['@Discontinued'].SourceVersion :=
DataRowVersion.Original;

Result := sqlCmd;
end;
544 Capítulo 22 Salvando dados na origem de dados

u Localize o código no CD: \Code\Chapter 22\Ex03\.

Semelhante à GetDataAdapterUpdateCommand( ), essa função de configuração para Delete-


Command também requer a versão Original de dados de linha.
Você pode ver que utilizando o SqlDataAdapter, você pode configurar as várias proprie-
dades SqlCommand uma vez e o SqlDataAdapter cuidará de mapear as colunas para os parâme-
tros apropriados. Basicamente, ao adicionar um parâmetro, o nome de parâmetro é atri-
buído à propriedade SourceColumn do objeto Parameter. SourceColumn referencia uma coluna
no DataTable à qual o DataAdapter está associado. Isso é um nome por referência de nome.
Além disso, quando você invocar o método SqlDataAdapter.Update( ), SqlDataAdapter deter-
mina qual SqlCommand utilizar na atualização linha por linha.
Utilizar classes SqlCommand diretas ou as propriedades SqlCommand da classe SqlDataAdap-
ter requer mais código do que utilizar a classe CommandBuilder. Entretanto, como você está
escrevendo o código de atualização, mais opções estão à sua disposição para otimização e
personalização. Uma opção seria utilizar uma stored procedure ao realizar suas atualiza-
ções, o que a próxima seção discute.

Atualizando com uma stored procedure


O SqlDataAdapter permite executar suas instruções de atualização por meio de stored pro-
cedures. Os seguintes exemplos ilustram esse processo. A Listagem 22.14 é um fragmento
do exemplo no CD que mostra o bloco de código principal que ilustra o uso do SqlDataA-
dapter.

LISTAGEM 22.14 SqlDataAdapter utilizando uma stored procedure


1: program SqlDAProc;
2: {$APPTYPE CONSOLE}
3: uses
4: System.Data,
5: System.Data.SqlClient,
6: System.IO,
7: System.Collections;
8: var
9: sqlCn: SqlConnection;
10: sqlDA: SqlDataAdapter;
11: dsNorthWind: DataSet;
12: tblProduct: DataTable;
13: dr: DataRow;
14: const
15: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
16: c_sel_cst = 'SELECT * FROM Products';
17:
18: ...
19: begin
20: sqlCn := SqlConnection.Create(c_cnstr);
21: sqlDA := SqlDataAdapter.Create('SelectProduct', sqlCn);
Atualizando a origem de dados com a lógica de atualização personalizada 545

LISTAGEM 22.14 Continuação


22: sqlDA.SelectCommand.CommandType := CommandType.StoredProcedure;
23: sqlDA.InsertCommand := GetDataAdapterInsertCommand;
24: sqlDA.UpdateCommand := GetDataAdapterUpdateCommand;
25: sqlDA.DeleteCommand := GetDataAdapterDeleteCommand;
26:
27: dsNorthWind := DataSet.Create('NorthWind');
28: sqlDA.Fill(dsNorthWind, 'Products');
29: sqlDA.TableMappings.Add('Table', 'Products');
30: tblProduct := dsNorthWind.Tables['Products'];
31:
32: // Insere uma linha
33: dr := tblProduct.NewRow;
34: dr['ProductName'] := 'Second Burn Hot Sauce';
35: dr['SupplierID'] := System.Object(2);
36: dr['CategoryID'] := System.Object(2);
37: dr['QuantityPerUnit'] := '48 - 6 oz jars';
38: dr['UnitPrice'] := System.Object(23.00);
39: dr['UnitsInStock'] := System.Object(50);
40: dr['UnitsOnOrder'] := System.Object(0);
41: dr['ReorderLevel'] := System.Object(15);
42: dr['Discontinued'] := System.Object(0);
43: tblProduct.Rows.Add(dr);
44:
45: //Modifica uma linha
46: dr := tblProduct.Rows[5];
47: dr['UnitsInStock'] := System.Object(10);
48:
49: // Exclui uma linha
50: dr := tblProduct.Rows[tblProduct.Rows.Count-1];
51: dr.Delete;
52:
53: sqlDA.Update(tblProduct);
54: end.

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Novamente, este não é muito diferente dos exemplos anteriores. Note na linha 21
que o nome da stored procedure SelectProduct é passado para o construtor do SqlDataAdap-
ter em vez de uma instrução de Transact-SQL. A stored procedure SelectProduct é mostra-
da na Listagem 22.15.

LISTAGEM 22.15 Stored procedure SelectProduct


CREATE PROCEDURE SelectProduct
AS
SET NOCOUNT ON
546 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.15 Continuação


SELECT
ProductId,
ProductName,
SupplierID,
CategoryID,
QuantityPerUnit,
UnitPrice,
UnitsInStock,
UnitsOnOrder,
ReorderLevel,
Discontinued
FROM
Products

return

GO

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Note também na linha 22 da Listagem 22.14 que SelectCommand é uma stored pro-
cedure atribuindo CommandType.StoredProcedure à sua propriedade CommandType. Como os
exemplos anteriores, esse exemplo contém três funções que retornam um SqlCommand para
cada operação a ser realizada. Cada um desses SqlCommands será associado com uma stored
procedure no banco de dados. A Listagem 22.16 ilustra a função que recupera uma ins-
tância SqlCommand para inserir um registro.

LISTAGEM 22.16 GetDataAdapterInsertCommand( )


1: function GetDataAdapterInsertCommand: SqlCommand;
2: var
3: sqlCmd: SqlCommand;
4: begin
5: sqlCmd := SqlCommand.Create('InsertProduct', sqlCn);
6: sqlCmd.CommandType := CommandType.StoredProcedure;
7:
8: sqlCmd.Parameters.Add('@ProductID', SqlDbType.Int, 0,
9: 'ProductID');
10: sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40,
11: 'ProductName');
12: sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int, 0,
13: 'SupplierID');
14: sqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int, 0,
15: 'CategoryID');
16: sqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20,
17: 'QuantityPerUnit');
18: sqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money, 0,
Atualizando a origem de dados com a lógica de atualização personalizada 547

LISTAGEM 22.16 Continuação


19: 'UnitPrice');
20: sqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt, 0,
21: 'UnitsInStock');
22: sqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt, 0,
23: 'UnitsOnOrder');
24: sqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt, 0,
25: 'ReorderLevel');
26: sqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit, 0,
27: 'Discontinued');
28:
29: sqlCmd.Parameters['@ProductID'].Direction :=
30: ParameterDirection.Output;
31: sqlCmd.Parameters['@ProductID'].SourceVersion :=
32: DataRowVersion.Original;
33:
34: Result := sqlCmd;
35: end;

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Como com o SelectCommand, a linha 6 especifica que essa classe SqlCommand está lidando
com uma stored procedure. Observe que na criação dos parâmetros para a stored proce-
dure, eu utilizo o método Add( ) sobrecarregado, que aceita o nome de campo como o úl-
timo parâmetro. Não fazer isso faz com que uma exceção seja levantada porque o SqlDa-
taAdapter não terá mapeado a coluna DataTable para um parâmetro na stored procedure.
As linhas 29–30 mostram como indicar um dos parâmetros da stored procedure como
um parâmetro de saída. Isso significa que o valor será retornado novamente à aplicação
chamadora. Nesse caso, estou retornando o ProductID, que é a chave primária para a tabe-
la Products. A stored procedure INSERT é mostrada na Listagem 22.17.

LISTAGEM 22.17 Stored procedure InsertProduct

CREATE PROCEDURE InsertProduct

@ProductID int output,


@ProductName nvarchar(40),
@SupplierID int,
@CategoryID int,
@QuantityPerUnit nvarchar(20),
@UnitPrice money,
@UnitsInStock smallint,
@UnitsOnOrder smallint,
@ReorderLevel smallint,
@Discontinued bit
AS
SET NOCOUNT ON
INSERT INTO Products (
548 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.17 Continuação


ProductName,
SupplierID,
CategoryID,
QuantityPerUnit,
UnitPrice,
UnitsInStock,
UnitsOnOrder,
ReorderLevel,
Discontinued)
VALUES (
@ProductName,
@SupplierID,
@CategoryID,
@QuantityPerUnit,
@UnitPrice,
@UnitsInStock,
@UnitsOnOrder,
@ReorderLevel,
@Discontinued)

IF @@rowcount = 0
return 0

SET @ProductID = Scope_Identity( )

SELECT @ProductID ProductID

return

GO

u Localize o código no CD: \Code\Chapter 22\Ex04\.

A Listagem 22.18 é o GetDataAdapterUpdateCommand( ), que mostra a criação da instância


SqlCommand associada com a stored procedure UPDATE.

LISTAGEM 22.18 GetDataAdapterUpdateCommand( )


1: function GetDataAdapterUpdateCommand: SqlCommand;
2: var
3: sqlCmd: SqlCommand;
4: begin
5: sqlCmd := SqlCommand.Create('UpdateProduct', sqlCn);
6: sqlCmd.CommandType := CommandType.StoredProcedure;
7:
8: sqlCmd.Parameters.Add('@ProductID', SqlDbType.Int, 0,
9: 'ProductID');
Atualizando a origem de dados com a lógica de atualização personalizada 549

LISTAGEM 22.18 Continuação


10: sqlCmd.Parameters.Add('@ProductName', SqlDbType.NVarChar, 40,
11: 'ProductName');
12: sqlCmd.Parameters.Add('@SupplierID', SqlDbType.Int, 0,
13: 'SupplierID');
14: sqlCmd.Parameters.Add('@CategoryID', SqlDbType.Int, 0,
15: 'CategoryID');
16: sqlCmd.Parameters.Add('@QuantityPerUnit', SqlDbType.NVarChar, 20,
17: 'QuantityPerUnit');
18: sqlCmd.Parameters.Add('@UnitPrice', SqlDbType.Money, 0,
19: 'UnitPrice');
20: sqlCmd.Parameters.Add('@UnitsInStock', SqlDbType.SmallInt, 0,
21: 'UnitsInStock');
22: sqlCmd.Parameters.Add('@UnitsOnOrder', SqlDbType.SmallInt, 0,
23: 'UnitsOnOrder');
24: sqlCmd.Parameters.Add('@ReorderLevel', SqlDbType.SmallInt, 0,
25: 'ReorderLevel');
26: sqlCmd.Parameters.Add('@Discontinued', SqlDbType.Bit, 0,
27: 'Discontinued');
28:
29: sqlCmd.Parameters['@ProductID'].SourceVersion :=
30: DataRowVersion.Original;
31:
32: Result := sqlCmd;
33: end;

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Essa função é semelhante àquela da Listagem 22.16 que lida com a stored procedure
INSERT. Mas a função GetDataAdapterUpdateCommand( ) não lida com um parâmetro de saída.
Isso nem sempre pode ser o caso. Talvez você tenha stored procedures UPDATE que devem
retornar valores que são atribuídos pelo sistema. Para manter a aplicação chamadora em
sincronia com o armazenamento de dados, você precisará retornar qualquer valor gera-
do pelo sistema. Isso seria tratado da mesma maneira como mostrado na Listagem 22.16.
A Listagem 22.19 mostra a stored procedure UPDATE associada com a classe SqlCommand na li-
nha 5 da Listagem 22.18.

LISTAGEM 22.19 Stored procedure UpdateProduct


CREATE PROCEDURE UpdateProduct
@ProductID int,
@ProductName nvarchar(40),
@SupplierID int,
@CategoryID int,
@QuantityPerUnit nvarchar(20),
@UnitPrice money,
@UnitsInStock smallint,
@UnitsOnOrder smallint,
550 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.19 Continuação


@ReorderLevel smallint,
@Discontinued bit
AS
SET NOCOUNT ON
UPDATE Products
SET
ProductName = @ProductName,
SupplierID = @SupplierID,
CategoryID = @CategoryID,
QuantityPerUnit = @QuantityPerUnit,
UnitPrice = @UnitPrice,
UnitsInStock = @UnitsInStock,
UnitsOnOrder = @UnitsOnOrder,
ReorderLevel = @ReorderLevel,
Discontinued = @Discontinued
WHERE
ProductID = @ProductID

IF @@rowcount = 0
return 0

return

GO

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Por fim, a Listagem 22.20 mostra a função que cria uma instância SqlCommand que lida
com as stored procedures DELETE para a tabela Products.

LISTAGEM 22.20 GetDataAdapterDeleteCommand( )


1: function GetDataAdapterDeleteCommand: SqlCommand;
2: var
3: sqlCmd: SqlCommand;
4: begin
5: sqlCmd := SqlCommand.Create('DeleteProduct', sqlCn);
6: sqlCmd.CommandType := CommandType.StoredProcedure;
7:
8: sqlCmd.Parameters.Add('@ProductID', SqlDbType.Int, 0, 'ProductID');
9:
10: sqlCmd.Parameters['@ProductID'].SourceVersion :=
11: DataRowVersion.Original;
12:
13: Result := sqlCmd;
14: end;

u Localize o código no CD: \Code\Chapter 22\Ex04\.


Atualizando a origem de dados com a lógica de atualização personalizada 551

Você notará que GetDataAdapterDeleteCommand( ) é bem menor que as outras duas fun-
ções. A razão aqui é porque a stored procedure para excluir um registro utiliza somente o
campo de chave primária, ProductID, para localizar o registro a ser excluído no banco de
dados. Isso é mostrado na Listagem 22.21.

LISTAGEM 22.21 Stored procedure DeleteProduct


CREATE PROCEDURE DeleteProduct
@ProductID int
AS
SET NOCOUNT ON

DELETE FROM Products


WHERE
ProductID = @ProductID

return
GO

u Localize o código no CD: \Code\Chapter 22\Ex04\.

Tratando concorrência
Se estiver desenvolvendo uma aplicação multiusuário, você vai inevitavelmente se depa-
rar com uma situação em que dois usuários recuperam um registro e tentam modificá-lo
ao mesmo tempo. Em poucos casos, a abordagem “o último vence” é aceitável especial-
mente se os usuários não forem cientes de que isso ocorre.
Para a maioria dos casos, há duas maneiras primárias em que a concorrência pode ser
gerenciada. Você viu a primeira com a classe CommandBuilder. Essa abordagem requer que o
usuário que salva o registro deve passar os valores Original de todas as colunas para uma
linha modificada para que o servidor possa assegurar que cada linha corresponda com
aquela que existe no banco de dados antes de atualizar a linha de banco de dados. Se não
houver correspondência, significa que o registro foi modificado por outro usuário. Uma
exceção deve ser levantada e o segundo usuário deve receber uma oportunidade de fazer
algo sobre a exceção.
Embora efetiva, essa abordagem é altamente ineficiente. Uma abordagem melhor se-
ria utilizar campos timestamp, ou algo equivalente a timestamp para origens de dados
que não suportam timestamp.
Um timestamp não é, como o nome sugere, um valor DateTime. Ele é um valor binário
que é único dentro do banco de dados. Quando um registro contendo um timestamp ti-
ver sido modificado, o timestamp também é alterado. Isso funciona porque o banco de
dados utiliza um timestamp como parte da pesquisa sobre um registro quando ele for
atualizado. Portanto, somente a chave primária e o timestamp são utilizados para locali-
zar o registro em vez de cada coluna na linha.
Para ilustrar isso, criei um banco de dados baseado no que é mostrado na Figura
22.1. Os scripts para criar e preencher esse banco de dados podem ser localizados no CD
no diretório \Code\Databases.
552 Capítulo 22 Salvando dados na origem de dados

FIGURA 22.1 Diagrama de banco de dados de exemplo.

Você notará que as duas tabelas, company e contact, contêm um campo timestamp,
row_version. O próximo exemplo ilustra como utilizar a coluna timestamp; ele também
ilustra como você pode utilizar SqlDataAdapter.RowUpdatedEvent para tratar possíveis erros.
A Listagem 22.22 mostra o bloco principal de código para esse exemplo. Essa é uma
Listagem parcial do exemplo no CD.

LISTAGEM 22.22 Exemplo de concorrência


1: program ConcurrencyDemo;
2:
3: uses
4: System.Data,
5: System.Data.SqlClient,
6: System.IO;
7: var
8: sqlCn: SqlConnection;
9: sqlDA: SqlDataAdapter;
10: dsCompany: DataSet;
11: tblcontact: DataTable;
12: dr: DataRow;
13:
14: const
Atualizando a origem de dados com a lógica de atualização personalizada 553

LISTAGEM 22.22 Continuação


15: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
16: c_sel_cst = 'SELECT * FROM company; SELECT * FROM contact';
17:
18: begin
19: sqlCn := SqlConnection.Create(c_cnstr);
20: sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
21: dsCompany := DataSet.Create('ddn_company');
22:
23: sqlDA.TableMappings.Add('Table', 'company');
24: sqlDA.TableMappings.Add('Table1', 'contact');
25: sqlDA.Fill(dsCompany);
26: sqlDA.ContinueUpdateOnError := True;
27: Include(sqlDA.RowUpdated, OnRowUpdated);
28: sqlDA.UpdateCommand := GetDAUpdateCommand;2929:
30: tblcontact := dsCompany.Tables['contact'];
31: //Modifica duas linhas
32: dr := tblcontact.Rows[0];
33: dr['cell_phone'] := '111-111-1122';
34: dr := tblcontact.Rows[1];
35: dr['cell_phone'] := '555-555-5556';
36:
37: Console.WriteLine('Press enter when you are ready to update.');
38: Console.ReadLine;
39:
40: sqlDA.Update(tblcontact);
41: end.

u Localize o código no CD: \Code\Chapter 22\Ex05\.

A linha 26 configura a propriedade SqlDataAdapter.ContinueUpdateOnError como True a


fim de permitir que todas as atualizações ocorram – mesmo se uma delas resultar em um
erro. Não configurando isso como True, uma DBConcurrencyException é levantada no pri-
meiro erro. A linha 27 fornece um handler de evento que é invocado depois de uma li-
nha ser atualizada. Dentro desse handler de evento você tem a oportunidade de fornecer
algum tratamento especial quando ocorrer um erro.
A Listagem 22.23 mostra o handler de evento OnRowUpdated que é adicionado ao SqlDa-
taAdapter.RowUpdatedEvent na linha 27 da Listagem 22.22.

LISTAGEM 22.23 Handler de evento OnRowUpdated( )


1: procedure OnRowUpdated(sender: System.Object; e: SqlRowUpdatedEventArgs);
2: begin
3: Console.WriteLine('Command: '+e.Command.ToString);
4: Console.WriteLine('CommandType: '+Enum(e.StatementType).ToString);
5: Console.WriteLine('RecordsAffected: '+e.RecordsAffected.ToString);
6: Console.WriteLine('Status: '+Enum(e.Status).ToString);
7:
554 Capítulo 22 Salvando dados na origem de dados

LISTAGEM 22.23 Continuação


8: if e.Status = UpdateStatus.ErrorsOccurred then
9: begin
10: Console.WriteLine('Error Type: '+e.Errors.GetType.ToString);
11: Console.WriteLine('On contact_id: '+E.Row['contact_id'].ToString);
12: end;
13:
14: Console.WriteLine(' – ');
15: end;

u Localize o código no CD: \Code\Chapter 22\Ex05\.

Um dos parâmetros para o handler de evento OnRowUpdated é uma classe SqlRowUpdate-


EventArgs (linha 1). Essa classe contém informações sobre a linha e o status da atualização.
Esse exemplo simplesmente copia as informações de volta para o console sobre cada atu-
alização de linha. Se ocorrer um erro, o identificador de linha, contact_id (linha 11), tam-
bém é exibido.

ATENÇÃO
Ao processar erros no evento RowUpdated, a aplicação tem uma conexão ativa com a origem de da-
dos. É importante não fazer nenhum processamento prolongado nesse evento. Isso incluiria qual-
quer processamento que exigiria interação de usuário. Se existir um erro que exija interação de
usuário, será melhor invocar assincronamente alguma ação que permita que esse evento termine,
mas ainda possibilite ao usuário realizar sua tarefa.

DICA
Uma maneira de tratar a invocação assíncrona mencionada no aviso anterior seria utilizar o mode-
lo de chamada assíncrona de um delegate por meio dos métodos BeginInvoke( ) e EndInvoke( ).
Delphi oculta esses métodos, então você teria de utilizar Reflection para fazer isso. Embora isso não
seja algo especificamente abrangido neste livro, numerosos exemplos estão disponíveis em várias
localizações na Internet.

A configuração da propriedade UpdateCommand é onde você especifica para utilizar a


versão Original do parâmetro timestamp. Não listarei o procedimento inteiro aqui. As li-
nhas específicas que fazem isso dentro da função GetDAUpdateCommand( ) são:

sqlCmd.Parameters.Add('@row_version', SqlDbType.TimeStamp, 0,
'row_version');
sqlCmd.Parameters['@row_version'].SourceVersion :=
DataRowVersion.Original;

As linhas restantes da função GetDAUpdateCommand( ) são muito semelhantes às mostra-


das nos exemplos anteriores.
Para demonstrar o tratamento de concorrência, adicionei o método ReadLine( ) antes
de realmente enviar os dados atualizados à origem de dados (consulte a Listagem 22.22,
linha 38). Isso me permitiu utilizar a ferramenta SQL Analyzer para modificar a primeira
Atualizando a origem de dados com a lógica de atualização personalizada 555

linha na tabela contact, simulando assim uma gravação concorrente. A modificação que
fiz foi:

update contact set first_name = 'Robert' where contact_id = 1

Depois de enviar essa atualização e, em seguida, pressionar a tecla Enter dentro da ja-
nela do console da aplicação, a seguinte saída é exibida mostrando que uma atualização
de registro foi bem-sucedida, enquanto uma outra falhou:

[n:\chapter 22\Ex05]ConcurrencyDemo.exe
Press enter when you are ready to update.

Command: System.Data.SqlClient.SqlCommand
CommandType: Update
RecordsAffected: 0
Status: ErrorsOccurred
Error Type: System.Data.DBConcurrencyException
On contact_id: 1

Command: System.Data.SqlClient.SqlCommand
CommandType: Update
RecordsAffected: 1
Status: Continue

Você pode ver que a primeira atualização de registro falhou porque outro usuário
havia feito modificações nesse registro antes de ele ser postado dentro dessa aplicação.

Atualizando dados depois da atualização


Para a maior parte, quando estiver atualizando ou adicionando dados em uma origem de
dados, você tem a maioria das informações já em seu DataSet. Entretanto, você não terá
os dados gerados pela origem de dados, como campos de auto-incremento que são utili-
zados como identificadores e talvez timestamps que você precisará para atualizações adi-
cionais para que a concorrência seja tratada corretamente.
Há algumas maneiras de obter essas informações de volta no DataSet, as quais são dis-
cutidas aqui.
A propriedade UpdatedRowSource da classe SqlCommand determina como os dados são pas-
sados de volta para o DataRow sempre que uma atualização for feita pelo método SqlData-
Adapter.Update( ). Essa propriedade pode armazenar um dos quatro valores mostrados na
Tabela 22.1.
Uma das maneiras de retornar valores para o DataRow modificado envolve realizar
uma consulta de lote como parte das instruções Transact-SQL INSERT ou UPDATE. Isso re-
quer que a origem de dados associada suporte consultas em lote.
Utilizando o banco de dados a partir do último exemplo, as Listagens 22.24 e 22.25
mostram as instruções Transact-SQL para uma operação INSERT e UPDATE, respectivamente.
556 Capítulo 22 Salvando dados na origem de dados

TABELA 22.1 Valores UpdatedRowSource


Valor Descrição
Both Tanto os parâmetros de saída como a primeira linha retornada são
passados de volta para o DataRow modificado.
FirstReturnedRecord Somente os valores na primeira linha retornada são passados de volta
para o DataRow modificado.
Nenhum Os valores de linha retornados e parâmetros são ignorados.
OutputParameters Somente parâmetros de saída são passados de volta para o DataRow
modificado.

LISTAGEM 22.24 Instrução Transact-SQL INSERT


INSERT INTO contact (
first_name,
last_name,
mi,
department,
position,
work_phone,
home_phone,
fax,
cell_phone,
email,
company_id)
VALUES (
@first_name,
@last_name,
@mi,
@department,
@position,
@work_phone,
@home_phone,
@fax,
@cell_phone,
@email,
@company_id);

SET @contact_id = Scope_Identity( );


SELECT row_version, @contact_id contact_id
FROM contact
WHERE (contact_id = @contact_id)

u Localize o código no CD: \Code\Chapter 22\Ex06\.


Atualizando a origem de dados com a lógica de atualização personalizada 557

LISTAGEM 22.25 Instrução Transact-SQL UPDATE


UPDATE contact
SET
first_name = @first_name,
last_name = @last_name,
mi = @mi,
department = @department,
position = @position,
work_phone = @work_phone,
home_phone = @home_phone,
fax = @fax,
cell_phone = @cell_phone,
email = @email
WHERE
contact_id = @contact_id AND
row_version = @row_version;

SELECT row_version FROM contact WHERE (contact_id = @contact_id)

u Localize o código no CD: \Code\Chapter 22\Ex06\.


Você notará que essas duas instruções contêm uma instrução SELECT que segue a ins-
trução UPDATE. No caso da instrução INSERT (ver Listagem 22.24), a instrução SELECT recupe-
ra tanto o novo contact_id como o row_version. No caso da instrução UPDATE (ver Listagem
22.25), somente o row_version é recuperado.
Outra técnica para recuperar a identidade de um registro adicionado recentemente é
utilizar o evento RowUpdated de SqlDataAdapter. Lembre-se de que o evento RowUpdated ocorre
quando a conexão ainda estiver ativa e o escopo da transação ainda for válido. Portanto,
a Listagem 22.26 mostra como você pode recuperar o último identificador e adicioná-lo
ao DataRow.

LISTAGEM 22.26 Evento RowUpdated utilizado para recuperar o último identificador


// handler para o evento RowUpdated
procedure OnRowUpdated(sender: System.Object; e: SqlRowUpdatedEventArgs);
var
sqlCmdID: SqlCommand;
contact_id: Integer;
begin
if (e.Status = UpdateStatus.Continue) and
(e.StatementType = StatementType.Insert) then
begin
sqlCmdID := SqlCommand.Create('SELECT @@IDENTITY', sqlCn);
e.Row['contact_id'] := sqlCmdID.ExecuteScalar;
e.Row.AcceptChanges;
end;
end;

u Localize o código no CD: \Code\Chapter 22\Ex07\.


NESTE CAPÍTULO
— Processamento de transação CAPÍTULO 23
— DataSets fortemente
tipificados Trabalhando com
transações e DataSets
fortemente tipificados

E ste capítulo fornecerá o entendimento de uma


capacidade de uma origem de dados que você pode
utilizar para impor integridade de dados com suas
aplicações de banco de dados. Essa capacidade é
chamada processamento de transação. Além disso, este
capítulo também abrange DataSets fortemente
tipificados. Esse recurso, suportado pela IDE, pode
aumentar significativamente a produtividade de seu
desenvolvimento.

Processamento de transação
Freqüentemente, as atualizações em tabelas devem
ocorrer como uma operação agrupada. Por exemplo,
suponha que dentro de uma operação você deva
atualizar múltiplas tabelas. Se a operação de atualização
falhar em alguma das tabelas, a operação inteira deve
falhar. Isso inclui atualizações anteriores para outras
tabelas dentro da mesma operação.
Esse tipo de proteção pode ser alcançado utilizando
processamento de transação. As transações são instruções
que definem o início e fim do limite de uma operação.
As transações são representadas pela classe SqlTransaction.
Essa classe é retornada como o resultado do método
SqlConnection.BeginTransaction( ).
Quando uma transação é invocada, todas as
operações pela conexão especificada ocorrem dentro dos
limites da transação. Quando as operações são
concluídas, as alterações são postadas invocando o
método SqlTransaction.Commit( ). Se, entretanto, ocorrer
um erro como resultado de uma das operações, talvez
seja necessário abortar a transação. Isso é feito por meio
do método SqlTransaction.Rollback( ).
Processamento de transação 559

O típico código de esqueleto de uma operação de transação, portanto, é semelhante a:

MyTransaction := MysqlConnection.BeginTransaction;
try
// Faz operações
MyTransaction.Commit;
except
MyTransaction.Rollback;
raise; // ou faz algum outro processamento de erro quando necessário
end;

Qualquer atualização feita na origem de dados depois da instrução try será bem-
sucedida na invocação do método MyTransaction.Commit( ). Entretanto, se ocorrer uma
exceção a qualquer hora antes de chamar MyTransaction.Commit( ), o bloco except é execu-
tado e o método MyTransaction.Rollback( ) é invocado, abortando assim todas as atualiza-
ções na origem de dados. O que você faz dentro do bloco except realmente depende das cir-
cunstâncias. No mínimo, você levantaria a exceção novamente. Você também poderia re-
gistrar o erro em log sem fazer com que a exceção fosse novamente levantada, mas gerar al-
gum outro processo ao qual o usuário deve responder.

Um exemplo simples de processamento de transação


Utilizando o banco de dados company do Capítulo 22, o seguinte exemplo ilustra como
uma transação pode conter uma operação que inclui valores em duas tabelas. O progra-
ma vai primeiro adicionar um registro à tabela company. Então, utilizando o company_id
obtido dessa operação, ele adicionará um registro à tabela contact. A Listagem 23.1 mos-
tra esse programa de exemplo.

LISTAGEM 23.1 Exemplo simples de processamento de transação


1: program TransEx;
2: uses
3: System.Data,
4: System.Data.SqlClient,
5: System.IO;
6:
7: const
8: c_cnstr = 'server=XWING;database=ddn_company;Trusted_Connection=Yes';
9: c_sel_cst = 'SELECT * FROM company; SELECT * FROM contact';
10:
11: var
12: sqlCn: SqlConnection;
13: sqlDA: SqlDataAdapter;
14: dsCompany: DataSet;
15: sqlTrans: sqlTransaction;
16:
17: cmdInsCompany: SqlCommand;
18: cmdInsContact: SqlCommand;
560 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

LISTAGEM 23.1 Continuação


19: drCompany: DataRow;
20: drContact: DataRow;
21:
22: function GetInsertContactCmd: SqlCommand;
23: begin
24: ...
25: end;
26:
27: function GetInsertCompanyCmd: SqlCommand;
28: begin
29: ...
30: end;
31:
32: function InsertCompany: DataRow;
33: begin
34: // adiciona uma empresa
35: Result := dsCompany.Tables['company'].NewRow;
36: Result['company_name'] := 'Xapware Technologies Inc.';
37: Result['address_1'] := '4164 Austin Bluffs Pkwy, #363';
38: Result['city'] := 'Colorado Springs';
39: Result['state_abbr'] := 'CO';
40: Result['zip'] := '80918';
41:
42: // preenche os parâmetros SqlCommand
43: cmdInsCompany.Parameters['@company_name'].Value :=
44: Result['company_name'];
45: cmdInsCompany.Parameters['@address_1'].Value := Result['address_1'];
46: cmdInsCompany.Parameters['@address_2'].Value := Result['address_2'];
47: cmdInsCompany.Parameters['@city'].Value := Result['city'];
48: cmdInsCompany.Parameters['@state_abbr'].Value := Result['state_abbr'];
49: cmdInsCompany.Parameters['@zip'].Value := Result['zip'];
50: cmdInsCompany.Transaction := sqlTrans;
51: cmdInsCompany.ExecuteNonQuery;
52:
53: // atribui o id gerado pelo sistema à linha de dados
54: Result['company_id'] := cmdInsCompany.Parameters['@company_id'].Value;
55: end;
56:
57: function InsertContact(acompany_id: Integer): DataRow;
58: begin
59: Result := dsCompany.Tables['contact'].NewRow;
60: Result['first_name'] := 'Xavier';
61: Result['last_name'] := 'Pacheco';
62: Result['mi'] := 'G';
63: Result['department'] := 'Dept 1';
64: Result['position'] := 'Worker bee';
65: Result['work_phone'] := '123-123-1234';
66: Result['home_phone'] := '234-234-2344';
Processamento de transação 561

LISTAGEM 23.1 Continuação


67: Result['fax'] := '456-456-4566';
68: Result['cell_phone'] := '345-345-3654';
69: Result['email'] := 'xavier@somewhere.com';
70: Result['company_id'] := System.Object(acompany_id);
71:
72: // preenche os parâmetros SqlCommand
73: cmdInsContact.Parameters['@first_name'].Value := Result['first_name'];
74: cmdInsContact.Parameters['@last_name'].Value := Result['last_name'];
75: cmdInsContact.Parameters['@mi'].Value := Result['mi'];
76: cmdInsContact.Parameters['@department'].Value := Result['department'];
77: cmdInsContact.Parameters['@position'].Value := Result['position'];
78: cmdInsContact.Parameters['@work_phone'].Value := Result['work_phone'];
79: cmdInsContact.Parameters['@home_phone'].Value := Result['home_phone'];
80: cmdInsContact.Parameters['@fax'].Value := Result['fax'];
81: cmdInsContact.Parameters['@cell_phone'].Value := Result['cell_phone'];
82: cmdInsContact.Parameters['@email'].Value := Result['email'];
83: cmdInsContact.Parameters['@company_id'].Value :=
84: System.Object(acompany_id);
85: cmdInsContact.Transaction := sqlTrans;
86: cmdInsContact.ExecuteNonQuery;
87:
88: // atribui o id gerado pelo sistema à linha de dados
89: Result['contact_id'] := cmdInsContact.Parameters['@contact_id'].Value;
90:
91: end;
92:
93: begin
94: sqlCn := SqlConnection.Create(c_cnstr);
95: sqlDA := SqlDataAdapter.Create(c_sel_cst, sqlCn);
96: dsCompany := DataSet.Create('ddn_company');
97:
98: sqlDA.TableMappings.Add('Table', 'company');
99: sqlDA.TableMappings.Add('Table1', 'contact');
100: sqlDA.Fill(dsCompany);
101:
102: cmdInsCompany := GetInsertCompanyCmd;
103: cmdInsContact := GetInsertContactCmd;
104:
105: sqlcn.Open;
106: try
107: sqlTrans := sqlcn.BeginTransaction;
108: try
109: drCompany := InsertCompany;
110: drContact := InsertContact(Integer(drCompany['company_id']));
111: sqlTrans.Commit;
112: except
113: sqlTrans.Rollback;
114: raise;
562 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

LISTAGEM 23.1 Continuação


115: end;
116: // Adiciona as linhas ao dataset
117: dsCompany.Tables['company'].Rows.Add(drCompany);
118: dsCompany.Tables['contact'].Rows.Add(drContact);
119: finally
120: sqlcn.Close;
121: end;
122: end.

u Localize o código no CD: \Code\Chapter 23\Ex01\.

A Listagem 23.1 é um fragmento de uma aplicação que ilustra a utilização de uma


classe SqlTransaction para inserir linhas em duas tabelas dentro de um banco de dados de
SQL Server. As atualizações são feitas utilizando classes SqlCommand.
As funções GetInsertCompanyCmd( ) e GetInsertContactCmd( ) (ambas são excluídas da lis-
tagem) simplesmente carregam as instruções Transact-SQL para inserir registros nas ta-
belas company e contact, respectivamente. Elas retornam as instâncias da classe SqlCom-
mand resultantes (linhas 102-103).
A idéia principal é adicionar o registro à tabela mestra primeiro (company) e então uti-
lizar a chave primária resultante como a chave estrangeira para a inserção de tabela de
detalhe (contact). Os valores gerados pelo sistema, como a chave primária, são recupera-
dos de parâmetros de saída e atribuídos à sua coluna respectiva no DataRow. Se ambas as
inserções forem bem-sucedidas, os DataRows são adicionados ao DataSet para manter o Da-
taSet em sincronia com a origem de dados.
As duas funções InsertCompany( ) e InsertContact( ) realizam as operações de inserção.
Primeiro, cada função cria um novo DataRow a partir do DataSet para sua respectiva tabela.
Ela adiciona os valores de coluna – exceto para a chave primária quando esses são gera-
dos pela origem de dados. Eles então atribuem esses valores aos parâmetros em seus res-
pectivos objetos SqlCommand e invocam a atualização chamando a função SqlCommand.Execu-
teNonQuery( ). Os parâmetros de saída dos objetos SqlCommand são a chave primária para
cada inserção de registro. As chaves primárias são atribuídas ao DataRow, que é o resultado
da função de inserção.
As linhas 107–115 mostram o código da transação. A transação é iniciada na linha 107.
Dentro do bloco try, a função InsertCompany( ) é chamada e retorna o DataRow inserido. A colu-
na de chave primária desse DataRow é utilizada como o parâmetro para a função InsertCon-
tact( ). InsertContact( ) também retorna o DataRow inserido. Se tudo for bem-sucedido sem
erro, o método Commit( ) da transação é chamado (linha 111). Caso contrário, se uma exce-
ção for levantada, a função é revertida e a exceção é levantada novamente. As linhas
117–118 mostram como as linhas são adicionadas ao DataSet para mantê-lo em sincronia
com o DataSource. Essas linhas não seriam chamadas se ocorresse uma exceção.

Transações ao utilizar um DataAdapter


Você pode fazer processamento de transação ao utilizar um DataAdapter atribuindo a classe de
transação a cada um dos objetos de comando do DataAdapter. Então, você simplesmente inclui
a função Update( ) do DataAdapter dentro da transação como ilustrado no seguinte código:
Processamento de transação 563

sqlTrans := sqlcn.BeginTransaction;
try
sqlDA.InsertCommand.Transaction := sqlTrans;
sqlDA.UpdateCommand.Transaction := sqlTrans;
sqlDA.DeleteCommand.Transaction := sqlTrans;
// faça atualizações, inserções, exclusões
sqlDA.Update(dsCompany);
sqlTrans.Commit;
except
sqlTrans.Rollback;
raise;
end;

Níveis de isolamento
Em um ambiente multiusuário, as operações simultâneas serão provavelmente reali-
zadas na origem de dados, como leituras, atualizações, inserções e exclusões. A capa-
cidade das operações externas de ver alterações sendo feitas dentro de uma transação
antes da confirmação é controlada utilizando níveis de isolamento (isolation levels).
Os níveis de isolamento impõem um modo de bloqueio na origem de dados que,
quando adequadamente utilizado, pode evitar problemas de concorrência e corrup-
ção de dados. Em ADO.NET, há dois modos de bloqueio – Shared e Exclusive. No modo
de bloqueio Shared, uma transação concorrente pode ler, mas não modificar um recur-
so bloqueado. No modo de bloqueio Exclusive, nenhum acesso é permitido no recurso
bloqueado.
Os níveis de isolamentos estabelecem que existe um grau de privacidade entre tran-
sações que executam simultaneamente. Dependendo do nível de isolamento desejado,
as leituras realizadas dentro de uma transação podem entrar em uma de várias categorias.
Essas são mostradas na Tabela 23.1.

TABELA 23.1 Leituras de transação


Nome Descrição
Leitura suja (Dirty Read) Uma transação lê dados não confirmados a partir de uma transação
anterior. Como a atualização não é confirmada, os dados podem ser
inválidos. Por exemplo, suponha que a transação A grava algumas linhas
para uma tabela, mas não as confirma (não chama commit). Agora
suponha que a transação B leia essas linhas. Isso é chamado de leitura suja,
porque é possível que a transação A reverta as alterações (roll back),
resultando na presença de dados inválidos na transação B.
Leitura não-repetível Uma transação lê dados não confirmados a partir de uma transação
anterior. Se as leituras subseqüentes por essa transação forem realizadas, os
dados podem ser inconsistentes quando a transação anterior confirmar
suas atualizações. Essa é uma condição em que a transação A lê uma linha
ou linhas; depois disso, a transação B modifica alguns dos dados que a
transação A tem. Não é possível que a transação A leia os mesmos dados;
assim a leitura é não-repetível.
564 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

TABELA 23.1 Continuação


Nome Descrição
Leitura fantasma Uma transação exclui e insere linhas que fazem parte de um intervalo de
linhas selecionadas por outra transação. Por exemplo, a transação A lê um
conjunto de linhas baseada em uma consulta. A transação B grava algumas
linhas na tabela que teria atendido essa consulta. Essas são linhas
fantasmas.

A transação pode controlar o nível de concorrência por meio de um dos cinco níveis
de isolamento que são listados na Tabela 23.2.

TABELA 23.2 Níveis de isolamento


Nível de isolamento Descrição
ReadUncommitted As transações externas são capazes de realizar uma leitura suja. Os
bloqueios de compartilhamento são estabelecidos, e os bloqueios
exclusivos são honrados.
ReadCommitted As leituras sujas são impedidas. Os bloqueios compartilhados são presos
enquanto uma transação lê os dados. Como os dados podem estar
atualizados, as leituras fantasmas ou leituras não-repetíveis poderiam
ocorrer.
RepeatableRead Os bloqueios compartilhados são colocados em todos os dados em uma
consulta, impedindo que as transações subseqüentes realizem atualizações.
As leituras fantasmas ainda podem ocorrer.
Serializable Os bloqueios de intervalo são colocados em leituras de dados, impedindo
que as transações subseqüentes atualizem os dados até a transação ser
confirmada. Esse nível de isolamento impede leituras fantasmas.
Chaos As alterações pendentes a partir de níveis de isolamento mais altos não
podem ser sobrescritas. Isso não é suportado no SQL Server.
Unspecified Um isolamento diferente dos outros listados, mas não determinável.

As diferentes origens de dados ditam um nível de isolamento padrão. Por exemplo, o


nível de isolamento padrão para o SQL Server é ReadCommitted. Para alterar o nível de isola-
mento de uma transação, passe o nível de isolamento desejado para o construtor quando
a transação for construída:

sqlTrans := sqlCN.BeginTransaction(IsolationLevel.Serializable);

Pontos de salvamento
Em alguns casos, você pode precisar reverter apenas uma parte de uma transação. Supon-
do que a origem de dados suporta isso, você pode utilizar savepoints. Um savepoint é equi-
valente à seguinte instrução de Transact-SQL:

SAVE TRANSACTION
Processamento de transação 565

Para criar um savepoint, utilize o método Save( ) da transação, passando um nome


para o savepoint. Para reverter um savepoint, chame a função Rollback( ), passando o mes-
mo nome utilizado no método Save( ) como mostrado aqui:

sqlTrans := sqlcn.BeginTransaction;
try
// faça a atualização 1
sqlTrans.Save('mysave');
try
// faça a atualização 2
except
sqlTrans.Rollback('mysave');
end;
sqlDA.Update(dsCompany);
except
sqlTrans.Rollback;
raise;
end;

Observe que a exceção não deve ser novamente levantada depois da reversão (roll
back) do savepoint, o que faria com que a transação externa falhasse. Entretanto, deveria
haver um novo levantamento da reversão (roll back) externa.

Transações aninhadas
Para os data providers que as suportam, as transações aninhadas são uma alternativa
para savepoints. Uma transação aninhada é criada invocando o método Begin( ) do objeto
de transação como mostrado no seguinte código de esqueleto:

oledbTransOuter := oleDBConn.BeginTransaction;
try
oledbTransInner := oledbTransOuter.Begin;
// atualizações externas
try
// atualizações internas
oledbTransInner.Commit;
except
oledbTransInner.Rollback;
end;
oledbTransOuter.Commit;
except
oledbTransOuter.RollBack;
end;

Observe que as transações aninhadas não são suportadas pelo SqlTransaction, mas são
potencialmente com o OleDBTransaction. O suporte para o aninhamento de transação é
contingente no suporte do driver subjacente para essa capacidade.
566 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

As transações aninhadas podem ser emuladas utilizando savepoints. Embora não


possam ser individualmente confirmadas, mesmo com a transação aninhada, a transa-
ção externa determina se a operação inteira é confirmada.

DataSets fortemente tipificados


Até agora, todos os exemplos que utilizam uma classe DataSet têm utilizado o DataSet
não-tipificado. Esta seção discute o DataSet fortemente tipificado. Um DataSet fortemente
tipificado é uma classe DataSet que descende de DataSet. Ele contém DataTables e DataRows
descendentes para as tabelas que ele contém. Ele estende o DataSet fornecendo proprieda-
des, os eventos e métodos que são identificados e fortemente tipificados de acordo com
as tabelas que ele armazena.
DataSets fortemente tipificados normalmente são gerados pela IDE ou utilizando a
ferramenta de linha de comando xsd incluída dentro do .NET SDK.

Vantagens e desvantagens
Há várias vantagens em utilizar DataSets fortemente tipificados sobre os não-tipificados.
— Escrevendo o código é mais fácil de entender porque os nomes fortes fazem senti-
do e, com a conclusão de código, isso é ainda mais simplificado.
— Os desenvolvedores escrevem menos código total porque os membros identifica-
dos são acessados diretamente em vez de utilizar propriedades indexadoras.
— DataSets fortemente tipificados já contêm informações de esquema que aprimo-
ram o desempenho.
— A tipificação forte pode impedir erros de tempo de execução que poderiam ocor-
rer porque serão capturados em tempo de compilação.
— Poderia haver algum aumento de desempenho, já que pesquisas de strings para
instâncias de tabela e coluna são feitas somente uma vez para cada acesso de tabe-
la e coluna.

Também pode haver algumas desvantagens em utilizar DataSets fortemente tipifica-


dos. Essas são
— Há algumas desvantagens de desempenho. Isso poderia ser devido ao inchaço de
código causado pela presença de um grande número de classes e métodos se você
tiver muitas tabelas. Isso requer mais tempo JIT e espaço em disco.
— Sua manutenção será difícil se o esquema subjacente mudar, o que normalmente
ocorre. O DataSet fortemente tipificado precisará ser recriado.
— Mesmo a manutenção de um DataSet não-tipificado precisa ser feita quando hou-
ver alterações. Isso é provavelmente mais propenso a erro, uma vez que você deve
manualmente identificar e realizar atualizações no código para fazê-lo correspon-
der à nova estrutura do banco de dados. Com DataSets não-tipificados, você perde
erros de tempo de compilação e muitos erros não serão identificados até o tempo
DataSets fortemente tipificados 567

de execução. Com os DataSets tipificados autogerados, você só tem de lembrar-se


de regerá-los – você então tem a garantia de que a classe corresponde ao banco de
dados. E alterar o código que utiliza o DataSet tipificado deve ser mais fácil.

Criando DataSets fortemente tipificados


Esta seção orienta você no processo de criação de um DataSet fortemente tipificado den-
tro da IDE. Ela assume que você tem o banco de dados de exemplo Northwind instalado
em um banco de dados de SQL Server. Se não, os passos são os mesmos para qualquer
banco de dados.
1. Crie uma nova aplicação Windows Forms e salve-a em algum lugar.

2. Adicione um componente SqlConnection ao formulário. Ele será chamado


SqlConnection1.

3. Configure a propriedade SqlConnection1.ConnectionString como "server=XWING;databa-


se=Northwind; Trusted_Connection=Yes".

4. Adicione um SqlDataAdapter ao formulário. Atribua à sua propriedade SelectCom-


mand.CommandText a seguinte string: "SELECT * FROM customers; SELECT * FROM employees".
Além disso, configure a propriedade SelectCommand.Connection como SqlConnection1.
5. Pressione o botão (…) no editor de propriedade TableMappings no Object Inspector.
Isso carregará a caixa de diálogo Table Mappings (ver Figura 23.1).
A Figura 23.1 mostra o mapeamento da tabela origem, Table, para Customers. Faça o
mesmo com a tabela Employees mapeando a tabela origem Table1 como Employees.
Pressione OK para fechar a caixa de diálogo.

FIGURA 23.1 Caixa de diálogo Table Mappings.


568 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

6. Clique com o botão direito do mouse em SqlDataAdapter para invocar o menu local
e selecione Generate DataSet. A caixa de diálogo Generate DataSet aparecerá e deve
ser semelhante à mostrada na Figura 23.2.

7. Pressione OK para adicionar o DataSet, chamado DataSet1, ao designer de formulá-


rios. Isso gerará os arquivos DataSet1.xsd e DataSet1Unit.pas quando você pressio-
nar OK.

NOTA
O nome padrão dado ao DataSet de "DataSet1" tipificado não é apenas obtuso, mas também in-
teiramente não-descritivo dos dados que ele representa. Você deve alterá-lo de maneira corres-
pondente. Por exemplo, NorthWindDataSet poderia ter sido utilizado no exemplo anterior.

Examinando o arquivo .pas de um DataSet fortemente tipificado


Depois de criar o DataSet fortemente tipificado, dois arquivos são adicionados ao proje-
to. Um é o arquivo .xsd e o outro é um arquivo .pas com um nome de arquivo correspon-
dente. O arquivo .xsd contém a definição do esquema para o Dataset que é utilizado na
construção do código-fonte que define o DataSet fortemente tipificado. O código-fonte é
contido no arquivo .pas.
A Listagem 23.2 é um fragmento de um projeto que contém o DataSet fortemente ti-
pificado gerado ao realizar os passos supracitados.

FIGURA 23.2 Caixa de diálogo Generate DataSet.


DataSets fortemente tipificados 569

LISTAGEM 23.2 DataSet fortemente tipificado


1: DataSet1 = class(DataSet)
2: strict private
3: public
4: type
5: CustomerRowChangeEvent = class;
6: CustomerRowChangeEventHandler = procedure(sender: System.Object;
7: e: CustomerRowChangeEvent) of object;
8: CustomerRow = class;
9: strict private
10: public
11: type
12: EmployeeRowChangeEvent = class;
13: EmployeeRowChangeEventHandler = procedure(sender: System.Object;
14: e: EmployeeRowChangeEvent) of object;
15: EmployeeRow = class;
16: strict private
17: type
18: [System.Diagnostics.DebuggerStepThrough]
19: CustomerDataTable = class(DataTable,
➥System.Collections.IEnumerable) ...
20:
21: [System.Diagnostics.DebuggerStepThrough]
22: CustomerRow = class(DataRow) ...
23:
24: [System.Diagnostics.DebuggerStepThrough]
25: CustomerRowChangeEvent = class(EventArgs) ...
26:
27: [System.Diagnostics.DebuggerStepThrough]
28: EmployeeDataTable = class(DataTable,
➥System.Collections.IEnumerable) ...
29:
30: TArrayOfByte = array of Byte;
31: [System.Diagnostics.DebuggerStepThrough]
32: EmployeeRow = class(DataRow)...
33:
34: [System.Diagnostics.DebuggerStepThrough]
35: EmployeeRowChangeEvent = class(EventArgs) ...
36:
37: strict private
38: tableCustomer: CustomerDataTable;
39: tableEmployee: EmployeeDataTable;
40: public
41: constructor Create; overload;
42: strict protected
43: constructor Create(info: SerializationInfo;
44: context: StreamingContext); overload;
45: public
46: function get_Customer: CustomerDataTable;
570 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

LISTAGEM 23.2 Continuação


47: function get_Employee: EmployeeDataTable;
48: [System.ComponentModel.Browsable(False)]
49: [System.ComponentModel.DesignerSerializationVisibilityAttribute(
50: System.ComponentModel.DesignerSerializationVisibility.Content)]
51: property Customer: CustomerDataTable read get_Customer;
52: [System.ComponentModel.Browsable(False)]
53: [System.ComponentModel.DesignerSerializationVisibilityAttribute(
54: System.ComponentModel.DesignerSerializationVisibility.Content)]
55: property Employee: EmployeeDataTable read get_Employee;
56: function Clone: DataSet; override;
57: strict protected
58: function ShouldSerializeTables: Boolean; override;
59: function ShouldSerializeRelations: Boolean; override;
60: procedure ReadXmlSerializable(reader: XmlReader); override;
61: function GetSchemaSerializable: System.Xml.Schema.XmlSchema; override;
62: private
63: procedure InitVars;
64: strict private
65: procedure InitClass;
66: function ShouldSerializeCustomer: Boolean;
67: function ShouldSerializeEmployee: Boolean;
68: procedure SchemaChanged(sender: System.Object;
69: e: System.ComponentModel.CollectionChangeEventArgs);
70: end;

u Localize o código no CD: \Code\Chapter 23\Ex02\.

A Listagem 23.2 mostra a classe DataSet1 como ela aparece na IDE. DataSet1 descende
diretamente de DataSet e contém classes aninhadas, que são descendentes de DataTable,
DataRow e EventArgs. A listagem mostra qualquer classe aninhada como dobrada no IDE. As
várias propriedades internas, classes e eventos contidos em DataSet1 são específicos ao
tipo de acordo com as duas tabelas que foram incluídas na instrução SELECT utilizada para
construir o DataSet. As linhas 5–7, por exemplo, declaram o handler de evento que será
utilizado para os eventos RowChanged, RowChanging, RowDeleted e RowDeleting do cliente DataTa-
ble. As linhas 12–14 fazem o mesmo para o empregado DataTable.
Uma classe CustomerDataTable é declarada na linha 19. CustomerDataTable descende de
DataTable. Uma classe CustomerRow é declarada na linha 22 e ela descende de DataRow. Os des-
cendentes DataTable e DataRow correspondentes para a tabela employee são declarados nas
linhas 28 e 32, respectivamente. Os membros de instância do CustomDataTable e EmployeeDa-
taTable são declarados nas linhas 38–39.
Duas propriedades-chave do Dataset1 são mostradas nas linhas 51 e 55. Essas proprie-
dades se referem às instâncias DataTable para CustomerDataTable e EmployeeDataTable.
Examinando essa listagem, você deve ter uma idéia de como DataSets fortemente ti-
pificados são construídos de três classes DataTable, DataRow e DataRowChangeEvent fortemente
tipificadas e aninhadas para cada tabela contida no DataSet. Anteriormente, você deve
ter visto que se referir a uma tabela específica é mais limpo. Em vez do seguinte código,
DataSets fortemente tipificados 571

DataSet1.Tables['customer']

você utilizaria o seguinte:

DataSet1.Customer

Em breve, você verá como é muito mais fácil lidar com valores de coluna.
A Listagem 23.3 mostra a definição de um dos DataTables aninhados, especificamente
o EmployeeDataTable. O CustomerDataTable é semelhante em estrutura.

LISTAGEM 23.3 A definição de EmployeeDataTable


1: EmployeeDataTable = class(DataTable, System.Collections.IEnumerable)
2: strict private
3: columnEmployeeID: DataColumn;
4: columnLastName: DataColumn;
5: columnFirstName: DataColumn;
6: columnTitle: DataColumn;
7: columnTitleOfCourtesy: DataColumn;
8: columnBirthDate: DataColumn;
9: columnHireDate: DataColumn;
10: columnAddress: DataColumn;
11: columnCity: DataColumn;
12: columnRegion: DataColumn;
13: columnPostalCode: DataColumn;
14: columnCountry: DataColumn;
15: columnHomePhone: DataColumn;
16: columnExtension: DataColumn;
17: columnPhoto: DataColumn;
18: columnNotes: DataColumn;
19: columnReportsTo: DataColumn;
20: columnPhotoPath: DataColumn;
21: public
22: EmployeeRowChanged: EmployeeRowChangeEventHandler;
23: EmployeeRowChanging: EmployeeRowChangeEventHandler;
24: EmployeeRowDeleted: EmployeeRowChangeEventHandler;
25: EmployeeRowDeleting: EmployeeRowChangeEventHandler;
26: private
27: constructor Create; overload;
28: constructor Create(table: DataTable); overload;
29: public
30: function get_Count: Integer;
31: private
32: function get_EmployeeIDColumn: DataColumn;
33: function get_LastNameColumn: DataColumn;
34: function get_FirstNameColumn: DataColumn;
35: function get_TitleColumn: DataColumn;
36: function get_TitleOfCourtesyColumn: DataColumn;
37: function get_BirthDateColumn: DataColumn;
572 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

LISTAGEM 23.3 Continuação


38: function get_HireDateColumn: DataColumn;
39: function get_AddressColumn: DataColumn;
40: function get_CityColumn: DataColumn;
41: function get_RegionColumn: DataColumn;
42: function get_PostalCodeColumn: DataColumn;
43: function get_CountryColumn: DataColumn;
44: function get_HomePhoneColumn: DataColumn;
45: function get_ExtensionColumn: DataColumn;
46: function get_PhotoColumn: DataColumn;
47: function get_NotesColumn: DataColumn;
48: function get_ReportsToColumn: DataColumn;
49: function get_PhotoPathColumn: DataColumn;
50: public
51: function get_Item(index: Integer): EmployeeRow;
52: [System.ComponentModel.Browsable(False)]
53: property Count: Integer read get_Count;
54: private
55: property EmployeeIDColumn: DataColumn read get_EmployeeIDColumn;
56: property LastNameColumn: DataColumn read get_LastNameColumn;
57: property FirstNameColumn: DataColumn read get_FirstNameColumn;
58: property TitleColumn: DataColumn read get_TitleColumn;
59: property TitleOfCourtesyColumn: DataColumn
60: read get_TitleOfCourtesyColumn;
61: property BirthDateColumn: DataColumn read get_BirthDateColumn;
62: property HireDateColumn: DataColumn read get_HireDateColumn;
63: property AddressColumn: DataColumn read get_AddressColumn;
64: property CityColumn: DataColumn read get_CityColumn;
65: property RegionColumn: DataColumn read get_RegionColumn;
66: property PostalCodeColumn: DataColumn read get_PostalCodeColumn;
67: property CountryColumn: DataColumn read get_CountryColumn;
68: property HomePhoneColumn: DataColumn read get_HomePhoneColumn;
69: property ExtensionColumn: DataColumn read get_ExtensionColumn;
70: property PhotoColumn: DataColumn read get_PhotoColumn;
71: property NotesColumn: DataColumn read get_NotesColumn;
72: property ReportsToColumn: DataColumn read get_ReportsToColumn;
73: property PhotoPathColumn: DataColumn read get_PhotoPathColumn;
74: public
75: property Item[index: Integer]: EmployeeRow read get_Item;
76: procedure AddEmployeeRow(row: EmployeeRow); overload;
77: function AddEmployeeRow(LastName: string; FirstName: string;
78: Title: string; TitleOfCourtesy: string; BirthDate: System.DateTime;
79: HireDate: System.DateTime; Address: string; City: string;
80: Region: string; PostalCode: string; Country: string;
81: HomePhone: string; Extension: string; Photo: array of Byte;
82: Notes: string; ReportsTo: Integer; PhotoPath: string):
83: EmployeeRow; overload;
84: function FindByEmployeeID(EmployeeID: Integer): EmployeeRow;
85: function GetEnumerator: System.Collections.IEnumerator;
DataSets fortemente tipificados 573

LISTAGEM 23.3 Continuação


86: function Clone: DataTable; override;
87: strict protected
88: function CreateInstance: DataTable; override;
89: private
90: procedure InitVars;
91: strict private
92: procedure InitClass;
93: public
94: function NewEmployeeRow: EmployeeRow;
95: strict protected
96: function NewRowFromBuilder(builder: DataRowBuilder): DataRow; override;
97: function GetRowType: System.Type; override;
98: procedure OnRowChanged(e: DataRowChangeEventArgs); override;
99: procedure OnRowChanging(e: DataRowChangeEventArgs); override;
100: procedure OnRowDeleted(e: DataRowChangeEventArgs); override;
101: procedure OnRowDeleting(e: DataRowChangeEventArgs); override;
102: public
103: procedure RemoveEmployeeRow(row: EmployeeRow);
104: end;

u Localize o código no CD: \Code\Chapter 23\Ex02\.

Como o DataSet fortemente tipificado, o DataTable interno é outra classe contendo


membros fortemente tipificados que correspondem diretamente ao esquema da tabela
que ele representa. Algumas propriedades e métodos public chave estão contidos em Da-
taTables descendentes e esses são descritos na Tabela 23.3.

TABELA 23.3 Propriedades/métodos de DataTable descendente


Método/propriedade Descrição
Item Essa propriedade indexada retorna o EmployeeRow para o índice
especificado (linha 75)
AddEmployeeRow( ) Há duas versões sobrecarregadas desse método (linhas 76 e 77). A
procedure aceita um EmployeeRow como um parâmetro e o adiciona
ao DataTable subjacente. A função aceita como parâmetros os valores
para cada coluna e realiza a inserção. Ela também retorna a instância
EmployeeRow da linha adicionada recentemente.
Count Essa propriedade retorna o número de linhas contido pelo
EmployeeDataTable. Ela é declarada na linha 53.
FindByEmployeeID( ) Essa função retorna o EmployeeRow para o EmployeeID especificado. Ela
é declarada na linha 84.
NewEmployeeRow( ) Essa função retorna uma nova instância de uma classe EmployeeRow.
Ela é declarada na linha 94.
RemoveEmployeeRow( ) Essa procedure remove a linha especificada pelo parâmetro
EmployeeRow. Ela é declarada na linha 103.
574 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

As propriedades e métodos descritos na Tabela 23.3 são criados para cada tabela con-
tida dentro do DataSet. Os nomes corresponderiam a cada tabela.
Para cada tabela, também é criado um DataRow fortemente tipificado. A Listagem 23.4
mostra a definição do descendente EmployeeRow.

LISTAGEM 23.4 A definição de EmployeeRow


1: EmployeeRow = class(DataRow)
2: strict private
3: tableEmployee: EmployeeDataTable;
4: private
5: constructor Create(rb: DataRowBuilder);
6: public
7: function get_EmployeeID: Integer;
8: function get_LastName: string;
9: function get_FirstName: string;
10: function get_Title: string;
11: function get_TitleOfCourtesy: string;
12: function get_BirthDate: System.DateTime;
13: function get_HireDate: System.DateTime;
14: function get_Address: string;
15: function get_City: string;
16: function get_Region: string;
17: function get_PostalCode: string;
18: function get_Country: string;
19: function get_HomePhone: string;
20: function get_Extension: string;
21: function get_Photo: TArrayOfByte;
22: function get_Notes: string;
23: function get_ReportsTo: Integer;
24: function get_PhotoPath: string;
25: procedure set_EmployeeID(Value: Integer);
26: procedure set_LastName(Value: string);
27: procedure set_FirstName(Value: string);
28: procedure set_Title(Value: string);
29: procedure set_TitleOfCourtesy(Value: string);
30: procedure set_BirthDate(Value: System.DateTime);
31: procedure set_HireDate(Value: System.DateTime);
32: procedure set_Address(Value: string);
33: procedure set_City(Value: string);
34: procedure set_Region(Value: string);
35: procedure set_PostalCode(Value: string);
36: procedure set_Country(Value: string);
37: procedure set_HomePhone(Value: string);
38: procedure set_Extension(Value: string);
39: procedure set_Photo(Value: TArrayOfByte);
40: procedure set_Notes(Value: string);
41: procedure set_ReportsTo(Value: Integer);
42: procedure set_PhotoPath(Value: string);
43: property EmployeeID: Integer read get_EmployeeID write set_EmployeeID;
44: property LastName: string read get_LastName write set_LastName;
DataSets fortemente tipificados 575

LISTAGEM 23.4 Continuação


45: property FirstName: string read get_FirstName write set_FirstName;
46: property Title: string read get_Title write set_Title;
47: property TitleOfCourtesy: string read get_TitleOfCourtesy
➥write set_TitleOfCourtesy;
48: property BirthDate: System.DateTime read get_BirthDate
➥write set_BirthDate;
49: property HireDate: System.DateTime read get_HireDate write set_HireDate;
50: property Address: string read get_Address write set_Address;
51: property City: string read get_City write set_City;
52: property Region: string read get_Region write set_Region;
53: property PostalCode: string read get_PostalCode write set_PostalCode;
54: property Country: string read get_Country write set_Country;
55: property HomePhone: string read get_HomePhone write set_HomePhone;
56: property Extension: string read get_Extension write set_Extension;
57: property Photo: TArrayOfByte read get_Photo write set_Photo;
58: property Notes: string read get_Notes write set_Notes;
59: property ReportsTo: Integer read get_ReportsTo write set_ReportsTo;
60: property PhotoPath: string read get_PhotoPath write set_PhotoPath;
61: function IsTitleNull: Boolean;
62: procedure SetTitleNull;
63: function IsTitleOfCourtesyNull: Boolean;
64: procedure SetTitleOfCourtesyNull;
65: function IsBirthDateNull: Boolean;
66: procedure SetBirthDateNull;
67: function IsHireDateNull: Boolean;
68: procedure SetHireDateNull;
69: function IsAddressNull: Boolean;
70: procedure SetAddressNull;
71: function IsCityNull: Boolean;
72: procedure SetCityNull;
73: function IsRegionNull: Boolean;
74: procedure SetRegionNull;
75: function IsPostalCodeNull: Boolean;
76: procedure SetPostalCodeNull;
77: function IsCountryNull: Boolean;
78: procedure SetCountryNull;
79: function IsHomePhoneNull: Boolean;
80: procedure SetHomePhoneNull;
81: function IsExtensionNull: Boolean;
82: procedure SetExtensionNull;
83: function IsPhotoNull: Boolean;
84: procedure SetPhotoNull;
85: function IsNotesNull: Boolean;
86: procedure SetNotesNull;
87: function IsReportsToNull: Boolean;
88: procedure SetReportsToNull;
89: function IsPhotoPathNull: Boolean;
90: procedure SetPhotoPathNull;
91: end;
576 Capítulo 23 Trabalhando com transações e DataSets fortemente tipificados

u Localize o código no CD: \Code\Chapter 23\Ex02\.

O DataRow descendente declara uma propriedade de acesso tipificada para cada colu-
na contida na linha. As linhas 7–42 são os métodos getter/setter para as várias proprieda-
des de coluna. As propriedades acessoras são definidas nas linhas 43–60. Para cada pro-
priedade é criada uma função para determinar se a linha é ou não Null. Uma procedure
para configurar o valor de coluna como Null também é criada para cada coluna.

Utilizando o DataSet fortemente tipificado


Utilizar um DataSet fortemente tipificado é bastante simples. Uma das vantagens reais de
lidar com eles está dentro da IDE, é porque o recurso code-completion da IDE reconhece
os nomes tipificados. Esses são os tipos de aprimoramentos de produtividade que os de-
senvolvedores apreciam.

Adicionando uma linha


Para adicionar uma linha, você pode recuperar um novo DataRow a partir de um DataTable,
configurar seus valores de coluna e então passá-lo para a procedure AddEmployeeRow( ),
como mostrado aqui:

var
er: DataSet1.EmployeeRow;
begin
er := Dataset11.Employee.NewEmployeeRow;
er.LastName := 'Pacheco';
er.FirstName := 'Xavier';
er.Title := 'Programmer';
...
DataSet11.Employee.AddEmployeeRow(er);
end;

Alternativamente, você pode chamar a função AddEmployeeRow( ), que aceita cada va-
lor de coluna como um parâmetro e retorna uma instância do EmployeeRow recentemente
adicionado.

NOTA
Quando escrevíamos este livro, tive de mover a declaração dos tipos EmployeeRow e CustomerRow
para uma seção pública dentro do DataSet fortemente tipificado. Isso foi baseado na versão do
Delphi for .NET. Isso foi informado como um defeito e deve ser corrigido.

Editando uma linha


Editar uma linha é relativamente simples, como é mostrado no próximo código:

var
er: Dataset1.EmployeeRow;
begin
DataSets fortemente tipificados 577

er := DataSet11.Employee.Item[0];
er.FirstName := 'Bob';
er.LastName := 'The Builder';

Todos os métodos, como BeginEdit( ), EndEdit( ) e CancelEdit( ), estão disponíveis


como uma conseqüência de tê-los herdado da classe pai DataSet.

Excluindo uma linha


Excluir uma linha envolve utilizar a procedure RemoveTableNameRow( ), que aceita um Data-
Row como um parâmetro, como é mostrado aqui:

DataSet11.Employee.RemoveEmployeeRow(DataSet11.Employee.Item[0]);

Localizando uma linha


Localizar uma linha envolve utilizar a função FindTableNameByID( ), como mostrado a se-
guir. Essa função retorna o DataRow localizado.

er := DataSet11.Employee.FindByEmployeeID(Convert.ToInt32(TextBox1.Text));

No exemplo anterior, se o valor da chave primária não pode ser localizado, nil é re-
tornado.

Dados hierárquicos
Se o DataSet contiver dados relacionais na forma de classes DataRelation, as classes DataRow
descendentes conterão dois métodos adicionais. Esses métodos são fornecidos para per-
mitir a navegação pelos relacionamentos hierárquicos. Os métodos são GetChildTableNa-
meRows( ) e GetParentTableNameRow( ).
A função GetChildTableNameRows( ) retorna um array de objetos ChildTableNameRow.
A função GetParentTableNameRow( ) retorna o ParentTableNameRow da linha filha.

DICA
É recomendado examinar a implementação dos métodos gerados dentro de um DataSet forte-
mente tipificado, que fornecerá um melhor entendimento do que está acontecendo “sob o capô”,
para só então comentá-los.
NESTE CAPÍTULO
— Visão geral da arquitetura CAPÍTULO 24
— Classes Borland Data Provider

— Designers dentro da IDE O Borland Data


Provider

C omo parte dos conjuntos de ferramentas Delphi


for.NET e C# Builder, a Borland incluiu sua própria
implementação das interfaces Data Provider centrais.
Este capítulo discute o Borland Data Provider (BDP) e os
componentes que compõem o BDP. Ele demonstra o uso
desses componentes para você desenvolver suas próprias
aplicações.NET.

Visão geral da arquitetura


A Borland Data Provider Architecture é mais bem
descrita como uma arquitetura aberta que fornece as
interfaces necessárias para integração independente. Essa
arquitetura é representada na Figura 24.1.
Implementando as interfaces básicas, as origens de
dados de qualquer tipo podem ser integradas na
arquitetura BDP. O provider com o qual os componentes
BDP estarão trabalhando é determinado por sua entrada
na propriedade CommandText do componente BdpConnection.
Os detalhes de implementação são completamente
ocultos dos usuários dos componentes BDP.
Ocultar os detalhes de implementação tem as
seguintes vantagens-chave:
— Os usuários podem codificar para um modelo
unificado.
— O código escrito para uma origem de dados
funcionará em outra origem de dados.
— Os tipos de dados são mapeados pelo BDP para
tipos de dados .NET específicos.
— Não há nenhuma camada Interop COM.
Classes Borland Data Provider 579

FIGURA 24.1 A arquitetura Borland Data Provider.

Enquanto o .NET Framework é fornecido com múltiplos data providers para acessar
diferentes origens de dados, o Borland Data Provider é um único provedor unificado
pelo qual múltiplas origens de dados são acessadas. Atualmente, os seguintes bancos de
dados são suportados pelo BDP:
— Interbase

— Oracle

— IBM DB2

— Microsoft SQL

As seções a seguir demonstram a utilização das várias classes BDP.

Classes Borland Data Provider


As classes BDP correspondem a outras classes data provider diretamente. As classes BDP
são definidas no namespace Borland.Data.Provider. De fato, muito do que você leu nos ca-
pítulos que discutem o ADO.NET utilizou apenas as classes a partir do namespace
System.Data.SQLClient. Essa cobertura referia-se igualmente a outras classes Data Providers
e o mesmo vale para as classes BDP. Você já deve saber utilizar a maioria dessas classes.
Portanto, este capítulo introduz cada classe e fornece um exemplo de seu uso. Os deta-
lhes da utilização dessas classes não são repetidos. A Tabela 24.1 mostra as classes corres-
pondentes da Borland, os data providers SqlClient e OleDB.
580 Capítulo 24 O Borland Data Provider

TABELA 24.1 Classes Data Provider


Classe BDP Classe SqlClient Classe OleDB
BdpCommand SqlCommand OleDBCommand
BdpCommandBuilder SqlCommandBuilder OleDbCommandBuilder
BdpConnection SqlConnection OleDbConnection
BdpDataAdapter SqlDataAdapter OleDBDataAdapter
BdpDataReader SqlDataReader OleDbDataReader
BdpTransaction SqlTransaction OleDbTransaction

NOTA
Na demonstração dos componentes a seguir, utilizarei prioritariamente atribuição de proprieda-
des em tempo de execução a fim de que as várias atribuições de propriedades sejam claras. Essas
propriedades podem ser configuradas em tempo de projeto e ao fazer isso utilizaremos os vários
editores de propriedades, que podem tornar o desenvolvimento com esses componentes muito
mais fácil.

BdpConnection
A classe BdpConnection é utilizada para conectar-se a bancos de dados. Antes de abrir uma
conexão utilizando a classe BdpConnection, você deve especificar uma entrada válida para
sua propriedade ConnectionString. Utilize o método Open( ) para abrir uma conexão de
banco de dados. Da mesma forma, utilize o método Close( ) para fechar uma conexão
de banco de dados. A Listagem 24.1 ilustra como abrir e fechar uma classe BdpConnection.

LISTAGEM 24.1 Estabelecendo uma conexão com BdpConnection


1: const
2: cnStr = 'assembly=Borland.Data.Interbase, Version=1.5.0.0, '+
3: 'Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;
➥vendorclient=gds32.dll;'+
4: 'database=C:\Program Files\Common Files\Borland
➥Shared\Data\EMPLOYEE.GDB;'+
5: 'provider=Interbase;username=sysdba;password=masterkey';
6: procedure TWinForm.btnOpen_Click(sender: System.Object;
7: e: System.EventArgs);
8: var
9: bdpCn: BdpConnection;
10: begin
11: bdpCn := BdpConnection.Create(cnStr);
12: Include(bdpCn.StateChange, Self.BdpCn_StateChange);
13: bdpCn.Open;
14: bdpCn.Close;
15: end;

u Localize o código no CD: \Code\Chapter 24\Ex01\.


Classes Borland Data Provider 581

As linhas 2–5 representam a string de conexão com um banco de dados Interbase. A


atribuição ConnectionString é tratada implicitamente pela chamada do construtor BdpCon-
nection.Create( ) (linha 11). A linha 12 cria e também demonstra a adição de um handler
de evento ao evento StateChange. Tanto a atribuição ConnectionString como o evento Sta-
teChange poderiam ter sido feitos em tempo de projeto pelo Object Inspector. A última se-
ção deste capítulo demonstra o uso do Connections Editor para criar uma conexão com
o BdpConnection em tempo de projeto.
Ao utilizar BdpConnection, você sempre deve corresponder a uma chamada para
Open( ) com uma chamada para Close( ) já que isso não é feito implicitamente. O
bdpCn_StateChange( ) handler de evento executa uma linha de código:

ListBox1.Items.Add(e.CurrentState)

Esse código copia um dos dois estados em que o BdpConnection pode estar. Esse é Open
ou Closed.
A utilização geral da classe BdpConnection é semelhante à da classe SqlConnection discu-
tida em capítulos anteriores.

BdpCommand
A classe BdpCommand é utilizada para executar instruções SQL contra a origem de dados sub-
jacente. Isso inclui a execução de stored procedures. A Listagem 24.2 ilustra a utilização
da classe BdpCommand.

LISTAGEM 24.2 Utilizando a classe BdpCommand


1: const
2: cnStr = 'assembly=Borland.Data.Interbase, Version=1.5.0.0, '+
3: 'Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;
➥vendorclient=gds32.dll;'+
4: 'database=C:\Program Files\Common Files\Borland
➥Shared\Data\EMPLOYEE.GDB;'+
5: 'provider=Interbase;username=sysdba;password=masterkey';
6: procedure TWinForm.btnOpen_Click(sender: System.Object;
7: e: System.EventArgs);
8: var
9: bdpCn: BdpConnection;
10: bdpCmd: BdpCommand;
11: bdpRdr: BdpDataReader;
12: begin
13: bdpCn := BdpConnection.Create(cnStr);
14: Include(bdpCn.StateChange, Self.BdpCn_StateChange);
15:
16: bdpCn.Open;
17: try
18: bdpCmd := bdpCn.CreateCommand;
19: bdpCmd.CommandText := 'SELECT * FROM customer';
20: bdpRdr := bdpCmd.ExecuteReader;
582 Capítulo 24 O Borland Data Provider

LISTAGEM 24.2 Continuação


21: finally
22: bdpCn.Close;
23: end;
24: end;

u Localize o código no CD: \Code\Chapter 24\Ex02\.

O BdpConnection deve ser aberto antes que você possa executar qualquer um dos méto-
dos BdpCommand que acessam o banco de dados. Nesse exemplo, o método BdpCommand.Execu-
teReader( ) é chamado, o que retorna um BdpDataReader. A próxima seção discute a classe
BdpDataReader. Os comandos adicionais que podem ser executados contra a origem de da-
dos subjacente são:
— ExecuteNonQuery( ) – Executa instruções que não retornam um resultset. Em vez
disso, esse método retorna o número de registros afetados pela instrução.
— ExecuteReader( ) – Executa instruções que retornam um resultset que é retornado
em um BdpDataReader.
— ExecuteScalar( ) – Executa uma consulta e retorna o valor da primeira coluna da
primeira linha do resultset.
— Prepare( ) – Cria (prepara) uma versão compilada do comando na origem de
dados.

A utilização geral da classe BdpCommand é semelhante àquela da classe SqlCommand, que é


discutida no Capítulo 23.

BdpDataReader
A classe BdpDataReader é uma representação de cursor, somente leitura, somente para fren-
te, de um resultset retornado do método BdpCommand.ExecuteReader( ). O BdpDataReader não é
criado explicitamente com um construtor. A Listagem 24.3 mostra como fazer loop pelas
linhas BdpDataReader e adicionar seus valores a um controle ListBox.

LISTAGEM 24.3 Utilizando a classe BdpDataReader


1: const
2: cnStr = 'assembly=Borland.Data.Interbase, Version=1.5.0.0, '+
3: 'Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;
➥vendorclient=gds32.dll;'+
4: 'database=C:\Program Files\Common Files\Borland
➥Shared\Data\EMPLOYEE.GDB;'+
5: 'provider=Interbase;username=sysdba;password=masterkey';
6: procedure TWinForm.btnOpen_Click(sender: System.Object;
7: e: System.EventArgs);
8: var
9: bdpCn: BdpConnection;
Classes Borland Data Provider 583

LISTAGEM 24.3 Continuação


10: bdpCmd: BdpCommand;
11: bdpRdr: BdpDataReader;
12: s: String;
13: begin
14: bdpCn := BdpConnection.Create(cnStr);
15: Include(bdpCn.StateChange, Self.BdpCn_StateChange);
16:
17: bdpCn.Open;
18: try
19: bdpCmd := bdpCn.CreateCommand;
20: bdpCmd.CommandText := 'SELECT * FROM country';
21: bdpRdr := bdpCmd.ExecuteReader;
22:
23: while bdpRdr.Read do
24: begin
25: s := bdpRdr['country'].ToString+' '+bdpRdr['currency'].ToString;
26: ListBox2.Items.Add(s);
27: end;
28:
29: finally
30: bdpCn.Close;
31: end;
32: end;

u Localize o código no CD: \Code\Chapter 24\Ex03\.

Este exemplo lê os registros da tabela country do banco de dados Employes.gdb. A li-


nha 21 invoca a função ExecuteReader( ), que atribui o BdpDataReader resultante à variável
bdpRdr. As linhas 23–27 fazem loop por bdpRdr e atribuem os valores de coluna de cada li-
nha à ListBox2. A utilização geral da classe BdpDataReader é semelhante à utilização da classe
SqlDataReader, que é discutida nos capítulos anteriores.

BdpDataAdapter
A classe BdpDataAdapter é o condutor entre o DataSet em memória e a origem de dados cor-
respondente. Ela contém todos os métodos necessários para manipular os dados que ela
mantém e para postar alterações na origem de dados. BdpDataAdapter deve ter uma instru-
ção válida em sua propriedade SelectCommand antes que possa ser utilizada para preencher
um DataSet. A Listagem 24.4 ilustra a utilização do BdpDataAdapter para recuperar a tabela
Customer do banco de dados Interbase Employee.gdb.

LISTAGEM 24.4 Utilizando a classe BdpDataAdapter


1: const
2: cmdStr = 'SELECT * FROM customer';
3:
584 Capítulo 24 O Borland Data Provider

LISTAGEM 24.4 Continuação


4: procedure TWinForm.btnOpen_Click(sender: System.Object;
5: e: System.EventArgs);
6: var
7: bdpCn: BdpConnection;
8: bdpDA: BdpDataAdapter;
9: ds: DataSet;
10: begin
11: bdpCn := BdpConnection.Create(cnStr);
12: bdpDA := BdpDataAdapter.Create(cmdStr, bdpCn);
13: ds := DataSet.Create;
14: bdpDA.Fill(ds);
15: try
16: DataGrid1.DataSource := ds.Tables['Table'];
17: finally
18: bdpCn.Close;
19: end;
20: end;

u Localize o código no CD: \Code\Chapter 24\Ex04\.

Este exemplo utiliza a mesma string de conexão que os exemplos anteriores utili-
zam. A linha 12 mostra onde o BdpDataAdapter é criado utilizando a constante cmdStr como
o parâmetro SelectCommand. Ela também passa o objeto BdpConnection, bdpCn. A linha 14 pre-
enche um DataSet com os valores consultados a partir do banco de dados e vincula a tabe-
la resultante à DataGrid1. A utilização geral da classe BdpDataAdapter segue estritamente a
classe SqlDataAdapter discutida nos capítulos anteriores da Parte IV.

BdpParameter/BpdParameterCollection
A propriedade BdpCommand.Parameters é do tipo BdpParameterCollection. Esse tipo é definido
no namespace Borland.Data.Common. A coleção Parameters mantém instâncias de objetos
BdpParameter. Cada instância BdpParameter refere-se a um parâmetro na instrução SQL que o
BdpComamand representa. Por exemplo, considere a seguinte stored procedure:

CREATE PROCEDURE CustOrdersOrders @CustomerID nchar(5)


AS
SELECT OrderID, OrderDate,RequiredDate,ShippedDate
FROM Orders
WHERE CustomerID = @CustomerID
ORDER BY OrderID
GO

Essa stored procedure aceita um único parâmetro @CustomerID. A Listagem 24.5 mos-
tra como utilizar a classe BdpParameter para fornecer um valor para esse parâmetro ao exe-
cutar essa procedure armazenada utilizando um objeto BdpCommand.
Classes Borland Data Provider 585

LISTAGEM 24.5 Utilizando a classe BdpParameter


1: const
2: cnStr = 'assembly=Borland.Data.Mssql, Version=1.5.0.0, Culture=neutral,'+
3: 'PublicKeyToken=91d62ebb5b0d1b1b;vendorclient=sqloledb.dll;'+
4: 'osauthentication=True;database=Northwind;username=;'+
5: 'hostname=localhost;password=sa;provider=MSSQL';
6:
7: procedure TWinForm1.Button1_Click(sender: System.Object;
8: e: System.EventArgs);
9: var
10: bdpCn: BdpConnection;
11: bdpCmd: BdpCommand;
12: bdpParm: BdpParameter;
13: bdpDA: BdpDataAdapter;
14: ds: DataSet;
15: begin
16: bdpCn := BdpConnection.Create(cnStr);
17: bdpCmd := BdpCommand.Create;
18: bdpCmd.Connection := bdpCn;
19: bdpCmd.CommandText := 'CustOrdersOrders';
20: bdpCmd.ParameterCount := 1;
21: bdpCmd.CommandType := CommandType.StoredProcedure;
22: bdpCn.Open;
23: try
24: bdpCmd.Prepare;
25: bdpParm := BdpParameter.Create('@CustomerID', dbType.StringFixedLength,
26: 5);
27: bdpParm.Value := 'ANTON';
28: bdpCmd.Parameters.Add(bdpParm);
29: bdpDA := BdpDataAdapter.Create(bdpCmd);
30: ds := DataSet.Create;
31: bdpDA.Fill(ds);
32: DataGrid1.DataSource := ds;
33: finally
34: bdpCn.Close;
35: end;
36: end;

u Localize o código no CD: \Code\Chapter 24\Ex05\.

Na Listagem 24.5, as linhas 17–21 configuram o objeto BdpCommand. As linhas 25–26


criam uma instância BdpParameter. Seu construtor Create( ) aceita o nome do parâmetro,
bem como o tipo do parâmetro e o tamanho, se necessário. O valor do parâmetro é confi-
gurado na linha 27 e adicionado à coleção Parameters do BdpCommand na linha 28. O código
restante mostra como a instância BdpDataAdapter invoca a stored procedure pela classe
BdpCommand. Utilizar a classe BpdParmeter é semelhante a utilizar a SqlParameter e outras clas-
ses data provider como ilustrado nos capítulos anteriores.
586 Capítulo 24 O Borland Data Provider

BdpTransaction
A classe BdpTransaction é que estabelece um limite dentro do qual ocorre um grupo de ope-
rações de banco de dados. O método BeginTransaction( ) da classe BdpConnection retorna
uma instância da classe BdpTransaction. As transações são finalizadas chamando BdpTran-
saction.Commit( ) ou BdpTranaction.RollBack( ). O primeiro é chamado quando o conjunto
de transações for bem-sucedido. O último é chamado quando houver uma falha no pro-
cessamento da operação. A Listagem 24.6 mostra um template para executar transações.

LISTAGEM 24.6 Exemplo de transação


1: procedure TWinForm.Button1_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: bdpCn: BdpConnection;
5: bdpTrans: BdpTransaction;
6: begin
7: bdpCn := BdpConnection.Create(cnStr);
8: bdpCn.Open;
9: try
10: bdpTrans := bdpCn.BeginTransaction;
11: try
12: // Faz seu material
13: bdpTrans.Commit;
14: except
15: bdpTrans.Rollback;
16: raise;
17: end;
18: finally
19: bdpCn.Close;
20: end;
21: end;

u Localize o código no CD: \Code\Chapter 24\Ex06\.

Na linha 10, uma transação é criada. Qualquer atividade de banco de dados que
ocorra entre a linha 10 e a linha 13 ocorreria dentro do contexto da transação e seria con-
firmada na linha 13. Se ocorrer uma falha (uma exceção é levantada), o bloco except será
invocado e a transação reverterá todas as ações pendentes.

Designers dentro da IDE


Agora que você viu como atribuir programaticamente várias propriedades aos compo-
nentes Borland Data Provider, esta seção demonstra os vários designers que estão dispo-
níveis dentro do IDE.

O Connections Editor
Designers dentro da IDE 587

A caixa de diálogo Connections Editor é onde você gerencia conexões para várias origens
de dados que são acessadas por componentes Borland Data Provider. A caixa de diálogo
Connections Editor é mostrada na Figura 24.2.
A partir da caixa de diálogo Connections Editor, você pode adicionar, editar e
remover conexões com as origens de dados. Você também pode testar a conexão de-
pois de criá-la. As conexões criadas dentro dessa caixa de diálogo são armazenadas
no arquivo BdpConnection.xml, que reside no diretório \Bin onde Delphi for .NET foi
instalado.

O Command Text Editor


O Command Text Editor é utilizado para construir o texto do comando para a proprieda-
de BdpCommand.CommandText. Essa caixa de diálogo é mostrada na Figura 24.3.
Examinando a Figura 24.3, a lista Tables é onde você seleciona a tabela em que
você quer realizar uma consulta. A lista Columns é onde você seleciona as colunas
que quer que sejam retornadas para o DataSet. Quando você clicar no botão Generate
SQL, a instrução SQL será gerada com base no botão de opção selecionado na lista de
seleção Command Type. A guia Preview Data permite visualizar os resultados de sua
instrução SQL.

O Parameter Collection Editor


A caixa de diálogo Parameter Collection Editor permite definir o objeto BdpParameters
para uma consulta parametrizada. Essa caixa de diálogo é mostrada na Figura 24.4.
Por meio dessa caixa de diálogo, você pode criar novos parâmetros e configurar as
propriedades que normalmente configuraria programaticamente como mostrado na Lis-
tagem 24.5.

FIGURA 24.2 A caixa de diálogo Connections Editor.


588 Capítulo 24 O Borland Data Provider

FIGURA 24.3 A caixa de diálogo Command Text Editor.

FIGURA 24.4 A caixa de diálogo Parameter Collection Editor.

Caixa de diálogo Data Adapter Configuration


A caixa de diálogo Data Adapter Configuration é utilizada para gerar as instruções SELECT,
UPDATE, INSERT e DELETE para as propriedades SelectCommand, UpdateCommand, InsertCommand e De-
leteCommands da classe BdpDataAdapter. Essa caixa de diálogo é mostrada na Figura 24.5.
Você pode ver na Figura 24.5 que essa caixa de diálogo é semelhante à Command
Text Editor, exceto que a caixa de diálogo Data Adapter Configuration contém uma guia
para cada instrução SQL gerada. Além disso, a caixa de diálogo Data Adapter Configura-
tion tem uma DataSet Table em que você pode especificar um controle DataSet que será
preenchido pelo BdpDataAdapter (ver Figura 24.6).
Designers dentro da IDE 589

FIGURA 24.5 A caixa de diálogo Data Adapter Configuration.

FIGURA 24.6 Opções DataSet na caixa de diálogo BdpDataAdapter.

Há três opções que podem ser selecionadas a partir da guia DataSet. Estes são:
— New DataSet – Especifica que um novo objeto DataSet será adicionado ao formulá-
rio que conterá os dados recuperados pelo DataAdapter.
— Existing DataSet – Especifica que um DataSet existente (que você seleciona na
DropDownList) será preenchido pelo BdpDataAdapter.
— None – Nenhum DataSet é especificado.
PÁGINA EM BRANCO
PARTE V:
CAPÍTULO 25 DESENVOLVIMENTO
PARA INTERNET COM
O ASP.NET
Fundamentos do 25 Fundamentos do ASP.NET

ASP.NET 26 Criando páginas Web com


o ASP.NET

27 Construindo aplicações
ASP.NET com acesso a
Q uando se trata de desenvolver soluções empresariais banco de dados
baseadas na Web, o ASP.NET é inquestionavelmente a
tecnologia mais robusta e mais fácil de utilizar nas 28 Criando Web Services
mesas dos desenvolvedores atualmente. O ASP.NET não 29 .NET Remoting e Delphi
é apenas simples de trabalhar de um ponto de vista de
desenvolvimento, mas também permite que os 30 .NET Remoting em ação
desenvolvedores criem aplicações para Internet
31 Tornando aplicações
altamente complexas, funcionalmente ricas, interativas ASP.NET seguras
e seguras que nenhuma outra ferramenta pode realizar
com tal facilidade. Seu poder, robustez e facilidade de 32 Distribuição e configuração
desenvolvimento e distribuição tornam realidade o do ASP.NET
sonho de um desenvolvedor. As pessoas que entendem
33 Cache e gerenciamento de
o Rapid Application Development (RAD) conhecem, em estados em aplicações
última instância, que o usuário final/cliente é quem se ASP.NET
beneficia dele. O ASP.NET se resume ao RAD de
aplicações Web. Com o ASP.NET é extremamente fácil 34 Desenvolvendo controles
desenvolver aplicações Web e isso se traduz em ciclos de ASP.NET Server
lançamentos mais rápidos, mais recursos e softwares personalizados
mais baratos. No topo disso, a distribuição e
configuração do .NET é brinquedo de criança NESTE CAPÍTULO
comparada a outras soluções de desenvolvimento Web. — Tecnologias Web – como elas
Os capítulos nesta seção do livro fornecem orientação funcionam
para tirarmos proveito dessa tecnologia que permite
— ASP.NET – como ele funciona
utilizar o Delphi for .NET. É provável que o ASP.NET
seja o recurso mais estimulante da estratégia — Classes ASP.NET
Microsoft .NET.

Tecnologias Web – como elas


funcionam
Em termos gerais, a Web funciona em um modelo
cliente-servidor e sem estado (stateless). A comunicação
ocorre entre um cliente e um servidor sobre o protocolo
de HTTP.
592 Capítulo 25 Fundamentos do ASP.NET

Visão geral sobre o protocolo HTTP


HTTP significa “Hypertext Transfer Protocol”. Ele é baseado em pacotes de texto e define
como ocorre a comunicação entre navegadores Web, como Internet Explorer e Opera, e
servidores Web, como o Internet Information Server do Microsoft (IIS) e o Apache. Deta-
lhes sobre HTTP podem ser obtidos a partir da RFC 2616 em ftp://ftp.isi.edu/in-notes/
rfc2616.txt.
Sob o HTTP, os navegadores se comunicam com servidores utilizando um modelo
request/response. O cliente ou navegador envia uma request (requisição) de uma página
Web para um servidor Web, que localiza ou cria dinamicamente a página Web e envia
uma Response (resposta) de volta ao navegador, que então exibe a página para o usuário fi-
nal. Esse modelo de comunicação simples é representado na Figura 25.1.
O HTTP é um protocolo stateless. Isso significa que cada troca de requisição/respos-
ta, ou transação, é independente de transações anteriores ou subseqüentes. Ao contrário
dos modelos de duas camadas em aplicações distribuídas, nenhuma conexão constante
mantém informações de estado.
Essa descrição é provavelmente a forma mais simples de comunicação da Internet e
provavelmente a mais popular. É uma forma estática de comunicação via Internet, que em
muitos casos é muito adequada. Inumeráveis páginas Web na Internet fornecem apenas o
conteúdo e os usuários não precisam interagir com elas. Entretanto, onde o processamen-
to dinâmico e interatividade são necessários, esse modelo simplesmente não basta.
ASP.NET (e algumas outras tecnologias) utiliza a maneira como o HTTP opera para
permitir sites Web altamente interativos. Antes de nos aprofundarmos sobre como o
ASP.NET faz isso, vejamos o conteúdo da solicitação e os pacotes de resposta do HTTP. A
propósito, o termo pacote é sinônimo do termo mensagem quando se discutem transa-
ções HTTP.

Solicitação

Internet

Cliente
Servidor
Resposta da Web
Exibe no
navegador

FIGURA 25.1 Comunicação de navegador/servidor Web do ASP.NET.

O pacote de solicitação HTTP


O pacote de solicitação HTTP consiste em três partes, que são:
— Linha de solicitação

— Cabeçalho HTTP que consiste em 0-n linhas de cabeçalho


— Corpo HTTP
Tecnologias Web – como elas funcionam 593

A linha de solicitação tem três partes. Essas partes são um método, um caminho para
um recurso no servidor que o cliente está solicitando e o número de versão do HTTP. Essa
linha é semelhante a:

GET /web/index.htm HTTP/1.1

Nesse exemplo, GET é o método que indica que o cliente quer obter algo a partir do
servidor. O caminho/arquivo é o arquivo solicitado que o cliente deseja. Por fim, a ver-
são do HTTP é incluída em cada solicitação.
Há mais sobre o método GET quando utilizado em conjunção com a tag <form>. Essa
tag permite ao cliente enviar informações adicionais para o servidor. Essas informações
são acrescentadas na URL pelo navegador. Portanto, a URL poderia ser semelhante a:

www.DelphiGuru.com/form.aspx?topic=Programming

A parte que se segue ao ponto de interrogação é chamada de string de consulta e pode


ser utilizada pelo servidor para executar uma pesquisa sobre um tópico especificado.
Outro método que você deve conhecer é o método POST. Ao contrário do método GET,
o método POST incorpora as informações que ele envia ao servidor no corpo do HTTP.
Muitos desenvolvedores preferem não ter o conteúdo adicional contido na URL. Isso
pode ser importante por causa de limitações no comprimento da URL que podem ser en-
viadas para alguns servidores. Nesse caso, é possível enviar informações adicionais utili-
zando o comando POST. Além disso, utilizar o método POST oculta parâmetros e valores
que desenvolvedores podem não querer exibir no campo de endereço do navegador.

O pacote de resposta HTTP


O pacote de resposta HTTP também consiste em três partes, que são:
— Linha de status

— Cabeçalho HTTP que consiste em 0-n linhas de cabeçalho

— Corpo HTTP

A linha de status consiste em três partes. Essas partes são a versão do HTTP, um códi-
go de status e um texto string para descrever o código de status. Uma linha de status
exemplo seria semelhante a:

HTTP/1.1 200 OK

Nesse exemplo, a versão HTTP/1.1. 200 é o código de status, que é descrito como OK,
que significa bem-sucedido. Outros códigos de status são classificados por seus números.
Esses códigos com suas classificações são listados aqui:
— 1xx – Information
— 2xx – Successful

— 3xx – Redirection

— 4xx – Client Error


— 5xx – Server Error
594 Capítulo 25 Fundamentos do ASP.NET

As linhas de cabeçalho HTTP contêm informações sobre a solicitação ou resposta. Elas


aparecem como um cabeçalho por linha no nome: formato de valor. A essa altura, não en-
traremos nos vários tipos de cabeçalhos senão para dizer que há basicamente quatro for-
mas de cabeçalhos: geral (general), requisição (request), resposta (response) e entidade (en-
tity). Os cabeçalhos gerais contêm informações sobre o servidor ou cliente. Os cabeçalhos
de resposta contêm informações sobre o servidor que podem ser úteis. A entidade envia in-
formações sobre a transferência de dados. Para informações adicionais sobre os vários ti-
pos de cabeçalhos, visite www.w3c.org e veja o documento RFC 2068, seção 4 e 7.
Por fim, o corpo HTTP do pacote de resposta em geral contém o código HTML que o
navegador deve traduzir e exibir para o usuário final. Portanto, um exemplo de um paco-
te de resposta HTTP pode ser semelhante a algo assim:

HTTP/1.1 200 OK
Date: Fri, 19 Dec 2000 15:12:00 GMT
Content-Type: text/html
Content-Length: 420

<html>
<body>
<h1>Welcome to DelphiGuru.com</h1>
.
.
.
</body>
</html>

Esta é uma explicação simples do protocolo HTTP. Agora vejamos como o ASP.NET
funciona.

ASP.NET – como ele funciona


A Figura 25.1 representa o modelo estático sobre como um navegador e um servidor Web
se comunicam. No ASP.NET, alguns passos extras ocorrem no lado do servidor. A Figura
25.2 ilustra esse processo.

Solicitação para
www.xapware.com
(index.aspx)

Envia por
processo
Internet trabalhador
ASP.NET

Web Server
Resposta IIS

FIGURA 25.2 A comunicação de navegador/servidor Web do ASP.NET.


ASP.NET – como ele funciona 595

Para o cliente, o processo é o mesmo: o cliente ou navegador faz uma solicitação


para o servidor. Ao receber a solicitação, o servidor – IIS nesse caso – determina se o clien-
te está solicitando uma página ASP.NET por sua extensão (.aspx). O IIS então envia a pá-
gina Web para um servidor ISAPI separado, aspnet_isapi.dll. Esse servidor envia a página
para um ASP.NET Worker Process, aspnet_wp.exe. O Worker Process invoca o Common
Language Runtime, que cuida da compilação e execução do código da página. O resulta-
do do Worker Process é o código HTML, que então é enviado de volta para o cliente no
corpo de resposta. Em termos simples, você pode visualizar o Worker Process como uma
caixa-preta cuja entrada é um arquivo .aspx e a saída é o texto HTML.
Nesse ponto, precisamos de um exemplo simples para discutir ainda mais como
tudo isso funciona. Então, sem mais adiamentos, vamos carregar o Delphi for .NET e cri-
ar uma aplicação ASP.NET.

Uma aplicação Web simples


Nesta seção, vamos criar uma aplicação ASP.NET simples utilizando Delphi for .NET.

NOTA
Pressupõe-se que você instalou os componentes necessários para desenvolver e executar aplica-
ções ASP.NET. Se não tiver feito isso, leia atentamente o arquivo README que é distribuído com as
instruções do Delphi for .NET sobre esses requisitos de instalação.

Para criar uma nova aplicação ASP.NET, simplesmente selecione File, New, ASP.NET
Web Application a partir do menu principal. Isso carrega uma caixa de diálogo que soli-
cita o nome da nova aplicação e a localização. Observe que a localização é um subdiretó-
rio do diretório-raiz Web do IIS. Inseri d4dnEx01 como o nome dessa aplicação como mos-
trado na Figura 25.3.

FIGURA 25.3 Uma nova caixa de diálogo de aplicação ASP.NET.

Depois de pressionar OK, recebo três páginas dentro do Designer. Essas são Web-
Form1.aspx, WebForm1.pas e Design. Nesse ponto, depois de selecionar a guia Design,
seleciono três controles a partir da categoria Web Controls na Component Palette e os
coloco na superfície Designer. Esses controles são um Label, um Button e um TextBox. A Fi-
gura 25.4 mostra o WebForm resultante.
596 Capítulo 25 Fundamentos do ASP.NET

FIGURA 25.4 Exemplo de um WebForm simples.

A seguir vemos o handler de evento que criei para o evento Button Click:

procedure TWebForm1.Button1_Click(sender: System.Object;


e: System.EventArgs);
begin
Label1.Text := TextBox1.Text;
end;

Ao compilar e executar a aplicação, o formulário aparecerá no navegador. Depois de


digitar texto no controle TextEdit e pressionar o botão, o navegador exibe o conteúdo na
Figura 25.5.
Agora, isso é tão fácil quanto parece. Muito do que está acontecendo aqui precisa de
explicação, então vou prosseguir.

FIGURA 25.5 Saída de navegador Web.


ASP.NET – como ele funciona 597

Estrutura de página do ASP.NET


O arquivo WebForm1.aspx é mostrado na Listagem 25.1. Esse arquivo armazena o texto da
página Web, semelhante a uma página HTML.

LISTAGEM 25.1 Conteúdo do WebForm1.aspx


1: <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas"
➥AutoEventWireup="true" Inherits="WebForm1.TWebForm1" %>
2: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
3:
4: <script runat="server">
5: void Page_Load(Object obj, EventArgs e) {
6: Label1.Text = "Tell me about Delphi for .NET";
7: }
8: </script>
9:
10: <html>
11: <head>
12: <title></title>
13: <meta name="GENERATOR" content="Borland Package Library 7.1">
14: </head>
15:
16: <body ms_positioning="GridLayout">
17: <form runat="server">
18: <asp:textbox id=TextBox1
19: style="Z-INDEX: 1; LEFT: 10px; POSITION: absolute; TOP: 42px"
20: runat="server">
21: </asp:textbox>
22: <asp:button id=Button1 style="LEFT: 10px; POSITION: absolute;
23: TOP: 81px" runat="server" text="Button">
24: </asp:button>
25: <asp:label id=Label1 style="LEFT: 10px; TOP: 15px"
➥runat="server">Label</asp:label>
26: </form>
27: </body>
28: </html>

Na Listagem 25.1, as linhas 4–8 não estavam no arquivo original gerado pelo Delphi
for .NET. Adicionei essas linhas manualmente.
Todas as páginas do ASP.NET têm a extensão .aspx, que é necessária para o servidor
Web saber que elas são páginas ASP.NET. Eu poderia ter digitado essa página no Bloco de
Notas, colocado-a em um subdiretório para meu servidor Web e executado-a exatamente
da mesma maneira.
598 Capítulo 25 Fundamentos do ASP.NET

O primeiro elemento do arquivo .aspx é a diretiva @ Page (linha 1). Essa diretiva con-
tém atributos que são utilizados pelo ASP.NET durante a compilação. Esses atributos têm
a ver com as instruções de compilação, comportamento ou saída da página. Há muitos
atributos que podem ser abrangidos neste capítulo, mas discutirei aqueles mostrados
aqui para que você possa ter uma idéia do tipo de informações que entram aqui.

NOTA
Visite http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/
cpconPage.asp para ver uma documentação completa sobre a diretiva @ Page.

O atributo language especifica a linguagem a ser utilizada ao compilar blocos de códi-


go inline que aparecem na tag <script>. Nesse exemplo, C# é a linguagem padrão. Isso só
se aplica às tags de script com os atributos runat="Server". As tags de script normais execu-
tam no cliente e, em geral, contêm o código JavaScript.

NOTA
Havia planos de incluir suporte para Delphi scripting com o Delphi for .NET; entretanto, ele não
aparece nessa versão. Talvez ele seja incluído em uma versão posterior do Delphi.

O atributo Debug especifica se a página é ou não compilada com informações de sím-


bolo.
O atributo CodeBehind indica o arquivo-fonte que tem a classe de página associada
com essa página. Esse atributo diz à IDE que arquivo-fonte associar com essa página
ASP.NET. Discutirei isso com mais detalhes ainda neste capítulo.
O atributo AutoEventWireup especifica se os eventos são automaticamente conectados
a handlers de evento. Esse atributo é configurado como False por padrão ao criar uma
nova aplicação ASP.NET no Delphi. Você deve configurar isso manualmente como True
para que o handler de evento Page_Load seja automaticamente conectado.
Inherits especifica uma classe a partir da qual essa página é herdada. Essa classe deve
ser direta ou indiretamente herdada da classe System.Web.UI.Page.
A próxima seção primária é o bloco <script> (linhas 4–8). Esse bloco contém código
que é executado no servidor quando essa página é processada. Observe a tag runat="ser-
ver". Essa tag instrui o ASP.NET a processar o código no servidor. Até o próprio código é
código C#. Page_Load é um handler de evento que é automaticamente associado com a
classe Page. Portanto, quando essa página for carregada pelo ASP.NET, você pode colocar
o código aqui para inicializar vários elementos na página Web como mostrado na Lista-
gem 25.1.
O bloco HTML (linhas 10–28) contém código HTML que o ASP.NET utilizará para
formular o conteúdo HTML final. Observe que na tag form, você vê a tag runat="server"
novamente. Essa diretiva renderiza esse formulário como um WebForm. Os vários ele-
mentos dentro desse formulário Web são utilizados pelo ASP.NET para gerar o código
HTML que, em última instância, chega ao cliente. Esses controles são aqueles que podem
interagir com o servidor para invocar o processamento de servidor conhecido como co-
de-behind. Discutirei mais sobre o code-behind em uma seção posterior neste capítulo.
ASP.NET – como ele funciona 599

Comunicação baseada em evento


A próxima listagem deve ajudá-lo a entender como o ASP.NET funciona com seu modelo
baseado em evento entre o cliente e servidor.
No exemplo anterior, quando o usuário digita a URL para a página Web a partir da
Listagem 25.1, a Listagem 25.2 é a origem HTML real retornada para o cliente.

LISTAGEM 25.2 HTML resultante do WebForm1.aspx


1: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2:
3: <html>
4: <head>
5: <title></title>
6: <meta name="GENERATOR" content="Borland Package Library 7.1">
7: </head>
8: <body ms_positioning="GridLayout">
9: <form name="_ctl0" method="post" action="WebForm1.aspx" id="_ctl0">
10: <input type="hidden" name="__VIEWSTATE"
➥value="dDwtNzA2MDcxNTQ5O3Q8O2w8aTwyPjs+O2w8dDw7bDxpPDU+Oz47bDx0PHA8cDxsPFR
➥leHQ7PjtsPFRlbGwgbWUgYWJvdXQgRGVscGhpIGZvciAuTkVUOz4+Oz47Oz47Pj47P
➥j47PriouWT0tSYLFvbR51o1q3beLGLB" />
11: <input name="TextBox1" type="text" id="TextBox1" style="Z-INDEX: 1;
12: LEFT: 10px; POSITION: absolute; TOP: 42px" />
13: <input type="submit" name="Button1" value="Button" id="Button1"
14: style="LEFT: 10px; POSITION: absolute; TOP: 81px" />
15: <span id="Label1" style="LEFT: 10px;
16: TOP: 15px">Tell me about Delphi for .NET</span>
17: </form>
18: </body>
19: </html>

Observe que isso é muito diferente do arquivo WebForm1.aspx original. De fato, esse ar-
quivo é semelhante a um antigo arquivo HTML convencional. O ASP.NET pegou o arqui-
vo WebForm1.aspx e o processou para gerar esse script HTML. Observe que as linhas 5 e 6 da
Listagem 25.1,

void Page_Load(Object obj, EventArgs e) {


Label1.Text = "Tell me about Delphi for .NET";

resultaram nas linhas 15–16 da Listagem 25.2. Esse é um exemplo do modelo de proces-
samento de evento no ASP.NET. Quando o ASP.NET carregou o arquivo WebForm1.aspx, ele
executou esse código, o que resultou na configuração do texto do Label1. Agora lembre-se
de que adicionamos um handler de evento ao evento Click do Button1. Se você digitasse
algo no TextBox e pressionasse Button1, o código HTML teria se tornado ligeiramente dife-
rente porque o texto do Label1 conteria o texto que você digitou no TextBox. Isso provavel-
600 Capítulo 25 Fundamentos do ASP.NET

mente levanta a pergunta de como o servidor sabia associar o handler de evento no ar-
quivo WinForm1.pas com o Button1 do arquivo WinForm.aspx. Em termos simples, pressionan-
do o botão, numa viagem de ida e volta, vai até o servidor e volta. Na extremidade do ser-
vidor o código do evento Click do Button1 é executado e uma nova página é retornada
para o cliente. Discutirei isso, mas primeiro, há uma parte adicional de informações im-
portantes: o campo oculto __VIEWSTATE.

VIEWSTATE e manutenção de estado


Na Listagem 25.2, existe um fluxo relativamente obscuro de caracteres para um campo
oculto chamado __VIEWSTATE. Esse campo permite que o ASP.NET mantenha o estado
entre as transações cliente/servidor. Por exemplo, se você executasse essa página, inse-
risse um texto no TextBox e pressionasse Button1, o resultado mostraria o que você pode-
ria esperar – Label1 exibiria o texto inserido. Entretanto, se pressionasse o botão Back do
navegador, a experiência poderia fazê-lo acreditar que você retornaria à página original
com um TextBox vazio. Esse não é o caso: de fato, o TextBox1 parece ter retido o texto que
você inseriu.
Essa é uma diferença significativa do desenvolvimento Web tradicional em que a
manutenção de estado era complicada ou inexistente. Também é o mecanismo pelo qual
os desenvolvedores serão capazes de criar poderosas aplicações interativas.
O campo VIEWSTATE que você vê no código de HTML resultante é um campo oculto
em que o ASP.NET coloca informações do estado sobre cada controle. Essas informações
são transferidas entre as transações cliente/servidor.

CodeBehind
Considere mais uma vez a Listagem 25.1. Você pode alterar a linha 1 em

<%@ Page language="c#" %>

e renomear a página para algo diferente, como demo.aspx. Inserindo a página no navega-
dor, ainda assim ele teria executado. Entretanto, você notará que pressionar o botão não
realiza a função originalmente realizada.
O ASP.NET introduziu um conceito chamado code-behind. Há duas distinções entre
code-behind de tempo de execução e tempo de projeto.
No Delphi for .NET, ao criar uma aplicação ASP.NET, você recebe dois arquivos. O
primeiro arquivo é a página ASP.NET com uma extensão .aspx. O segundo é um arqui-
vo-fonte com uma extensão .pas. Esse arquivo contém a classe Page específica para a pági-
na ASP.NET. Quando você compilar a aplicação, o assembly resultante conterá a defini-
ção dessa classe Page específica. A associação de tempo de projeto entre o arquivo .aspx e
.pas é determinada pelo atributo CodeBehind da diretiva @Page. A associação de tempo de
execução entre o arquivo .aspx e a classe Page é determinada pelo atributo inherits da dire-
tiva @Page no arquivo .aspx.
Ao solicitar um arquivo .aspx que não tem o atributo inherits (como o caso com
nosso arquivo demo.aspx), o ASP.NET instancia uma classe genérica que diretamente
herda da classe System.Web.UI.Page. Nesse caso, a classe Page descendente herda a funcio-
Classes ASP.NET 601

nalidade padrão da classe Page de base. De outra maneira, System.Web.UI.Page é a classe


base para a classe dinamicamente criada. Quando uma classe mais específica é forneci-
da, essa nova classe torna-se agora a classe base para a classe criada dinamicamente. Ela
incluirá quaisquer personalizações que o desenvolvedor possa ter programado nos vá-
rios eventos e métodos dessa nova classe básica, como o nosso handler de evento But-
ton_Click.
Ao executar a página demo.aspx, a classe Page criada no lado ASP.NET não continha có-
digo especializado. Portanto, pressionar o botão não realizou nada. Entretanto, ao execu-
tar a classe original WebForm1.aspx contendo Inherits="PageWebForm1.TWebForm1", pressionar o
botão faz com que seu handler de evento seja invocado. Esse atributo refere-se à classe
definida no arquivo WebForm1.pas em que criamos um handler de evento Click que alterou
o texto do Label1 para aquele contido no TextBox.
Esse baixo acoplamento entre a página ASP.NET e o código por trás dela (daí o termo
code-behind) cria pelo menos duas vantagens principais para o desenvolvimento no
ASP.NET. A primeira é a separação entre projeto Web e a programação Web. A segunda
tem a ver com a herança de página.

Separação de projeto/programação
Os dois arquivos distintos permitem tanto aos desenvolvedores como projetistas Web
trabalhar no mesmo projeto sem compartilhar o mesmo arquivo. Os projetistas Web po-
dem focalizar a aparência e o comportamento da página trabalhando no arquivo .aspx,
enquanto os programadores podem focalizar a funcionalidade fornecida pela página.

Herança de página
Uma vantagem muito mais notável é provavelmente a da herança de página. É possível
criar uma classe Page (chamada BasePage) que descende da classe System.Web.UI.Page e a par-
tir da qual descenderão todas as outras classes Page na aplicação. Isso permite um concei-
to semelhante à herança visual de um formulário. Todas as classes Page finais herdarão a
funcionalidade de BasePage. Além disso, a classe BasePage pode ser utilizada para estabele-
cer aparência e comportamento comuns a um site inteiro. Nesse ponto, podemos exami-
nar alguns objetos fundamentais dentro de ASP.NET.

Classes ASP.NET
Os exemplos a seguir examinarão alguns objetos centrais que você provavelmente utili-
zará no desenvolvimento de suas próprias aplicações ASP.NET. Há vários objetos dentro
do framework ASP.NET e seria impossível discutir todos eles neste livro. Em vez disso,
esta seção abrange algumas das principais classes e seus métodos.

A classe HTTPResponse
A classe HTTPResponse é utilizada quando o servidor precisa retornar uma resposta de volta
para o navegador. Esta seção discute os aspectos-chave dessa classe.
602 Capítulo 25 Fundamentos do ASP.NET

Escrevendo texto no cliente


Quando o objeto Page é criado no servidor, uma de suas propriedades é um objeto
HTTPResponse chamado Response. Você pode referenciar esse objeto no código para enviar
informações de volta para o cliente. Uma utilização comum é escrever texto no navega-
dor cliente. O seguinte código produz a saída no navegador, mostrado na Figura 25.6.

for i := 1 to 7 do
Response.Write(
System.String.Format('<font size={0}>Delphi for .NET<br></font>', [i]));

FIGURA 25.6 Saída Response.Write( )

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex01\.

O próximo código demonstra como gravar informações a partir do pacote HTTP


Response:

Response.Write(Response.StatusCode.ToString);
Response.Write(Response.StatusDescription);
Response.Write(Response.Status);

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex02\.

DICA
Examinando o código de HTML do exemplo anterior, você verá que Response.Write( ) escreve
"200OK200OK", uma saída HTML fictícia:

" 200OK200 OK
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
Classes ASP.NET 603

<title></title>
<meta name="GENERATOR" content="Borland Package Library 7.1">
</head>
<body ms_positioning="GridLayout">
<form name="_ctl0" method="post" action="WebForm1.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value=
➥"dDwtMTI3OTMzNDM4NDs7PktG25iT1NcUAmnlzewNqw6xHn/f" />
</form>
</body>
</html>"
Para evitar a parte HTML não-utilizada, você poderia adicionar uma linha:

Response.&End;
O método End( ) “envia toda a saída atualmente armazenada em buffer para o cliente, interrom-
pe a execução da página e levanta o evento Application_EndRequest”. Observe que ele deve ser
precedido do caractere & porque End é uma palavra reservada do Delphi.

Filtrando saída
Outra utilização para a classe HTTPResponse é filtrar a saída antes de ela ir para o cliente. Isso
é feito atribuindo um filtro, que é uma classe que descende da classe Stream ou uma deri-
vada disso. A classe HTTPResponse passa qualquer texto que vai para o cliente pelo filtro an-
tes de realmente enviá-lo para o cliente. A Listagem 25.3 mostra o código parcial de um
exemplo que demonstra essa técnica.

LISTAGEM 25.3 Demo de filtro de saída


1: type
2: TWebForm1 = class(System.Web.UI.Page)
3: strict private
4: procedure Page_Load(sender: System.Object; e: System.EventArgs);
5: end;
6:
7: TChangeCaseFilter = class(MemoryStream)
8: private
9: mStream: Stream;
10: public
11: constructor Create(aStream: Stream); override;
12: procedure Write(bfr: array of byte; offset: integer;
13: count: Integer); override;
14: end;
15:
16: implementation
17:
18: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
19: begin
20: Response.Filter := TChangeCaseFilter.Create(Response.Filter);
21: Response.Write('Hello Delphi for .NET');
604 Capítulo 25 Fundamentos do ASP.NET

LISTAGEM 25.3 Continuação


22: end;
23:
24: { TChangeCaseFilter }
25:
26: constructor TChangeCaseFilter.Create(aStream: Stream);
27: begin
28: inherited Create;
29: mStream := aStream;
30: end;
31:
32: procedure TChangeCaseFilter.Write(bfr: array of byte; offset,
33: count: Integer);
34: var
35: data: array of byte;
36: i: integer;
37: diff: Integer;
38: begin
39: inherited;
40: Borland.Delphi.System.SetLength(data, count);
41: Buffer.BlockCopy(bfr, offset, data, 0, count);
42: diff := ord('a')-ord('A');
43:
44: for i := 0 to count-1 do
45: begin
46: if (data[i] >= ord('a')) and (data[i] <= ord('z')) then
47: data[i] := data[i] - diff
48: else if (data[i] >= ord('A')) and (data[i] <= ord('Z')) then
49: data[i] := data[i] + diff;
50: end;
51:
52: mStream.Write(data, 0, count);
53: end;
54:
55: end.

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex03\.

A classe TChangeCaseFilter é derivada de MemoryStream (linha 7). Essa classe poderia ter
sido derivada de Stream; entretanto, isso exigiria que métodos adicionais fossem
sobrescritos. No handler de evento Page_Load( ), uma instância desse filtro é criada e atribuída
à propriedade HTTPResponse.Filter (linha 20). Esse filtro stream criado recentemente adota
o filtro existente como um parâmetro e o encaminha para a saída modificada (em uma
cadeia de filtros). Antes de enviar o corpo HTTP para o cliente, a classe HTTPResponse envia
o corpo HTTP pelo método Write( ) do filtro. É dentro desse método que você tem a
oportunidade de filtrar o conteúdo do corpo HTTP. Nesse exemplo simples o método
TChangeCaseFilter.Write( ) inverte a caixa (caixa-alta ou caixa-baixa) de caracteres no
fluxo de texto.
Classes ASP.NET 605

Redirecionando o navegador
Outra utilização da classe HTTPResponse é redirecionar o navegador do usuário para outro
URL. Isso é feito de maneira muito simples por dentro do handler de evento Page_Load( )
como mostrado aqui:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


begin
Response.Redirect('http://www.xapware.com');
end;

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex04\.

A Classe HTTPRequest
A classe HTTPRequest é a conversão da classe HTTPResponse. Ela permite que o cliente envie as
informações de volta para o servidor. Além disso, por meio da classe HTTPRequest, o servidor
pode eleger utilizá-la para fornecer informações para o cliente e por isso armazena infor-
mações detalhadas sobre a solicitação do cliente. A Listagem 25.4 ilustra essa técnica.

LISTAGEM 25.4 Exemplo de HTTPRequest


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: var
3: NameValCol: NameValueCollection;
4: StrArray: array of String;
5: i: integer;
6: begin
7: // Cliente pode solicitar informações de servidor pela Request
8: Response.Write(System.String.Format('Application Path: {0} <br>',
9: [Request.ApplicationPath]));
10: Response.Write(System.String.Format('File Path: {0} <br>',
11: [Request.FilePath]));
12: Response.Write(System.String.Format('Path Info: {0} <br>',
13: [Request.PathInfo]));
14: Response.Write(System.String.Format('Physical Path: {0} <br>',
15: [Request.PhysicalPath]));
16: Response.Write(System.String.Format('Physical Application Path: {0} <br>',
17: [Request.PhysicalApplicationPath]));
18: Response.Write(System.String.Format('User Host Address: {0} <br>',
19: [Request.UserHostAddress]));
20: Response.Write(System.String.Format('User Host Name: {0} <br>',
21: [Request.UserHostName]));
22:
23:
24: // Grava informações da HTTP Request
25: Response.Write(System.String.Format('HTTP Method: {0} <br>',
26: [Request.HTTPMethod]));
27: Response.Write('Headers<br>');
606 Capítulo 25 Fundamentos do ASP.NET

LISTAGEM 25.4 Continuação


28:
29: NameValCol := Request.Headers;
30: StrArray := NameValCol.AllKeys;
31: for i := Low(StrArray) to High(StrArray) do
32: Response.Write(System.String.Format(' {0} : {1} <br>',
33: [StrArray[i], Request.Headers.Item[StrArray[i]]]));
34: // Grava informações do navegador
35: Response.Write('<br>');
36: Response.Write('Browser Information<br>');
37: Response.Write(System.String.Format('Type: {0}<br>',
38: [Request.Browser.&Type.ToString]));
39: Response.Write(System.String.Format('Version: {0}<br>',
40: [Request.Browser.Version]));
41: Response.Write(System.String.Format('ClrVersion: {0}<br>',
42: [Request.Browser.ClrVersion.ToString]));
43: Response.Write(System.String.Format('PlatForm: {0}<br>',
44: [Request.Browser.Platform]));
45: Response.Write(System.String.Format('Supports Frames: {0}<br>',
46: [Request.Browser.Frames.ToString]));
47:
48: // Grava informações de consulta
49:
50: Response.Write('<br>');
51: Response.Write(System.String.Format('Query String: {0}<br>',
52: [Request.QueryString]));
53: Response.Write(System.String.Format('Query String ''Name'': {0}<br>',
54: [Request.QueryString['Name']]));
55: Response.Write(System.String.Format('Query String ''State'': {0}<br>',
56: [Request.QueryString['State']]));
57: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex05\.

A primeira parte da Listagem 25.4 ilustra como o servidor pode retornar informações
sobre o próprio cliente (linhas 8–21). Então, nas linhas 25–27, os clientes examinam o
pacote de solicitação HTTP. As linhas 29–46 ilustram como imprimir as informações so-
bre o navegador do cliente. Por fim, as linhas 50–56 ilustram como extrair e imprimir in-
formações a partir da parte querystring da URL. Portanto, se a seguinte URL fosse inserida
no navegador,

http://localhost/Ex05/WebForm1.aspx?Name=XaviePacheco&State=Colorado

a saída das linhas 50–56 seria

Query String: Name=XaviePacheco&State=Colorado


Query String 'Name': XaviePacheco
Query String 'State': Colorado
Classes ASP.NET 607

A Classe HTTPCookie
As classes HTTPResponse e HTTPRequest têm uma propriedade Cookies, que é uma coleção de
cookies. Por meio dessa propriedade, você poderia salvar e gerenciar os cookies no com-
putador cliente.
O seguinte código demonstra como criar um cookie na coleção Cookies:

var
hc: HttpCookie;
begin
hc := HttpCookie.Create('Name', TextBox1.Text);
hc.Expires := DateTime.Now.AddMonths(1);
Response.Cookies.Add(hc);
Label1.Text := System.String.Format('Hello {0}', TextBox1.Text);
end

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex06\.

Nesse exemplo, uma instância da classe HTTPCookie é criada. Os cookies são armazena-
dos como um par nome/valor. Nesse exemplo, o nome do cookie é 'Name' e o valor é o
conteúdo de um controle TextBox. Você pode especificar uma data para o cookie expirar
atribuindo essa data à propriedade HTTPCookie.Expires. Os cookies podem armazenar tipos
de dados simples como strings, inteiros, números de ponto flutuante e assim por diante.
Além disso, um único cookie pode armazenar uma lista de valores de string (ver a pro-
priedade HttpCookie.Values).
Para avaliar o conteúdo de um cookie, você simplesmente acessa a propriedade
Cookies como acessaria qualquer outra coleção, como ilustrado aqui:

if Request.Cookies['Name'] = nil then


Label1.Visible := False
else
Label1.Text := System.String.Format('Hello {0}',
Request.Cookies['Name'].Value);

Para excluir um cookie, você pode limpar a classe HTTPCookie, como mostrado aqui:

var
emptyCookie: HttpCookie;
begin
EmptyCookie := Request.Cookies['Name'];
EmptyCookie.Values.Clear;
Response.Cookies.Add(emptyCookie);
end;

Fazendo isso, o cookie será removido do diretório de cookies do usuário.


608 Capítulo 25 Fundamentos do ASP.NET

Tratando eventos de Postback


A classe Page contém uma propriedade Boolean chamada IsPostBack. Quando um usuário
solicita uma URL pela primeira vez, o valor Page.IsPostBack é falso. Esse valor de proprie-
dade é verdadeiro com qualquer postagem subseqüente da página de volta para o cliente.
Considere o código na Listagem 25.5, que demonstra o uso da propriedade IsPost-
Back.

LISTAGEM 25.5 Uso da propriedade IsPostBack


1: procedure TWebForm1.Page_Load(sender: System.Object;
2: e: System.EventArgs);
3: begin
4: if IsPostback and (TextBox1.Text < > '') then
5: begin
6: Label1.Visible := True;
7: Label1.Text := System.String.Format('Hello {0}', TextBox1.Text);
8: end
9: else begin // não é um postback - visita inicial
10: if Request.Cookies['Name'] = nil then
11: Label1.Visible := False
12: else
13: Label1.Text := System.String.Format('Hello {0}',
14: Request.Cookies['Name'].Value);
15: end;
16: end;
17:
18: procedure TWebForm1.btnDeleteCookie_Click(sender: System.Object;
19: e: System.EventArgs);
20: var
21: emptyCookie: HttpCookie;
22: begin
23: EmptyCookie := Request.Cookies['Name'];
24: EmptyCookie.Values.Clear;
25: Response.Cookies.Add(emptyCookie);
26: Label1.Visible := False;
27: end;
28:
29: procedure TWebForm1.btnAddCookie_Click(sender: System.Object;
30: e: System.EventArgs);
31: var
32: hc: HttpCookie;
33: begin
34: hc := HttpCookie.Create('Name', TextBox1.Text);
35: hc.Expires := DateTime.Now.AddMonths(1);
36: Response.Cookies.Add(hc);
37: end;

u Veja o exemplo inteiro no CD: \Code\Chapter 25\Ex07\.


Classes ASP.NET 609

Esse é um exemplo de uma página com dois botões. O handler de evento Button1_
Click( ) adiciona um cookie. O handler de evento Button2_Click( ) exclui um cookie.
Quando a página é carregada pela primeira vez, o handler de evento Page_Load( ) dispara.
Se essa for a primeira vez que a página é carregada, a cláusula else é processada (linhas
9–15). Esta seção de código simplesmente determina se o cookie já existe e configura um
Label para mostrar o valor desse cookie. Se essa for uma renderização subseqüente da pá-
gina (IsPostBack= true), a cláusula then só será processada se o usuário tiver digitado texto
no TextBox. Se tiver, o conteúdo TextBox é exibido de volta para o usuário. Você verá que
IsPostBack também é freqüentemente utilizado para realizar validação.
NESTE CAPÍTULO
— Criando páginas Web com CAPÍTULO 26
controles ASP.NET
— Pré-preenchendo controles
de lista
Criando páginas Web
— Realizando validação de
formulário Web
com o ASP.NET
— Formatação de formulário
Web
— Navegando entre formulários E ste capítulo se concentra nos detalhes práticos de
Web construir páginas Web interativas utilizando Web Forms
do ASP.NET. Neste capítulo, você verá como os vários
— Dicas e técnicas
controles são utilizados dentro de WebForms. Você
aprenderá a realizar a validação dos dados que os
usuários inserem em seus formulários e a formatar os
formulários para exibição. Este capítulo mostra como
navegar entre páginas dentro da aplicação ASP.NET.
Você também verá outras técnicas muito úteis para criar
aplicações Web.

NOTA
Ao desenvolver páginas Web interativas, há principalmente dois
participantes. Um é o desenvolvedor/programador. O outro é o
projetista criativo. Ambas as habilidades são necessárias para criar
aplicações/sites Web sofisticados. Este capítulo focaliza o aspecto
do desenvolvimento/programação da aplicação Web. De fato, eu
não poderia nem sequer desenhar uma linha reta, muito menos
discutir o aspecto criativo do desenvolvimento de sites Web. Deixa-
rei este tópico para aqueles dotados desse talento.

Criando páginas Web com controles


ASP.NET
Em geral, uma página Web puramente HTML é estática.
Os controles que são colocados em uma página HTML
existem e funcionam no cliente. Talvez as páginas HTML
tenham script para realizar algum nível de interação, mas
isso é uma grande disparidade com páginas Web
altamente funcionais.
O ASP.NET trabalha com controles de servidor
(Server controls). Os controles de servidor funcionam no
servidor e são renderizados no cliente em HTML. Por
meio de script no lado cliente, na página HTML, o
servidor é notificado da interatividade de usuário e tem
Criando páginas Web com controles ASP.NET 611

a oportunidade de responder. O usuário realmente não vê tudo isso acontecendo – o re-


sultado é uma aplicação Web altamente interativa.
Existem cinco tipos de controles diferentes que podem ser utilizados para desenvol-
ver WebForms. Esses são descritos brevemente aqui:
— Controles HTML – Os controles HTML mapeiam para os elementos HTML com os
quais você está familiarizado se realizou algum tipo de desenvolvimento de site
Web. Ao adicionar o atributo runat="server" a um controle HTML, você permite ao
ASP.NET manipular várias propriedades desse controle a partir do servidor. Exis-
tem controles HTML no namespace System.Web.UI.HtmlControls.
— Controles Web de servidor – Os controles Web são semelhantes aos controles
HTML, mas, em geral, são mais funcionais e não mapeiam para os elementos
HTML padrão. Os controles Web requerem os atributos runat="server". Na maio-
ria, eles podem ser utilizados como uma substituição para controles HTML. Os
controles Web derivam de classes ASP.NET, fornecendo a essas, capacidades fun-
cionais melhores do que eles teriam com controles HTML.
— Controles de validação – Os controles de validação são controles não-visíveis que
são utilizados para validar a entrada de usuário. Cinco controles de validação di-
ferentes podem ser utilizados para validar tudo, desde a existência de uma entra-
da ao formato exato dessa entrada. Quando um controle de validação padrão não
atender à necessidade, você pode até mesmo realizar a validação personalizada no
cliente ou servidor.
— Controles de usuário – Os controles de usuário são controles que você ou um
fornecedor independente fornece com funcionalidade especializada. Eles são,
de fato, controles de servidores Web, mas não fazem parte do pacote padrão de
controle de servidor Web. O Capítulo 34 discute como desenvolver seus própri-
os controles de servidor Web.
— Controles vinculados a dados – Os controles vinculados a dados são controles de
servidor Web que têm capacidades de vinculação de dados automáticas. Por auto-
mático, entende-se que suas propriedades suportam a capacidade de extrair e exi-
bir dados a partir de uma origem de dados. Obviamente, você pode extrair de e a-
tribuir programaticamente os mesmos dados a qualquer controle. Os controles
vinculados a dados simplesmente suportam melhor a técnica de vinculação de
dados por meio de suas propriedades.
Para ilustrar o uso de controles, este capítulo constrói uma aplicação Web de exem-
plo. Esta aplicação contém um formulário que um usuário preencheria para solicitar o
download de um produto.

Exemplo de formulário de solicitação de download


Este típico WebForm é aquele que uma organização poderia utilizar para obter informações
de pessoas interessadas em seu(s) produto(s). A idéia é que antes que o usuário possa fazer
download do software demo, ele deve fornecer pelo menos algumas informações pes-
soais. Preferivelmente, você obtém seu endereço de e-mail para poder enviar a URL do
612 Capítulo 26 Criando páginas Web com o ASP.NET

download. Ao fazer isso, é sensato obter uma permissão para enviar informações adicio-
nais sobre os produtos da sua empresa. Normalmente, as informações necessárias são o
nome e o e-mail da pessoa e talvez algumas informações adicionais sobre como o possí-
vel cliente descobriu seu produto. As seções a seguir ilustram como criar essa página.

Layout de página
Uma das considerações iniciais sobre o desenvolvimento de uma página Web é como ela
será exibida para o usuário final. Isso não se refere ao tipo de “aparência e comportamento”
de um ponto de vista artístico, mas principalmente do ponto de vista de onde os controles
são posicionados – em que ordem etc. Deve-se considerar que os vários navegadores podem
renderizar sua página de modo diferente; portanto, talvez você queira ter certeza de que uti-
liza o método compatível com a maioria dos navegadores. Há realmente três abordagens
principais sobre como organizar controles na página. Cada uma delas é discutida aqui:
— Utilização de tabelas – Esse é possivelmente o método mais popular de posicionar
controles dentro de uma página Web. Você simplesmente utiliza tabelas para pro-
jetar o layout dos formulários. O posicionamento de controles dentro do formulá-
rio é ditado pelas posições de célula das tabelas. Como é possível aninhar tabelas
dentro de células, você pode projetar a página Web inteira utilizando essa técnica.
— Utilização de Cascading Style Sheet (CSS) – A especificação Cascading Style Sheet
(CSS) adiciona suporte para posicionamento absoluto de controles dentro de pá-
ginas HTML. A desvantagem, naturalmente, é se o navegador alvo suporta ou não
CSS. A maioria dos navegadores modernos suporta CSS, mas é realmente você
que determina sua utilização. As folhas de estilo em cascata (Cascading Style
Sheets) fornecem muitas capacidades não disponíveis através da HTML pura.
— Utilização de ASP.NET Absolute Positioning – Com o ASP.NET, você pode especi-
ficar como um controle é posicionado quando você o solta na página. Isso faz
com que a definição do controle tenha o tag absolute colocado no arquivo .aspx
como mostrado aqui:
<asp:textbox id=TextBox1
style="LEFT: 38px; POSITION: absolute; TOP: 46px"
runat="server">

Isso é sintaticamente idêntico a CSS. Para ativar posicionamento absoluto em um


controle, você simplesmente marca o botão de posição absoluta na HTML Design
Toolbar. Embora essa técnica simplifique o layout de página de um ponto de vista
de design, ela também requer que o navegador suporte CSS. Portanto, essa técni-
ca tem o mesmo requisito do método anterior utilizando CSS.

NOTA
De modo ideal, seria excelente se o ASP.NET pudesse reformatar a saída para utilizar tabelas se o
navegador não suportar CSS. Ele não suporta nesse ponto; já tentei. Contudo, a maioria de nave-
gadores suporta CSS e pode-se assumir seguramente que suas páginas Web serão legíveis por pra-
ticamente todo mundo que a visitar.
Criando páginas Web com controles ASP.NET 613

Criando um formulário
Para o formulário de solicitação de download nessa aplicação de exemplo, optarei pela
formatação baseada em tabela. A Figura 26.1 mostra o formulário que criei na IDE do
Delphi. Esse é o formulário padrão que foi configurado durante a criação de uma aplica-
ção Web do ASP.NET. Renomeei esse formulário como DwnLdForm.aspx. Esse formulário
utiliza controles Web Labels, TextBoxes, DropDownLists e CheckBoxes. Você achará que selecio-
nar esses controles e manipular suas propriedades não é nada diferente de lidar com con-
troles Windows Forms.

u Veja um exemplo completo no CD sob \Code\Chapter 26\Ex01\.

Esse formulário também utiliza um controle HTML padrão para o botão Reset e um
controle Web para o botão Submit. O botão Submit faz o que diz e envia a resposta do
usuário de volta para o servidor. Para a validação funcionar, o botão Submit deve ser um
controle Web. O botão Reset, por outro lado, é capaz de fazer isso no cliente; utilizar o
controle HTML padrão impede uma viagem para o servidor só para limpar os controles.

Processando o evento de carga


Nesse ponto a aplicação demo não faz nada. Certamente, você pode executá-la, preen-
cher o formulário e pressionar Submit, mas descobrirá que o formulário retorna para o
lugar que você o deixou. Normalmente, você vai querer aceitar as informações forneci-
das pelo usuário, processá-las e então retornar para o usuário uma página que indica um
envio bem-sucedido. Para demonstrar isso, acrescentei um formulário adicional à aplica-
ção, ThankYouForm.aspx, que simplesmente contém um Label, que agradece a solicitação do
usuário.

FIGURA 26.1 Formulário de solicitação de download.


614 Capítulo 26 Criando páginas Web com o ASP.NET

Quando o usuário pressionar o botão Submit, o comportamento padrão do ASP.NET


é reenviar o formulário de volta para o cliente. Isso é chamado de post-back. Você pode
capturar esse evento no servidor e redirecionar o usuário para outra página, por exem-
plo. O handler de evento Page_Load( ) realiza o seguinte para essa aplicação:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


begin
if IsPostBack then
Response.Redirect('ThankYouForm.aspx');
end;

Quando o servidor carregar essa página uma segunda vez como resultado de um
post-back, a propriedade IsPostBack será True. Nesse evento, o método Response.Redirect( )
é invocado, então o usuário é redirecionado para a página Thank You.

Salvando arquivos de dentro de uma aplicação ASP.NET


Embora seja excelente poder ter um usuário para preencher nosso formulário e até mes-
mo poder agradecê-lo por fazer isso, não fizemos nada com os dados fornecidos. Portan-
to, uma procedure SaveUserInfo( ) é adicionada ao formulário. Essa procedure aceita os
dados que o usuário inseriu no formulário e os salva em um arquivo na unidade local. A
Listagem 26.1 mostra essa procedure.

LISTAGEM 26.1 Procedure SaveUserInfo( )


1: procedure TWebForm1.SaveUserInfo;
2: const
3: fname = '.\files\dld_{0}.txt';
4: var
5: fs: FileStream;
6: sw: StreamWriter;
7: begin
8: fs := FileStream.Create(Server.MapPath(System.String.Format(fname,
9: [System.Guid.NewGuid.ToString])), FileMode.Append,
10: FileAccess.Write);
11: try
12: sw := StreamWriter.Create(fs);
13: try
14: sw.WriteLine(System.String.Format('{0}={1}', [tbxFirstName.ID,
15: tbxFirstName.Text]));
16: sw.WriteLine(System.String.Format('{0}={1}', [tbxLastName.ID,
17: tbxLastName.Text]));
18: sw.WriteLine(System.String.Format('{0}={1}', [tbxTitle.ID,
19: tbxTitle.Text]));
20: sw.WriteLine(System.String.Format('{0}={1}', [tbxEmail.ID,
21: tbxEmail.Text]));
22: sw.WriteLine(System.String.Format('{0}={1}', [tbxCompany.ID,
23: tbxCompany.Text]));
Criando páginas Web com controles ASP.NET 615

LISTAGEM 26.1 Continuação


24: sw.WriteLine(System.String.Format('{0}={1}', [tbxAddress1.ID,
25: tbxAddress1.Text]));
26: sw.WriteLine(System.String.Format('{0}={1}', [tbxAddress2.ID,
27: tbxAddress2.Text]));
28: sw.WriteLine(System.String.Format('{0}={1}', [tbxCity.ID,
29: tbxCity.Text]));
30: sw.WriteLine(System.String.Format('{0}={1}', [tbxPostalCode.ID,
31: tbxPostalCode.Text]));
32: sw.WriteLine(System.String.Format('{0}={1}', [tbxTelephone.ID,
33: tbxTelephone.Text]));
34: sw.WriteLine(System.String.Format('{0}={1}', [tbxFax.ID,
35: tbxFax.Text]));
36: sw.WriteLine(System.String.Format('{0}={1}', [tbxURL.ID,
37: tbxURL.Text]));
38: if ddlCountry.SelectedIndex >= 0 then
39: sw.WriteLine(System.String.Format('{0}={1}', [ddlCountry.ID,
40: ddlCountry.Items[ddlCountry.SelectedIndex]]));
41: if ddlState.SelectedIndex >= 0 then
42: sw.WriteLine(System.String.Format('{0}={1}', [ddlState.ID,
43: ddlState.Items[ddlState.SelectedIndex]]));
44: sw.WriteLine(System.String.Format('{0}={1}', [cbxFreeStuff.ID,
45: cbxFreeStuff.Checked.ToString]));
46: finally
47: sw.Close;
48: end;
49: finally
50: fs.Close;
51: end;
52: end;

u Veja um exemplo completo no CD sob \Code\Chapter 26\Ex01\.

Essa procedure utiliza uma stream para salvar as informações do usuário em um ar-
quivo de texto. Para certificar-se de que o nome de arquivo sendo gerado é único, a pro-
cedure cria um nome de arquivo utilizando a função System.Guid.NewGuid( ) (linha 9).
Observe o uso da função Server.MapPath( ) na linha 8. O objeto Server é uma classe
HTTPServerUtility que fornece várias funções auxiliares para processar solicitações Web. A
constante

const
fname = '.\files\dld_{0}.txt';

é utilizada para formatar o nome de arquivo adequado. O nome resultante deve ser um
diretório virtual que o ASP.NET converterá em um diretório físico. O resto do código é re-
lativamente simples e direto. Ele conta com as classes FileStream e StreamWriter abrangidas
no Capítulo 12, para salvar os dados do usuário. Isso também demonstra como os vários
controles são acessíveis por dentro do código code-behind.
616 Capítulo 26 Criando páginas Web com o ASP.NET

NOTA
Para que uma aplicação ASP.NET salve um arquivo, os direitos IIS e NTFS adequados devem estar
configurados. O Capítulo 31 abrange a segurança do ASP.NET.

Nesse ponto o programa deve invocar a procedure SaveUserInfo, que é tratada no


handler de evento Page_Load( ) como mostrado aqui:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


begin
if IsPostBack then
begin
SaveUserInfo;
Response.Redirect('ThankYouForm.aspx');
end;
end;

É importante que a procedure seja inicialmente invocada antes de executar a instrução


Redirect( ) para assegurar que ela seja de fato executada. Realmente, essa é a razão pela qual
não colocamos o código na procedure SaveUserInfo( ) no evento Click do botão Submit.

Ordem do processamento de eventos para um formulário da Web


Considere a seguinte seqüência de processamento de evento para um formulário Web:

1. A página (.aspx) é solicitada.

2. O ViewState do controle é restaurado.

3. O evento Load de Page dispara.

4. Outros eventos de controle disparam.

5. O evento Unload de Page dispara.

Se colocássemos o código para salvar informações de usuário no evento Click do


botão Submit, o evento Load de Page dispararia primeiro e redirecionaria o usuário para
outra página, contornando todo o evento Click. É comum a avaliação de um estado par-
ticular dentro do evento Load para determinar o que deve ser processado.

Pré-preenchendo controles de lista


Dois dos controles no formulário de solicitação de download são controles DropDownList
para permitir que o usuário selecione seu país e estado/província. Obviamente, precisare-
mos preencher esses controles. O método pelo qual isso é feito atualmente é ler esses va-
lores de um arquivo contendo o par nome/valor. Então, por exemplo, um arquivo sta-
tes.txt contém dados como

AL=Alabama
AK=Alaska
Pré-preenchendo controles de lista 617

AB=Alberta
AS=American Samoa
AZ=Arizona
AR=Arkansas
BC=British Columbia
CA=California
CO=Colorado

Da mesma forma, um arquivo, countries.txt, contém dados semelhantes, mas com


países. A Listagem 26.2 é uma função que lê esses arquivos e preenche o DropDownList que
é passado como um parâmetro com o conteúdo do arquivo cujo nome também é passa-
do como um parâmetro.

LISTAGEM 26.2 Procedure PopulateDdlFromFile( )


1: procedure TWebForm1.PopulateDdlFromFile(aDDL: DropDownList;
2: aFileName: String);
3: var
4: sr: StreamReader;
5: FileLine: array of String;
6: NameStr: String;
7: ValueStr: String;
8: begin
9: if System.IO.File.Exists(aFileName) then
10: begin
11: sr := StreamReader.Create(System.IO.File.OpenRead(aFileName));
12: try
13: while sr.Peek > -1 do
14: begin
15: FileLine := sr.ReadLine.Split(['=']);
16: NameStr := FileLine[0];
17: ValueStr := FileLine[1];
18: aDDL.Items.Add(ListItem.Create(ValueStr, NameStr));
19: end;
20: finally
21: sr.Close;
22: end;
23: end;
24: end;

A Listagem 26.2 utiliza a função System.String.Split( ) (linha 15) para separar as en-
tradas de nome/valor, que são então utilizadas para preencher o DropDownList (linhas
13–18). Essa função é chamada no evento Page Init como mostrado na Listagem26.3.

LISTAGEM 26.3 Handler de evento Init


1: procedure TWebForm1.OnInit(e: EventArgs);
2: begin
618 Capítulo 26 Criando páginas Web com o ASP.NET

LISTAGEM 26.3 Continuação


3: InitializeComponent;
4: inherited OnInit(e);
5: PopulateDdlFromFile(ddlCountry,
6: Request.PhysicalApplicationPath+'countries.txt');
7: ddlCountry.Items.FindByText('United States').Selected := True;
8: PopulateDdlFromFile(ddlState,
9: Request.PhysicalApplicationPath+'states.txt');
10: end;

Realizando validação de formulário Web


Se você quiser coletar lixo de seus usuários, evite validar seus dados. Por outro lado, a uti-
lização adequada dos controles de validação pode ajudar seus usuários a inserir as infor-
mações corretas. Os controles de validações fornecidos pelo ASP.NET devem abranger a
maioria das necessidades. Quando não abrangerem, você pode fornecer sua própria lógi-
ca de validação personalizada.
Os controles de validação permitem especificar o tipo de validação que ocorre e a
mensagem de erro que ocorre quando o usuário insere dados incorretos. Os controles de
validação funcionam exatamente como os controles Server porque você os coloca no
formulário e configura suas propriedades. Quando o formulário for renderizado em
HTML, as rotinas de validação podem ser tratadas no lado cliente, impedindo assim uma
viagem de ida e volta até o servidor.

Validação no cliente versus no servidor


A validação pode ocorrer no cliente por meio de script ou no servidor por meio do co-
de-behind. A vantagem de realizar a validação no lado cliente é que não requer uma
viagem para o servidor. A desvantagem da validação no lado cliente é que nem todos
os navegadores suportam o script que realiza a validação, ou usuários têm essa capaci-
dade desativada. Felizmente, o ASP.NET cuida dessa questão realizando a validação
onde fizer sentido. Se puder ser feita no cliente porque o navegador a suporta, o script
de validação será renderizado no cliente. Se, entretanto, o navegador do cliente não
suportar o script, a validação ocorrerá no servidor. Você pode, por meio da proprieda-
de EnableClientScript comum, desativar o script do lado cliente se quiser. Mas isso não
é recomendado.

Classe BaseValidator
Todos os controles de validação descendem da classe BaseValidator. Essa classe define os
membros e métodos comuns de cada controle de validação. A Tabela 26.1 lista algumas
propriedades-chave da classe BaseValidator.
Realizando validação de formulário Web 619

TABELA 26.1 Propriedades-chave da classe BaseValidator


Propriedade Descrição
ControlToValidate Referencia o controle de entrada a validar.
Display Refere-se a um tipo enumerado ValidatorDisplay que determina o
comportamento de exibição de mensagens de erro de validação
(propriedade Text). Há três tipos de comportamento de exibição – none
(Text exibido somente no controle ValidationSummary), static (Text
exibido como parte do Page) e dynamic (Text adicionado dinamicamente
ao Page).
EnableClientScript Especifica se ativa ou não o script de validação no lado cliente.
Enabled Especifica se o controle de validação está ativado.
ErrorMessage Especifica o texto da mensagem de erro que é exibido inline ou em um
controle ValidationSummary quando a validação falha.
IsValid Especifica se o conteúdo inserido no controle de entrada é válido.
Page Refere-se ao Page no qual o controle de servidor reside.
Text Especifica o texto que é exibido quando a validação falha.

Existem outras propriedades relacionadas aos atributos de vídeo que não são listadas
na Tabela 26.1.
Mencionarei um método-chave da classe BaseValidator, que é o método Validate( ).
Esse método, quando invocado, realiza a validação contra o controle de entrada especifi-
cado (ControlToValidate) e atualiza a propriedade IsValid de maneira correspondente.

RequiredFieldValidator
O controle RequiredFieldValidator verifica se o controle de entrada contém um valor. O
Download Request Form apresentado neste capítulo contém três controles RequiredField-
Validator para assegurar que o usuário insira o nome e o sobrenome e seu correio eletrôni-
co no formulário. O seguinte código mostra a declaração de um desses controles no ar-
quivo .aspx:

<asp:requiredfieldvalidator id=RequiredFieldValidator1
runat="server"
errormessage="First name required"
controltovalidate="tbxFirstName">
</asp:requiredfieldvalidator>

O que você vê no arquivo .aspx são as configurações de propriedade feitas no Object


Inspector. Você pode explorar algumas configurações de vídeo, como fonte e cor, para
alcançar a aparência que você deseja para suas mensagens de erro.
Quando o usuário não insere um valor no controle especificado pela propriedade
ControlToValidate, a mensagem inserida na propriedade ErrorMessage é exibida quando o
usuário tenta enviar o formulário. A Figura 26.2 mostra o formulário enviado em que o
usuário não inseriu um sobrenome.
620 Capítulo 26 Criando páginas Web com o ASP.NET

FIGURA 26.2 Mensagem de erro RequiredFieldValidator.

A propriedade InitialValue do RequiredFieldValidator permite testar contra um valor


com o qual você inicializa o ControlToValidate. Isso permite impedir o usuário de enviar o
formulário com valores inicializados – em outras palavras, o usuário terá de modificar o
valor original. Basicamente, a propriedade InitialValue deve corresponder ao valor inicial
contido no ControlToValidate.

CompareValidator
CompareValidator permite comparar um valor inserido em um controle com um constante
ou um valor de outro controle. CompareValidator tem algumas propriedades-chave adicio-
nais não herdadas de BaseValidator, como mostrado na Tabela 26.2.

TABELA 26.2 Propriedades CompareValidator


Propriedade Descrição
ControlToCompare Refere-se ao controle cujo conteúdo será utilizado na comparação.
Operator Especifica um tipo enumerado com operadores de comparação, que poderiam
ser DataTypeCheck, Equal, GreaterThan, GreaterThanEqual, LessThan,
LessThanEqual ou NotEqual.
Type Especifica o tipo de dados com o qual valores sendo comparados são
convertidos antes de realizar a comparação. Possíveis valores são Currency,
Date, Double, Integer e String.
ValueToCompare Especifica um valor a ser utilizado na comparação.

A Listagem 26.4 ilustra o uso de vários controles CompareValidator que são utilizados
em uma aplicação de exemplo. Esse é o código parcial de um arquivo .aspx.
Realizando validação de formulário Web 621

LISTAGEM 26.4 Uso de controle CompareValidator


1. <asp:comparevalidator id=CompareValidator1
2. style="Z-INDEX: 8; LEFT: 182px; POSITION: absolute; TOP: 86px"
3. runat="server" operator="GreaterThan"
4. controltovalidate="txbxEndDate"
5. controltocompare="txbxBeginDate"
6. errormessage="End date must follow begin date"
7. type="Date">
8. </asp:comparevalidator>
9. <asp:comparevalidator id=CompareValidator2
10. style="Z-INDEX: 9; LEFT: 182px; POSITION: absolute; TOP: 142px"
11. runat="server" operator="LessThanEqual"
12. controltovalidate="txbxValue"
13. errormessage="Value must be less than or equal to 10"
14. type="Integer" valuetocompare="10">
15. </asp:comparevalidator>
16. <asp:comparevalidator id=CompareValidator3
17. style="Z-INDEX: 12; LEFT: 190px; POSITION: absolute; TOP: 206px"
18. runat="server" operator="DataTypeCheck"
19. controltovalidate="txbxDateType"
20. errormessage="Value must be a date" type="Date">
21. </asp:comparevalidator>

A Listagem 26.4 é uma parte de um arquivo .aspx para o Web Form mostrado na Fi-
gura 26.3.
Os dois primeiros campos de texto na Figura 26.3 demonstram a utilização de Compa-
reValidator para comparar valores de dois controles de formulário separados – nesse caso,
dois controles TextBox para conter uma data inicial e uma final. O CompareValidator1 para
esses controles assegura que a data final é maior ou menor que a data inicial.
CompareValidator2 realiza uma verificação contra um valor estático. Ele assegura que o
valor inserido em txbxValue é menor que ou igual ao valor 10. Por fim, CompareValidator3 ve-
rifica se a entrada em txbxDateType é date.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex02\.

RegularExpressionValidator
As expressões regulares são padrões de string que especificam a entrada que usuários po-
dem inserir em seus controles de entrada. Expressões regulares são discutidas no Capítu-
lo 11. O controle RegularExpressionValidator permite validar entrada de usuário com base
em uma expressão regular. Por exemplo, você pode especificar um padrão válido de
string para um número de telefone, CEP, endereço de correio eletrônico etc. A Listagem
26.5 mostra um arquivo .aspx parcial para um projeto demo que aceita um número de te-
lefone, SSN (Security Social Number, número de seguro social nos EUA), CEP, senha de
nove caracteres, correio eletrônico e uma URL válidos.
622 Capítulo 26 Criando páginas Web com o ASP.NET

FIGURA 26.3 WebForm CompareValidator.

LISTAGEM 26.5 Exemplo de RegularExpressionValidator


1: <asp:regularexpressionvalidator id=regexPhoneNo
2: runat="server" width="222px"
3: height="15px"
4: controltovalidate="txbxPhoneNo"
5: validationexpression="((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"
6: errormessage="Invalid phone number">
7: </asp:regularexpressionvalidator>
8: <asp:regularexpressionvalidator id=regexSSN
9: runat="server"
10: controltovalidate="txbxSSN"
11: validationexpression="\d{3}-\d{2}-\d{4}"
12: errormessage="Invalid SSN">
13: </asp:regularexpressionvalidator>
14: <asp:regularexpressionvalidator id=regexZipCode
15: runat="server"
16: controltovalidate="txbxZip"
17: validationexpression="\d{5}(-\d{4})?"
18: errormessage="Invalid zip code">
19: </asp:regularexpressionvalidator>
20: <asp:regularexpressionvalidator id=regexPassword
21: runat="server"
22: controltovalidate="txbxPassword"
23: validationexpression="\w{6,9}"
24: errormessage="Invalid password (6-9 chars)">
Realizando validação de formulário Web 623

LISTAGEM 26.5 Continuação


25: </asp:regularexpressionvalidator>
26: <asp:regularexpressionvalidator id=regexEmail
27: runat="server"
28: controltovalidate="txbxEmail"
29: validationexpression="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"
30: errormessage="Invalid email address"
31: display="Dynamic">
32: </asp:regularexpressionvalidator>
33: <asp:requiredfieldvalidator id=reqFldEmail
34: runat="server"
35: controltovalidate="txbxEmail"
36: errormessage="Email required"
37: display="Dynamic">
38: </asp:requiredfieldvalidator>
39: <asp:regularexpressionvalidator id=regexURL
40: runat="server"
41: controltovalidate="txbxURL"
42: validationexpression="http://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)?"
43: errormessage="Invalid URL">
44: </asp:regularexpressionvalidator>

A propriedade ValidationExpression de cada controle RegularExpressionValidator con-


tém a expressão regular específica. As outras propriedades são o mesmo que com os ou-
tros controles descendentes BaseValidator.
Observe que o controle txbxEmail é associado com dois controles de validação dife-
rentes (linhas 26, 33). O controle reqFldEmail é um RequiredFieldValidator para certificar-se
de que o usuário insere um correio eletrônico. O controle regexEmail assegura que o correio
eletrônico inserido é um endereço válido de correio eletrônico.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex03\.

RangeValidator
O controle RangeValidator permite especificar um valor mínimo e máximo que o usuário
pode inserir. Rigorosamente, os valores nas propriedades min/max são sempre strings,
mas como com todos os validadores, o Type herdado determina de que tipo é o valor de
entrada e o valor min/max convertidos antes da verificação. Esses podem ser datas,
strings de números ou valores monetários. O controle RangeValidator adiciona duas pro-
priedades-chave: MinimumValue e MaximumValue.
O seguinte código declara um controle RangeValidator que especifica um intervalo de
data para um controle TextBox:

<asp:rangevalidator id=RangeValidator1
style="Z-INDEX: 3; LEFT: 182px; POSITION: absolute; TOP: 38px"
runat="server"
errormessage="Date must be between Jan 1, 2003 and Dec 31, 2003"
624 Capítulo 26 Criando páginas Web com o ASP.NET

controltovalidate="TextBox1" type="Date">
</asp:rangevalidator>

O evento Load dessa página contém o seguinte código para especificar o formato cur-
to de data para entrada de usuário:

RangeValidator1.MinimumValue :=
DateTime(EncodeDate(2003, 1, 1)).ToShortDateString;
RangeValidator1.MaximumValue :=
DateTime(EncodeDate(2003, 12, 31)).ToShortDateString;

Dada essa especificação, o usuário não será capaz de enviar uma data fora do ano
2003.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex04\.

CustomValidator
Ocasionalmente, os controles de validação padrão não fornecem o tipo de validação que
você requer. Nesse evento, você pode utilizar a validação personalizada. O controle Cus-
tomValidator permite realizar validação personalizada no cliente ou servidor. O seguinte
código ilustra a declaração de um controle CustomValidator no arquivo .aspx:

<asp:customvalidator id=CustomValidator1
style="Z-INDEX: 3; LEFT: 166px; POSITION: absolute; TOP: 30px"
runat="server"
errormessage="Please enter an even number"
controltovalidate="TextBox1"
clientvalidationfunction="ClientValidate">
</asp:customvalidator>

A propriedade clientvalidationfunction recebe o nome da função do lado cliente. Essa


função existe no arquivo .aspx e pode estar no VBScript ou JavaScript. O seguinte código
ilustra a função do lado cliente para esse exemplo em VBScript:

<script Language="VBScript">
Sub ClientValidate(source, args)
args.IsValid = (args.Value mod 2) == 0
End Sub
</script>

Essa função verifica se o usuário insere um número par. Essa mesma rotina de vali-
dação também deve ser realizada no servidor, que, de todo modo, sempre ocorrerá. A
verificação do lado cliente é só para reduzir as viagens de ida e volta. A validação no
lado servidor sempre acontece para verificar os valores de entrada – o servidor nunca
pode ter certeza de que as verificações do lado cliente não foram desviadas. Você sim-
plesmente anexa um handler de evento ao evento ServerValidate. O seguinte código
ilustra esse handler de evento:
Formatação de formulário Web 625

procedure TWebForm1.CustomValidator1_ServerValidate(source: System.Object;


args: System.Web.UI.WebControls.ServerValidateEventArgs);
begin
args.IsValid := Convert.ToInt32(TextBox1.Text) mod 2 = 0;
end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex05\.

ValidationSummary
Em WebForms maiores, pode vir a ser difícil, para os usuários, examinar os vários erros sem
ter de rolar por todo o formulário. O controle ValidationSummary permite resumir todos os
erros em uma localização particular na página, como na parte superior da página. As pro-
priedades do controle ValidationSummary são listadas na Tabela 26.3.

TABELA 26.3 Propriedades ValidationSummary


Membro Descrição
DisplayMode Determina o formato de exibição dos erros. Esse valor de tipo enumerado
pode ser BulletList, List ou SingleParagraph.
EnableClientScript Determina se o controle ValidationSummary se atualiza a partir do script de
validação do lado cliente.
HeaderText Especifica o texto de cabeçalho exibido na parte superior do resumo.
ShowMessageBox Especifica se o texto de erro é exibido em uma caixa de mensagem.
ShowSummary Especifica se o texto de erro é exibido na linha (inline).

Ao configurar um controle Validator, é importante observar se a propriedade Error-


Text é utilizada para armazenar o texto a ser exibido pelo controle ValidationSummary e não
pela propriedade Text. Além disso, se quiser proibir que outros controles de validação
também exibam seu texto de erro, você deve configurar sua propriedade Display como Va-
lidationDisplay.None. A Figura 26.4 mostra os resultados de utilizar um controle Validati-
onSummary.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex06\.

Formatação de formulário Web


Ao projetar as páginas Web, você precisará considerar como fornecer uma aparência
mais agradável e atraente sem eliminar a legibilidade. Há duas maneiras de fazer isso em
ASP.NET Forms – pelas propriedades de formatação da classe WebControl básica e por meio
de Cascading Style Sheets (CSS).

Propriedades WebControl fortemente tipificadas


Os controles Web descendem da classe WebControl, que contém várias propriedades for-
matação de exibição. Essas propriedades são listadas na Tabela 26.4.
626 Capítulo 26 Criando páginas Web com o ASP.NET

FIGURA 26.4 Exemplo de resumo de validação.

TABELA 26.4 Propriedades WebControl de formatação fortemente tipificadas


Propriedade Descrição
AccessKey Especifica uma tecla de atalho de teclado para configurar o foco para o
WebControl.
BackColor Cor de fundo do WebControl.
BorderColor Cor da borda do WebControl.
BorderStyle Estilo da borda do WebControl.
BorderWidth Largura da borda do WebControl.
CssClass Especifica uma classe CSS com a qual o UWebControl será renderizado no cliente.
Font Especifica as propriedades Font para o WebControl.
ForeColor Especifica a cor de fundo do WebControl.
Height Especifica a altura do WebControl.
Style Uma coleção de atributos de estilo HTML que são renderizados no tag externo
do WebControl. Observe que os estilos fortemente tipificados como BackColor
sobrescreverão estilos nessa coleção.
TabIndex Especifica o índice de guia do controle do servidor Web.
ToolTip Especifica o texto de balão que aparece quando o cursor de mouse paira sobre o
WebControl.
Width Especifica a altura do WebControl.
Formatação de formulário Web 627

A Listagem 26.6 mostra a declaração para controles em um arquivo .aspx com confi-
gurações de estilo fortemente tipificado.

LISTAGEM 26.6 Estilos fortemente tipificados em WebControls


1: <asp:textbox id=TextBox1
2: style="Z-INDEX: 2; LEFT: 14px; POSITION: absolute; TOP: 6px"
3: runat="server" borderstyle="Ridge" width="307px"
4: font-names="Bradley Hand ITC" height="37"
5: bordercolor="Yellow" backcolor="#C0FFFF" font-size="Large"
6: forecolor="#000040" font-bold="True">Strongly Typed Settings</asp:textbox>
7: <asp:label id=Label1
8: style="Z-INDEX: 1; LEFT: 14px; POSITION: absolute; TOP: 54px"
9: runat="server" borderstyle="Solid" font-names="Comic Sans MS"
10: height="35px" bordercolor="Magenta" backcolor="#0000C0"
11: font-size="Large" forecolor="White">Strongly Typed Settings</asp:label>

NOTA
A Figura 26.5, mostrada mais adiante no capítulo, ilustra os resultados dessas configurações em
uma página Web.

Folhas de estilo em cascata


Uma folha de estilo em cascata (Cascading Style Sheet – CSS) é um arquivo especial que
contém definições para estilos que podem ser utilizados em páginas Web. Os arquivos de
CSS são excelentes porque permitem armazenar a definição de estilos em uma única locali-
zação. As alterações nos estilos em CSS afetam todos os controles que se referem ao estilo
modificado. Isso ocorre em todas as páginas que se referem esse arquivo CSS. A Listagem
26.7 mostra um arquivo CSS de exemplo contendo duas dessas definições de estilos.

LISTAGEM 26.7 Exemplo de arquivo .css


1: BODY {
2: }
3: .MyBodyText {
4: font-family: Verdana, Arial, Helvetica, sans-serif;
5: font-size: 9px;
6: font-style: normal;
7: color: #333300;
8: }
9: .MyHeader {
10: font-family: Verdana, Arial, Helvetica, sans-serif;
11: font-size: 12px;
12: font-style: normal;
13: font-weight: bold;
14: text-transform: capitalize;
15: color: #000000;
16: }
628 Capítulo 26 Criando páginas Web com o ASP.NET

As linhas 3–7 definem uma classe de estilo, .MyBodyText. As linhas 9–15 definem uma
segunda classe de estilo, .MyHeader. Para anexar esse arquivo .css a um arquivo .aspx, você
pode utilizar o atributo link como mostrado aqui dentro do bloco Head do arquivo .aspx:

<head>
<title></title>
<meta name="GENERATOR" content="Borland Package Library 7.1">
<link href="FormFormat.css" rel="stylesheet" type="text/css">
</head>

Nesse exemplo, o arquivo, FormFormat.css, é vinculado a esse arquivo .aspx. Nesse


ponto, você pode adicionar qualquer uma das duas classes definidas em FormFormat.css à
propriedade CssClass de qualquer WebControl. O seguinte código .aspx ilustra essa classe:

<asp:textbox id=TextBox2
style="Z-INDEX: 3; LEFT: 14px; POSITION: absolute; TOP: 102px"
runat="server" cssclass="MyHeader" borderstyle="None"
width="259px" height="24">cssclass style setting</asp:textbox>

NOTA
A Figura 26.5, mostrada mais adiante no capítulo, ilustra os resultados dessas configurações em
uma página Web.

Utilização da classe Style


Você pode utilizar uma classe Style para aplicar atributos de formatação a WebControls no
lado servidor que são renderizados como HTML no cliente. A Listagem 26.8 mostra
como utilizar essa classe e como aplicá-la a um WebControl pelo método ApplyStyle( ).

LISTAGEM 26.8 Utilizando a classe de estilo


1: procedure TWebForm1.Page_Load(sender: System.Object;
2: e: System.EventArgs);
3: var
4: MyStyle: Style;
5: begin
6: MyStyle := Style.Create;
7: MyStyle.BackColor := Color.Aqua;
8: MyStyle.ForeColor := Color.Yellow;
9: MyStyle.BorderColor := Color.BlueViolet;
10: MyStyle.BorderWidth := &Unit.Create(3);
11: MyStyle.Font.Name := 'Comic Sans MS';
12: Label2.ApplyStyle(MyStyle);
13: end;

A Figura 26.5 ilustra os resultados dessas configurações em uma página Web.


Navegando entre formulários Web 629

FIGURA 26.5 Exemplo de formatação de formulário.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex07\.

Navegando entre formulários Web


Ao desenvolver aplicações ASP.NET, considere que seus usuários ainda precisarão navegar
por várias páginas da aplicação e, em alguns casos, precisarão transportar dados de um for-
mulário anterior. Há várias maneiras de realizar isso, que são discutidas nesta seção.

Passando dados via POST


Com o processamento no lado servidor do Web Forms, o mecanismo POST não funciona
mais como funcionava no desenvolvimento ASP clássico. Você pode ainda postar com
POST outro Formulário Web especificando o Formulário Web alvo no atributo Action.
Entretanto, você não pode enviar dados juntamente com essa ação – pelo menos não au-
tomaticamente. Ainda, você pode recorrer ao processamento POST clássico para alcançar
essa técnica. Por exemplo, considerando dois Formulários Web, TWebForm1 poderia conter
o seguinte tag de formulário para enviar o usuário para TWebForm2 quando o formulário
fosse enviado:

<form method="Post" Action="WebForm2.aspx">

Observe que esse formulário não contém o atributo runat="server". Portanto, sob
esse esquema, você não pode utilizar nenhum controle de servidor. Em outras pala-
vras, você só deve utilizar controles HTML. Considerando que TWebForm1 contém dois
controles de entrada, FirstName e LastName, TWebForm2 seria capaz de acessar esses contro-
les pela propriedade HttpRequest.Params. Um método Load para TwebForm2 poderia ser se-
melhante a
630 Capítulo 26 Criando páginas Web com o ASP.NET

procedure TWebForm2.Page_Load(sender: System.Object;


e: System.EventArgs);
begin
Response.Write(System.String.Format(c_ty, [Request.Params['FirstName'],
Request.Params['LastName']]));
end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex08\.

Utilizando o método Response.Redirect( ) e QueryString


A técnica anterior poderia ser simples; entretanto, anula as vantagens de utilizar contro-
les de servidor com o code-behind etc. Outra maneira de enviar dados para um formulá-
rio diferente é invocar a URL para esse formulário utilizando o método HttpResponse.Redi-
rect( ). O método Redirect( ) redireciona o cliente para um URL especificado. Junto com
esse URL, você também enviaria parâmetros QueryString que podem ser recuperados no
formulário alvo. Por exemplo, suponha que você tem uma aplicação Web com dois
Web Forms. O TWebForm1 tem o seguinte código em seu evento Load para construir um
QueryString baseado no conteúdo de seus controles:

const
UrlStr = 'WebForm2.aspx?FirstName={0}&LastName={1}';
procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
begin
if IsPostBack then
Response.Redirect(System.String.Format(UrlStr, [txbxFirstName.Text,
txbxLastName.Text]));
end;

A URL que isso formaria quando o usuário pressionasse o botão submit seria

http://localhost/QueryString/WebForm2.aspx?FirstName="Xavier"&LastName="Pacheco"

Dentro do evento Load de TWebForm2, você pode fazer o seguinte código escrever uma
mensagem de boas-vindas para o usuário recuperando os parâmetros da propriedade Re-
quest.QueryString:

const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello,
Request.QueryString['FirstName'], Request.QueryString['LastName']));
end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex09\.


Navegando entre formulários Web 631

Utilizando o método Server.Transfer( )


O método Response.Redirect( ), embora simples, tem desvantagens. Primeiro, os parâme-
tros são passados na URL, onde eles são visíveis no campo de endereço do navegador. Se-
gundo, há limitações quanto ao tamanho de texto que alguns navegadores podem tratar
na string de URL. Terceiro, você só pode passar valores primitivos como strings e núme-
ros. Você não pode passar objetos para páginas diferentes. Por fim, o método Redirect( )
é uma solicitação para o navegador no cliente. É como se o servidor estivesse dizendo ao
cliente para examinar a URL sugerida.
Um método Server.Transfer( ) acontece no servidor e portanto o cliente não sabe o
que está acontecendo. Basicamente, o cliente solicita um URL e é enviado para outra
URL. Utilizar o método Server.Transfer( ) é mais complicado que utilizar o método Redi-
rect( ), mas ele apresenta mais capacidades, como ser capaz de passar objetos. Considere
os mesmos dois exemplos de páginas das seções anteriores. TWebForm1 não pode conter o
seguinte código no seu evento Load:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


begin
if IsPostBack then
begin
Context.Items.Add('FirstName', txbxFirstName.Text);
Context.Items.Add('LastName', txbxLastName.Text);
Server.Transfer('WebForm2.aspx');
end;
end;

Aqui, acessamos a propriedade Context e adicionamos os dois itens nome/valor a sua


propriedade Items. Então, a chamada para Transfer( ) é invocada, enviando a solicitação
de URL do usuário para WebForm2.aspx. Em TWebForm2, o seguinte código pode aparecer em
seu evento Load:

const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello, [
Context.Items['FirstName'], Context.Items['LastName']]));
end;

Embora exija um pouco mais de trabalho, é uma abordagem muito mais limpa. Ou-
tra maneira de realizar isso de um modo mais condizente com a OOP é adicionar proprie-
dades públicas que retornam os valores requeridos no formulário de invocação – nesse
caso, TWebForm1. O formulário alvo, TWebForm2, então pode obter uma referência a TWebForm1
pelo dicionário Context.
Então, por exemplo, TWebForm1 pode conter as seguintes propriedades:

property FirstName: String read get_FirstName;


property LastName: String read get_LastName;
632 Capítulo 26 Criando páginas Web com o ASP.NET

Os métodos getter dessas propriedades são semelhantes ao seguinte:

function TWebForm1.get_FirstName: String;


begin
Result := txbxFirstName.Text;
end;
function TWebForm1.get_LastName: String;
begin
Result := txbxLastName.Text;
end;

O evento Load de TWebForm2 agora conteria o seguinte código:

const
c_hello = 'hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
var
wf1: TWebForm1;
begin
wf1 := TWebForm1(Context.Handler);
Response.Write(System.String.Format(c_hello, [wf1.FirstName,
wf1.LastName]));
end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex10\.

Utilizando variáveis de sessão


Outra maneira de transferir o usuário para outra página Web junto com dados é utilizar va-
riáveis de sessão. Essa técnica é discutida em maiores detalhes no Capítulo 33, que lida
com o gerenciamento de estado. O exemplo aqui é fornecido para ilustrar como transferir
dados de uma página para outra utilizando essa técnica. Novamente, utilizando a mesma
aplicação dos exemplos anteriores, o handler de evento Load de TWebForm1 teria o seguinte
código para carregar o conteúdo de dois controles TextBox em variáveis de sessão:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


begin
if IsPostBack then
begin
Session.Add('FirstName', txbxFirstName.Text);
Session.Add('LastName', txbxLastName.Text);
Server.Transfer('WebForm2.aspx');
end;
end;

O evento Load de TWebForm2 conteria o seguinte para extrair esses valores do objeto Ses-
sion e copiar uma mensagem de boas-vindas utilizando esses valores:
Dicas e técnicas 633

const
c_hello = 'Hello {0} {1}!';
procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);
begin
Response.Write(System.String.Format(c_hello, [Session['FirstName'],
Session['LastName']]));
end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex11\.

Dicas e técnicas
As seções a seguir apresentam algumas técnicas comuns utilizadas por desenvolvedores
Web. Embora esses exemplos sejam específicos, cada um demonstra técnicas diferentes
que você provavelmente utilizará em várias circunstâncias, como criar arquivos na má-
quina de servidor, gerar imagens na memória etc.

Utilizando o controle Panel para simulação de múltiplos formulários


Ocasionalmente, você deve criar formulários que solicitam uma grande quantidade de da-
dos dos usuários, como uma pesquisa. É possível ser favorável à utilização de múltiplos for-
mulários Web .aspx a fim de evitar forçar o usuário a rolar por um único e extenso formulá-
rio. Isso poderia ser complicado já que você não deve só gerenciar múltiplos formulários,
mas também armazenar todos os dados que o usuário insere entre formulários para utiliza-
ção posterior. Uma abordagem mais fácil é utilizar seções de um único formulário, que são
alternadamente visíveis e invisíveis dependendo da seção do formulário que o usuário
pode estar preenchendo. Isso pode ser feito utilizando o controle Web Panel.
A Listagem 26.9 é código parcial para um exemplo que ilustra essa técnica utilizando
três controles Panel.

LISTAGEM 26.9 Simulação de múltiplas páginas com painéis


1: unit WebForm1;
2:
3: interface
4:
5: type
6: TWebForm1 = class(System.Web.UI.Page)
7: strict private
8: procedure btnPrev_Click(sender: System.Object; e: System.EventArgs);
9: procedure btnNext_Click(sender: System.Object; e: System.EventArgs);
10: strict private
11: procedure Page_Load(sender: System.Object; e: System.EventArgs);
12: private
13: procedure SetPanel(aOp: Integer);
14: end;
15:
634 Capítulo 26 Criando páginas Web com o ASP.NET

LISTAGEM 26.9 Continuação


16: implementation
17:
18: procedure TWebForm1.Page_Load(sender: System.Object;
19: e: System.EventArgs);
20: begin
21: if not IsPostBack then
22: begin
23: ViewState['CurrentPanel'] := Convert.ToString(1);
24: btnPrev.Enabled := False;
25: end;
26: end;
27:
28: procedure TWebForm1.btnNext_Click(sender: System.Object;
29: e: System.EventArgs);
30: begin
31: SetPanel(1);
32: end;
33:
34: procedure TWebForm1.btnPrev_Click(sender: System.Object;
35: e: System.EventArgs);
36: begin
37: SetPanel(-1);
38: end;
39:
40: procedure TWebForm1.SetPanel(aOp: Integer);
41: var
42: pnl: Panel;
43: pnlInt: Integer;
44: begin
45: // desativa o painel atual
46: pnlInt := Convert.ToInt32(ViewState['CurrentPanel']);
47: pnl := (FindControl('Panel'+pnlInt.ToString) as Panel);
48: pnl.Visible := False;
49: // Salva o novo painel atual e o torna visível
50: pnlInt := pnlInt + aOp;
51: ViewState['CurrentPanel'] := pnlInt.ToString;
52: pnl := (FindControl('Panel'+pnlInt.ToString) as Panel);
53: pnl.Visible := True;
54:
55: btnPrev.Enabled := pnlInt < > 1;
56: btnNext.Enabled := pnlInt < > 3;
57: end;
58:
59: end.

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex12\.


Dicas e técnicas 635

Nesse exemplo, a idéia é utilizar o ViewState para armazenar o painel atual sendo exi-
bido. Quando o usuário pressionar o botão btnNext ou btnPrev, o método SetPanel( ) é in-
vocado, que configura o painel atual como invisível (linhas 46–48) e então torna o novo
painel visível como determinado pelo parâmetro aOp. Esse parâmetro será um valor de -1
ou 1 dependendo do botão que o usuário tiver pressionado, o botão btnPrev ou btnNext.
Depois de o Panel apropriado tornar-se visível, sua posição é armazenada no ViewState sob
a chave CurrentPanel. Por fim, os botões btnPrev e btnNext são ativados/desativados com
base no CurrentPanel.

Fazendo o upload de um arquivo a partir do cliente


Permitir que o usuário carregue arquivos a partir de uma página Web tem sido com freqüên-
cia uma tarefa árdua. Sob o ASP.NET, o controle HtmlInputFile encapsula esse processo.
Você deve declarar o controle HtmlInputFile na página .aspx real já que não é um con-
trole que existe em modo de projeto. A Listagem 26.10 mostra o arquivo .aspx que de-
monstra o uso desse controle.

LISTAGEM 26.10 Utilizando o controle HtmlInputFile


1: <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas"
➥AutoEventWireup="True" Inherits="WebForm1.TWebForm1" %>
2: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
3: <script runat="server">
4: void btnSubmit_Click(Object sender, EventArgs e){
5: String fName =
6: System.IO.Path.GetFileName(UpFile.PostedFile.FileName);
7: String pName =
8: Server.MapPath(System.String.Format(".\\Files\\{0}", fName));
9: UpFile.PostedFile.SaveAs(pName);
10: Response.Write(pName);
11: }
12: </script>
13:
14: <html>
15: <head>
16: <title></title>
17: <meta name="GENERATOR" content="Borland Package Library 7.1">
18: </head>
19:
20: <body ms_positioning="GridLayout">
21: <form enctype="multipart/form-data" runat="server">
22: <input id=UpFile type=file name=UpFile runat="server">
23: <asp:button id=Button1
24: style="Z-INDEX: 1; LEFT: 6px; POSITION: absolute; TOP: 62px"
25: onclick=btnSubmit_Click runat="server" text="Upload File">
26: </asp:button>
27: </form>
28: </body>
29: </html>
636 Capítulo 26 Criando páginas Web com o ASP.NET

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex13\.

O controle HTMLInputFile é declarado na linha 22. Esse renderiza uma combinação


TextBox/Button como mostrado na Figura 26.6. Esse controle corresponde a um objeto
HTMLInputFile no servidor. É a combinação do controle "<input ...>" com o "type=file" e
"runat=server" que faz com que o servidor ASP.NET crie um controle HTMLInputFile para re-
presentá-lo em tempo de execução no servidor.
Outro passo necessário é alterar o formato da codificação padrão do Web Form. A li-
nha 21 faz isso especificando o atributo enctype no tag form. Quando o usuário pressionar
o botão Browse, uma caixa de diálogo File Open é exibida a partir da qual o usuário pode
selecionar o arquivo a ser carregado. Quando um arquivo é selecionado, o controle Text-
Box é atribuído ao caminho completo para o arquivo na máquina do usuário. O botão
Upload File invoca o handler de evento btn_SubmitClick definido no bloco de script C# no
lado servidor. Esse handler de evento realiza o processo de carregar o arquivo invocando
o método HttpInputFile.PostedFile.SaveAs( ) contra o arquivo escolhido pelo usuário. O
controle UpFile é essa instância HTMLInputFile que representa o arquivo carregado. Há ou-
tras propriedades do HttpPostedFile a partir das quais você pode examinar informações
sobre o arquivo que você pode querer pesquisar.

Enviando um e-mail de resposta a partir de um formulário


Ter a capacidade de enviar e-mail por meio de uma aplicação Web é crucial se você quiser
ser capaz de fornecer feedback oportuno para os visitantes do seu site Web. Considere o
Download Request Form anteriormente apresentado neste capítulo. Quando os usuários
solicitam o produto, a aplicação salva as informações inseridas por eles em um arquivo
de texto para recuperação posterior. Os endereços de e-mail desses usuários são incluídos
nessas informações. Em algum ponto, os usuários precisam receber via e-mail o link de
download do produto. Você não gostaria de processar manualmente essas solicitações

FIGURA 26.6 Carregando um arquivo.


Dicas e técnicas 637

visto que o site está gerando muitas solicitações por dia. Idealmente, o usuário recebe um
e-mail imediatamente depois de ter enviado sua solicitação.
O namespace System.Web.Mail contém duas classes que permitem enviar email por
meio do seu site Web. São as classes MailMessage e SmtpMail.
A Tabela 26.5 lista as várias propriedades da classe MailMessage.

TABELA 26.5 Propriedades MailMessage


Propriedade Descrição
Attachments Contém uma coleção de anexos.
Bcc Uma lista delimitada por ponto-e-vírgula de endereços de e-mail que receberá
uma cópia oculta cega do e-mail.
Body O corpo da mensagem de e-mail.
BodyEncoding Especifica o tipo de codificação para o corpo da mensagem de e-mail.
BodyFormat Especifica o formato da mensagem de e-mail. Esse pode ser Text ou HTML.
Cc Uma lista delimitada por ponto-e-vírgula de endereços de e-mail que receberá
uma cópia do e-mail.
From O endereço de e-mail do remetente .
Headers Os cabeçalhos personalizados que são transmitidos com o endereço de e-mail.
Priority A prioridade do endereço de e-mail.
Subject O assunto do endereço de e-mail.
To Uma lista delimitada por ponto-e-vírgula de destinatários de e-mail.

A classe SmtpMail é uma classe estática que encapsula o protocolo de SMTP. Ela trata
do envio de MailMessage por um servidor SMTP padrão da maneira especificada por sua
propriedade SmtpServer.
Revendo o Product Download Application, o WebForm principal agora tem uma proce-
dure adicional chamada SendEMail( ) como mostrado na Listagem 26.11.

LISTAGEM 26.11 Método SendEMail( ) para o Product Download Request App


1: procedure TWebForm1.SendEmail(aEmail: &String);
2: var
3: MailMsg: MailMessage;
4: sr: StreamReader;
5: begin
6: MailMsg := MailMessage.Create;
7: MailMsg.From := 'info@xapware.com';
8: MailMsg.&To := aEmail;
9: MailMsg.Bcc := 'sales@somehwere.com';
10: MailMsg.Subject := 'Your request to download XYZ Product';
11: MailMsg.BodyFormat := MailFormat.HTML;
12:
13: sr := StreamReader.Create(System.IO.File.OpenRead(
14: Request.PhysicalApplicationPath+'dnloadmail.htm'));
15: try
16: MailMsg.Body := sr.ReadToEnd;
638 Capítulo 26 Criando páginas Web com o ASP.NET

LISTAGEM 26.11 Continuação


17: finally
18: sr.Close;
19: end;
20:
21: SmtpMail.SmtpServer := 'localhost';
22: SmtpMail.Send(MailMsg);
23: end;

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex01\.

A maior parte desse código simplesmente está fazendo as atribuições adequadas para
as propriedades da classe MailMessage. As linhas 13–19 carregam uma template HTML que
é enviada como o corpo do e-mail Por último, o valor de 'localhost'é atribuído à proprie-
dade SmtpMail.SmtpServer, indicando que ela deve referir-se ao servidor SMTP instalado lo-
calmente. SmtpMail.Send( ) é invocado passando a instância MailMessage.

Exibindo imagens
Seguramente, em algum ponto, você precisará exibir ou gerar imagens se estiver criando
aplicações Web. Esta seção demonstra como gerar dinamicamente a miniatura de uma ima-
gem a partir de uma imagem maior em um diretório. A técnica demonstrada aqui utiliza
dois formulários Web. Um formulário principal contém um ImageButton cuja propriedade
ImageUrl aponta para outro formulário Web (ImageForm.aspx file). ImageForm.aspx contém o có-
digo que gera dinamicamente a miniatura de uma imagem que é exibida no ImageButton do
formulário principal. Essa técnica utiliza classes definidas nos namespaces System.Drawing e
System.Drawing.Imaging. A Listagem 26.12 mostra o handler de evento Load para o ImageForm.

LISTAGEM 26.12 Evento ImageForm Load – Criando uma miniatura


1: procedure TImageForm.Page_Load(sender: System.Object;
2: e: System.EventArgs);
3: var
4: bm: Bitmap;
5: im: System.Drawing.Image;
6: tni: System.Drawing.Image;
7: begin
8: // Pega a imagem de disco
9: im := System.Drawing.Image.FromFile(
10: Request.PhysicalApplicationPath+'z.jpg');
11: // Cria a miniatura
12: tni := im.GetThumbNailImage(96, 112, nil, IntPtr.Zero);
13: // Cria um mapa de bits a partir da imagem miniatura
14: bm := Bitmap.Create(tni);
15: // Salva a imagem no stream de saída de resposta.
16: bm.Save(Response.OutputStream, ImageFormat.jpeg);
17: end;
Dicas e técnicas 639

u Veja o exemplo desta seção no CD: \Code\Chapter 26\Ex14\.

Esse código utiliza várias classes definidas no namespace System.Drawing. Primeiro, uma
imagem é carregada a partir do disco (linha 9–10). Ela é convertida em uma miniatura invo-
cando o método GetThumbNailImage( ) da classe Image (linha 12). Por fim, um Bitmap é criado a
partir da miniatura e desenhado para o OutputStream da classe HttpResponse (linha 16). O Main-
Form não contém código especial. Ele meramente contém um componente ImageButton que
tem uma propriedade ImageUrl que aponta para o ImageForm. Você pode anexar um evento
Click ao ImageForm para redirecionar o navegador do usuário para a imagem maior real como

Response.Redirect('j.jpg');

O resultado desse código é mostrado na Figura 26.7.

Adicionando controles dinamicamente – um visualizador


de imagens baseado em miniaturas
Esse exemplo ilustra como você poderia adicionar programaticamente controles a uma
página Web. A idéia por trás dessa aplicação é ter uma visualização da imagem que cria
automaticamente miniaturas baseadas nas imagens que existem dentro do diretório do
site. As miniaturas são mantidas em um diretório separado. A partir das miniaturas, Ima-
geButtons individuais são adicionados à página, e ao clicar neles, eles redirecionam o na-
vegador cliente para exibir a imagem real. A Listagem 26.13 ilustra o método a partir do
formulário Web que realiza o processo de loop por meio de todas as imagens no diretório
do site, criando as miniaturas e ImageButtons para cada miniatura. Além disso, demons-
tra-se como um evento é dinamicamente atribuído ao ImageButton.

FIGURA 26.7 Imagem maior.


640 Capítulo 26 Criando páginas Web com o ASP.NET

LISTAGEM 26.13 Método GenerateThumbs( )


1: procedure TWebForm1.GenerateThumbs;
2: var
3: fArray: array of String;
4: i: integer;
5: bm: Bitmap;
6: tni: System.Drawing.Image;
7: im: System.Drawing.Image;
8: fName: String;
9: fs: FileStream;
10: ib: ImageButton;
11: begin
12: fArray := Directory.GetFiles(Request.PhysicalApplicationPath, '*.jpg');
13: for i := Low(fArray) to High(fArray) do
14: begin
15: fName := System.IO.Path.GetFileName(fArray[i]);
16: fName := 'tn_'+fName;
17: fName := Request.PhysicalApplicationPath+'thumbs\'+fName;
18: if not System.IO.File.Exists(fName) then
19: begin
20: im := System.Drawing.Image.FromFile(fArray[i]);
21: tni := im.GetThumbNailImage(96, 112, nil, IntPtr.Zero);
22: bm := Bitmap.Create(tni);
23: fs := FileStream.Create(fName, FileMode.OpenOrCreate,
24: FileAccess.Write);
25: try
26: bm.Save(fs, ImageFormat.Jpeg);
27: finally
28: fs.Close;
29: end;
30: end;
31: ib := ImageButton.Create;
32: ib.ImageUrl := fName;
33: ib.Attributes['runat'] := '"server"';
34: Include(ib.Click, ImageButton1_Click);
35: Panel1.Controls.Add(ib);
36: end;
37: end;

u Veja o exemplo dessa seção no CD: \Code\Chapter 26\Ex15\.

Esse exemplo só funciona com arquivos JPEG. O loop for (linha 13) itera por todos
os arquivos no diretório do site Web que têm uma extensão .jpg. O nome de arquivo uti-
lizado para a miniatura é o mesmo utilizado com a imagem com os caracteres "tn_" prefi-
xado ao nome de arquivo (linha 16). Se o arquivo de miniatura não existir, ele é criado
(linha 20–30). Por fim, um ImageButton é criado, vinculado ao arquivo de miniatura e adi-
cionado à página. A Figura 26.8 mostra a página Web resultante com miniaturas.
Dicas e técnicas 641

FIGURA 26.8 Página de imagens miniaturas.

Algumas coisas precisam ser indicadas aqui. Primeiro, a linha 34 adiciona o handler
de evento ImageButton1_Click( ) a cada ImageButton criado. Esse handler de evento contém
o código mostrado na Listagem 26.14.

LISTAGEM 26.14 Handler de evento ImageButton1_Click( )


1: procedure TWebForm1.ImageButton1_Click(sender: System.Object;
2: e: System.Web.UI.ImageClickEventArgs);
3: var
4: fName: String;
5: begin
6: with sender as ImageButton do
7: begin
8: fName := ImageUrl;
9: fName := System.IO.Path.GetFileName(fName);
10: fName := fName.Remove(0, 3);
11: Response.Redirect(fName);
12: end;
13: end;

Esse handler de evento reconstrói o nome de arquivo original e redireciona o nave-


gador do usuário para o arquivo JPEG, que exibe toda a imagem.
Além disso, observe que a imagem é realmente adicionada a um controle contêiner
Panel. A razão para isso é que ao adicionar WebControl's ao Page, eles devem existir dentro
do tag Form que contém o atributo runat="server". Caso contrário, um erro será gerado.
Acrescentando os controles a um Panel, isso garante que o controle será adicionado den-
tro do tag Form.
NESTE CAPÍTULO
— Data Binding CAPÍTULO 27
— Controles de lista com
vinculação de dados Construindo
— Controles iterativos de
vinculação de dados aplicações ASP.NET
— Trabalhando com o DataGrid

— O formulário de solicitação
com acesso a banco
de downloads e
administração orientados a de dados
banco de dados

O s modernos sites Web de hoje não são mais simples


páginas estáticas de informações. São aplicações Web
altamente interativas que permitem aos usuários finais
visualizar e manipular dados. O que era uma vez
disponível apenas para a plataforma desktop, aplicações
padrão Windows, também é agora disponível por meio
de navegadores Web modernos. De fato, os fornecedores
de software sabem que não é incomum clientes
desejarem uma versão Web de uma aplicação.
A Internet permitiu que as aplicações criassem um
paradigma muito diferente de como desenvolvemos
software. O ASP.NET combinado com ADO.NET ou BDP
oferece capacidades poderosas para trabalhar com
origens de dados. Este capítulo aprofunda-se no
desenvolvimento de aplicações Web orientadas a banco
de dados que utilizam essas capacidades.

Data Binding
No Capítulo 21 você aprendeu a vinculação de dados
ao trabalhar com o Windows Forms. A vinculação de
dados para Web Forms é semelhante. Talvez você se
lembre de que a vinculação de dados é o processo de
associar dados a partir de uma origem de dados para
uma propriedade de um controle. Alguns dos Web
Controls são especificamente projetados para ser
controles vinculados a dados. Esses são discutidos mais
adiante neste capítulo.
Data Binding 643

Há duas formas de vinculação de dados que você utilizará ao desenvolver aplicações


Web. A primeira é baseada em propriedade, ou vinculação de dados simples. A segunda é
baseada na origem de dados, ou vinculação de dados complexa.

Vinculação simples de dados


No ASP.NET, a vinculação de dados simples refere-se ao processo de associar um Web
Control com dados de banco de dados ou quaisquer dados armazenados direta ou indire-
tamente pelo Web Form – os campos, propriedades, métodos e assim por diante – com
uma propriedade de um Web Control. Para realizar isso, você deve incluir uma expressão
de vinculação de dados. A expressão segue a sintaxe mostrada aqui:

<%# data binding expression %>

Essa expressão pode fazer parte da porção valor do par atributo-valor na tag de aber-
tura do controle de servidor como mostrado aqui:

<asp:textbox id=TextBox1 style="Z-INDEX: 2; LEFT: 46px; POSITION:


absolute; TOP: 326px" runat="server" text="<%# MyName % >">

Ele também pode ser colocado em qualquer lugar dentro do corpo da página, utili-
zando a seguinte sintaxe:

Literal text <%# data binding expression %>

Por exemplo, dado um Web Form com um campo público string MyName, como mos-
trado aqui:

TWebForm1 = class(System.Web.UI.Page)
public
{ Public Declarations }
MyName: String;
end;

Você pode utilizar a expressão anterior com TextBox1 para vincular o TextBox ao con-
teúdo do campo String.
Você também pode vincular ao resultado de uma função. Por exemplo, a seguir é
mostrada a tag de abertura para um controle TextBox que vincula sua propriedade Text ao
resultado da função GetMyName( ), que retorna uma String:

<asp:textbox id=TextBox1
style="Z-INDEX: 2; LEFT: 22px; POSITION: absolute; TOP: 422px"
runat="server" text="<%# GetMyName( ) %>">
</asp:textbox>

Observe que embora o parêntese não seja necessário ao escrever o código Delphi,
você deve incluir o parêntese ao referenciar uma função na expressão de vinculação.
Além disso, o método que você referencia deve ser declarado como public ou protected ou
strict protected. A razão disso é que a expressão é de fato código de script; e o código de
644 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

script é declarado como no C#. Atualmente, não há nenhum suporte oficial para utilizar
Delphi como a linguagem de script nesse lançamento do Delphi.
A vinculação de dados realmente ocorre quando você invoca a função DataBind( ) da
Page. Você pode fazer isso no evento Load para uma página depois que estabelece os dados
aos quais o controle será vinculado. Em outras palavras, chame DataBind( ) depois de pre-
encher um array, preencher um DataSet, configurar o valor de um campo e assim por dian-
te. O método DataBind( ) converte expressões de vinculação de dados com os controles de
servidor.

u Localize o código no CD: \Code\Chapter 27\Ex01\.

Provavelmente a utilização mais comum da vinculação de dados simples seja a vin-


culação a um campo de banco de dados. Na realidade, você não tem de realmente vincular
um controle a um campo de banco de dados, mas, em vez disso, a uma função ou pro-
priedade que fornece o valor do campo no banco de dados. A razão disso é simular o conceito
de um cursor (ou registro atual) dentro do formulário. Um exemplo ilustrará esse con-
ceito.
A Listagem 27.1 contém um extrato de um exemplo no CD, que ilustra como escre-
ver uma página que pode navegar por uma tabela de banco de dados. Esse exemplo exibe
três campos do registro atual. Esse registro pseudo-atual é realizado mantendo uma refe-
rência a um número do registro no Session. Esse número do registro é incrementado ou
decrementado baseado na direção que o usuário seleciona e é utilizado na recuperação
do registro a exibir.

LISTAGEM 27.1 Navegação de tabela em uma página da Web


1: type
2:
3: TRowMove = (rmFirst, rmPrev, rmNext, rmLast);
4:
5: TWebForm1 = class(System.Web.UI.Page)
6: strict private
7: procedure Page_Load(sender: System.Object; e: System.EventArgs);
8: private
9: procedure ReBind;
10: function GetField(aFieldName: String): String;
11: procedure SetRowNum(aRowMove: TRowMove);
12: public
13: function GetFirstName: String;
14: function GetLastName: String;
15: function GetTitle: String;
16: end;
17:
18: implementation
19:
20: procedure TWebForm1.Page_Load(sender: System.Object;
21: e: System.EventArgs);
22: begin
Data Binding 645

LISTAGEM 27.1 Continuação


23: ReBind;
24: end;
25:
26: function TWebForm1.GetFirstName: String;
27: begin
28: Result := GetField('FirstName');
29: end;
30:
31: function TWebForm1.GetLastName: String;
32: begin
33: Result := GetField('LastName');
34: end;
35:
36: function TWebForm1.GetTitle: String;
37: begin
38: Result := GetField('Title');
39: end;
40:
41: procedure TWebForm1.btnLast_Click(sender: System.Object;
42: e: System.EventArgs);
43: begin
44: SetRowNum(rmLast);
45: end;
46:
47: procedure TWebForm1.btnNext_Click(sender: System.Object;
48: e: System.EventArgs);
49: begin
50: SetRowNum(rmNext);
51: end;
52:
53: procedure TWebForm1.btnPrev_Click(sender: System.Object;
54: e: System.EventArgs);
55: begin
56: SetRowNum(rmPrev);
57: end;
58:
59: procedure TWebForm1.btnFirst_Click(sender: System.Object;
60: e: System.EventArgs);
61: begin
62: SetRowNum(rmFirst);
63: end;
64:
65: function TWebForm1.GetField(aFieldName: &String): String;
66: var
67: dt: DataTable;
68: rowNum: System.Int32;
69: begin
70: dt := Session['Employees'] as DataTable;
646 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.1 Continuação


71: rowNum := Session['RowNum'] as System.Int32;
72: result := dt.Rows[RowNum][aFieldName].ToString;
73: end;
74:
75: procedure TWebForm1.ReBind;
76: var
77: rowNum: System.Int32;
78: numRows: System.Int32;
79: begin
80: txbxFirstName.DataBind;
81: txbxLastName.DataBind;
82: txbxTitle.DataBind;
83:
84: rowNum := Session['RowNum'] as System.Int32;
85: numRows := Session['NumRows'] as System.Int32;
86:
87: btnPrev.Enabled := rowNum > 1;
88: btnFirst.Enabled := rowNum > 1;
89: btnNext.Enabled := rowNum < numRows;
90: btnLast.Enabled := rowNum < numRows;
91:
92: Label4.Text := 'Number of rows: '+numRows.ToString;
93: Label5.Text := 'Row number: '+rowNum.ToString;
94:
95: end;
96:
97: procedure TWebForm1.SetRowNum(aRowMove: TRowMove);
98: var
99: rowNum: System.Int32;
100: numRows: System.Int32;
101: begin
102: rowNum := Session['RowNum'] as System.Int32;
103: numRows := Session['NumRows'] as System.Int32;
104:
105: case aRowMove of
106: rmFirst : rowNum := 1;
107: rmPrev : rowNum := rowNum - 1;
108: rmNext : rowNum := rowNum + 1;
109: rmLast : rowNum := numRows;
110: end; // case
111:
112: Session.Add('RowNum', System.Object(RowNum));
113: ReBind;
114: end;
115:
116: end.

u Localize o código no CD: \Code\Chapter 27\Ex02\.


Controles de lista com vinculação de dados 647

Primeiro, há três funções que retornam uma string correspondente a um dos três
campos de banco de dados. Essas funções são GetFirstName( ), GetLastName( ) e GetTitle( )
(linhas 26–39). Essas são as funções que são referenciadas na expressão de vinculação de
dados no arquivo .aspx. A seguir um exemplo:

<asp:textbox id=txbxLastName
style="Z-INDEX: 1; LEFT: 134px; POSITION: absolute;
TOP: 118px" runat="server" height="24" width="180px"
text="<%# GetLastName( )%>">
</asp:textbox>

Cada função chama a função GetField( ), que é implementada nas linhas 65–73.

NOTA
Antes de continuar a explicação, devo acrescentar que você pode assumir que um DataTable, sua
contagem de linha e um inteiro que representa um número atual de linha são todos armazenados
no objeto Session. Ainda não discuti o objeto Session; esse é um tópico para o Capítulo 33. Por
enquanto, suponha apenas que ele é um lugar para guardar objetos que sobrevivem durante a
sessão inteira. Em outras palavras, os dados não são perdidos entre solicitações.

GetField( ) aceita um único parâmetro String que representa o nome de campo a


obter. GetField( ) primeiro recupera o objeto DataTable do objeto Session. Ele também
recupera o valor inteiro RowNum. A partir desses valores, ele retorna o valor do campo repre-
sentado pelo parâmetro aFieldName. Isso é basicamente como o controle é vinculado ao
controle.
A demo tem alguma funcionalidade adicional não específica à vinculação de dados
mas que vale a pena mencionar. O formulário contém quatro botões de navegação
(btnFirst, btnPrev, btnNext e btnLast). O evento Click para cada botão chama uma procedure
SetRowNum( ) (linhas 97–114), que aceita um parâmetro TRowMove que o instrui sobre como
navegar. Essa função simplesmente configura o valor da entrada RowNum no objeto Session
de maneira correspondente.
A função ReBind( ) (linhas 75–76), que é chamada pelo handler de evento Page_
Load( ) e procedure SetRowNum( ), invoca o método DataBind( ) para cada um dos controles
vinculados e ativa/desativa os botões com base na linha atual que está no início ou no
fim do DataTable.

Vinculação complexa de dados


A vinculação de dados complexa envolve vincular controles de lista ou iterativos a dados
em uma lista ou coluna. No ASP.NET, os controles de lista são CheckBoxList, DropDownList,
ListBox e RadioButtonList. Os controles iterativos são Repeater, DataList e DataGrid.

Controles de lista com vinculação de dados


Em geral, os controles de lista são vinculados a uma coleção (um implementador de
ICollection) por meio de sua propriedade DataSource.
648 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

Controle CheckBoxList
O controle CheckBoxList representa um grupo de caixa de seleção multisseleção que pode
ser associado com uma origem de dados. CheckBoxList descende do ListControl definido
no namespace System.Web.UI.WebControls. Outros controles de lista discutidos neste capítu-
lo também descendem de ListControl. ListControl contém algumas propriedades-chave
que seus descendentes, como CheckBoxList, herdam. Essas são listadas na Tabela 27.1.

TABELA 27.1 Propriedades-chave do ListControl


Propriedade Descrição
Items Retorna uma coleção de itens contidos no CheckBoxList.
DataSource Configura a origem de dados que preenche os controles.
DataMember Configura a tabela em uma origem de dados para vincular ao controle.
DataTextField Configura o campo de uma origem de dados que fornece o texto para
cada item de lista.
DataValueField Configura o campo de uma origem de dados que fornece o valor para
cada item.
DataTextFormatString Uma string de formatação que determina como o texto é formatado em
controles vinculados.
SelectedIndex Índice do item selecionado ou índice mais baixo em uma lista de seleção
múltipla.
SelectedItem O item selecionado ou item com o índice mais baixo em uma listagem
de seleção múltipla.
SelectedValue O valor do item selecionado no controle.

O controle CheckBoxList contém duas propriedades-chave adicionais – RepeatLayout e


RepeatDirection. RepeatLayout permite configurar o layout das caixas de seleção como em
uma tabela (RepeatLayout.Table) ou sem estrutura de tabela (RepeatLayout.Flow). RepeatDirec-
tion especifica se os itens CheckBoxList são exibidos vertical ou horizontalmente.

Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância CheckBoxList:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


var
i: integer;
begin
for i := 0 to 9 do
MyArray[i] := 'Item '+i.ToString;
DataBind;
end;

u Localize o código no CD: \Code\Chapter 27\Ex03\.


Controles de lista com vinculação de dados 649

Esse exemplo assume que o controle CheckBoxList é tag de abertura no arquivo .aspx
que aparece como

<asp:checkboxlist id=CheckBoxList1
style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute; TOP: 62px"
runat="server" repeatdirection="Horizontal"
repeatlayout="Flow" datasource="<%# MyArray %>"
width="331px" height="26">
</asp:checkboxlist>

Esse exemplo geraria a saída mostrada na Figura 27.1. Note que configurar a proprie-
dade RepeatDirection como Horizontal afeta o layout do controle.

FIGURA 27.1 CheckBoxList vinculado a um array.

Vinculando a uma tabela de banco de dados


Ao vincular um CheckBoxList a um Table dentro de um DataSet, é provável que você tam-
bém queira utilizar as propriedades DataTextFields e DataValueField. A Listagem 27.2 ilustra
a aparência do código que configura essas vinculações.

LISTAGEM 27.2 Vinculando um CheckBoxList a uma tabela


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM categories';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
650 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.2 Continuação


11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'Categories');
15: dt := ds.Tables['Categories'];
16: CheckBoxList1.DataSource := dt.DefaultView;
17: CheckBoxList1.DataTextField := 'CategoryName';
18: CheckBoxList1.DataValueField := 'CategoryID';
19: DataBind;
20: sqlcn.Close;
21: end;

u Localize o código no CD: \Code\Chapter 27\Ex04\.

As linhas 16–18 realizam a atribuição às propriedades do controle CheckBoxList. A linha


17 determina o campo da tabela de categorias que é exibido no navegador (ver Figura 27.2).
A linha 18 determina o campo que representa o valor retornado pelos itens verificados.

Controle DropDownList
O controle DropDownList é semelhante ao ComboBox do Windows Form. Permite aos usuários
selecionar um único valor de uma lista de itens. O DropDownList descende do ListControl e,
portanto, herda suas propriedades como da maneira listada na Tabela 27.1.

Vinculando a um array
O próximo handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância DropDownList:

FIGURA 27.2 CheckBoxList vinculado a uma tabela.


Controles de lista com vinculação de dados 651

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


var
i: integer;
begin
for i := 0 to 9 do
MyArray[i] := 'Item '+i.ToString;
DataBind;
end;

u Localize o código no CD: \Code\Chapter 27\Ex05\.

Esse exemplo assume que o controle DropDownList é tag de abertura no arquivo .aspx
que aparece como

<asp:dropdownlist id=DropDownList1
style="Z-INDEX: 1; LEFT: 62px; POSITION: absolute; TOP: 78px"
runat="server" datasource="<%# MyArray %>" height="22"
width="195px">
</asp:dropdownlist>

Esse exemplo geraria a saída mostrada na Figura 27.3.

Vinculando a uma tabela de banco de dados


Ao vincular um DropDownList a um Table dentro de um DataSet, você utilizará as proprieda-
des DataTextFields e DataValueField. A Listagem 27.3 ilustra a aparência do código que con-
figura essas vinculações.

FIGURA 27.3 DropDownList vinculado a um array.


652 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.3 Vinculando um DropDownList a uma tabela


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM categories';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'Categories');
15: dt := ds.Tables['Categories'];
16: DropDownList1.DataSource := dt.DefaultView;
17: DropDownList1.DataTextField := 'CategoryName';
18: DropDownList1.DataValueField := 'CategoryID';
19: DataBind;
20: sqlcn.Close;
21: end;

u Localize o código no CD: \Code\Chapter 27\Ex06\.

Você notará que esse código é quase idêntico ao da Listagem 27.2 com exceção das
linhas 16–18, que operam em um controle DropDownList em vez de em um controle Check-
BoxList. A Figura 27.4 mostra a saída dessa listagem.

FIGURA 27.4 DropDownList vinculado a uma tabela.


Controles de lista com vinculação de dados 653

Controle ListBox
O controle ListBox é uma lista vertical de itens que suporta seleção de único item ou de
múltiplos itens. Quando a lista excede os limites do controle, ela é rolável. ListBox des-
cende do ListControl e, portanto, herda suas propriedades como as que estão listadas na
Tabela 27.1. ListControl tem as propriedades adicionais de RepeatDirection e RepeatLayout.
Essas propriedades servem ao mesmo propósito que as propriedades do mesmo nome
para o controle CheckBoxList.

Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância ListBox:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


var
i: integer;
begin
for i := 0 to 9 do
MyArray[i] := 'Item '+i.ToString;
DataBind;
end;

u Localize o código no CD: \Code\Chapter 27\Ex07\.

Esse exemplo assume que a tag de abertura do controle ListBox no arquivo .aspx apa-
rece como

<asp:listbox id=ListBox1
style="Z-INDEX: 1; LEFT: 46px; POSITION: absolute; TOP: 38px"
runat="server" height="123px" width="171px"
datasource="<%# MyArray %>" selectionmode="Multiple">
</asp:listbox>

Observe que o atributo selectionmode é configurado como Multiple para permitir mul-
tisseleção.
Esse exemplo geraria a saída mostrada na Figura 27.5.

Vinculando a uma tabela de banco de dados


Quando vincular um ListBox para um Table dentro de um DataSet, você utilizará as proprie-
dades DataTextFields e DataValueField como com os controles anteriores. A Listagem 27.4
ilustra a aparência do código que configura essas vinculações.
654 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

FIGURA 27.5 ListBox vinculado a um array.

LISTAGEM 27.4 Vinculando um ListBox a uma tabela


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM categories';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'Categories');
15: dt := ds.Tables['Categories'];
16: ListBox1.DataSource := dt.DefaultView;
17: ListBox1.DataTextField := 'CategoryName';
18: ListBox1.DataValueField := 'CategoryID';
19: DataBind;
20: sqlcn.Close;
21: end;

u Localize o código no CD: \Code\Chapter 27\Ex08\.

Novamente, esse código é praticamente o mesmo que aparece nas Listagens 27.2 e
27.3, exceto que o controle agora é um controle ListBox. A Figura 27.6 mostra a saída des-
sa listagem.
Controles de lista com vinculação de dados 655

FIGURA 27.6 ListBox vinculado a uma tabela.

Controle RadioButtonList
O controle RadioButtonList encapsula um grupo de RadioButton. RadioButtonList descende
de ListControl e herda suas propriedades como mostrado na Tabela 27.1.

Vinculando a um array
O seguinte handler de evento Page_Load( ) ilustra o preenchimento de um array of String
e a vinculação desse array a uma instância RadioButtonList:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


var
i: integer;
begin
for i := 0 to 9 do
MyArray[i] := 'Item '+i.ToString;
DataBind;
end;

u Localize o código no CD: \Code\Chapter 27\Ex09\.

Esse exemplo assume que a tag de abertura do controle RadioButtonList no arquivo


.aspx aparece como

<asp:radiobuttonlist id=RadioButtonList1
style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute; TOP: 14px"
runat="server" datasource="<%# MyArray %>" height="26"
width="211px" repeatlayout="Flow"
repeatdirection="Horizontal" repeatcolumns="3">
</asp:radiobuttonlist>
656 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

Esse exemplo configura o atributo repeatdirection como Horizontal e o atributo repeat-


columns como 3, resultando na saída mostrada na Figura 27.7.

FIGURA 27.7 RadioButtonList vinculado a um array.

Vinculando a uma tabela de banco de dados


Quando vincular um RadioButtonList para um Table dentro de um DataSet, você utilizará as
propriedades DataTextFields e DataValueField como com os controles anteriores. A Lista-
gem 27.5 ilustra a aparência do código que configura essas vinculações.

LISTAGEM 27.5 Vinculando um RadioButtonList a uma tabela


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM categories';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'Categories');
15: dt := ds.Tables['Categories'];
16: RadioButtonList1.DataSource := dt.DefaultView;
17: RadioButtonList1.DataTextField := 'CategoryName';
Controles iterativos de vinculação de dados 657

LISTAGEM 27.5 Continuação


18: RadioButtonList1.DataValueField := 'CategoryID';
19: DataBind;
20: sqlcn.Close;
21: end;

u Localize o código no CD: \Code\Chapter 27\Ex10.

A Figura 27.8 mostra a saída dessa listagem.

FIGURA 27.8 RadioButtonList vinculado a uma tabela.

Controles iterativos de vinculação de dados


Controles iterativos são também vinculados por meio de sua propriedade Datasource. Con-
troles iterativos aplicam uma template HTML a cada item dentro da coleção, fornecendo
mais opções personalizáveis para apresentação de dados.

Controle Repeater
O controle Repeater itera por uma coleção de dados e utiliza templates para determinar
como cada item na coleção será renderizado. As templates são construções que permitem
aplicar a formatação a itens que o controle Repeater exibe. Há cinco tipos de templates,
que são listadas na Tabela 27.2.

TABELA 27.2 Templates do controle Repeater


Template Descrição
HeaderTemplate A formatação especificada nessa template é renderizada antes de
qualquer um dos itens. Essa template não contém vinculações de dados.
658 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

TABELA 27.2 Continuação


Template Descrição
ItemTemplate A formatação especificada nessa template é renderizada para cada item
ou linha, da origem de dados. Essa template talvez contenha expressões
de vinculação de dados.
AlternatingItemTemplate A formatação especificada nessa template opcional é renderizada para
cada item ou linha alternada. Ela funciona da mesma maneira que o
ItemTemplate, mas para linhas de números ímpares.
SeparatorTemplate A formatação especificada nessa template especifica a HTML entre cada
linha.
FooterTemplate O oposto da HeaderTemplate. É renderizada uma vez depois de todas as
linhas terem sido renderizadas. Não pode conter expressões de
vinculação de dados.

A Tabela 27.3 lista algumas propriedades e eventos-chave da classe Repeater.

TABELA 27.3 Propriedades e eventos da classe Repeater


Propriedade/evento Descrição
DataMember Uma propriedade que referencia a tabela específica na propriedade
DataSource, a qual o Repeater vinculará.
DataSource Uma propriedade que referencia a origem de dados que fornece a lista de
dados vinculáveis.
Items Uma propriedade que retorna a coleção de objetos RepeaterItem.
ItemCommand Um evento que dispara quando um botão de comando é pressionado no
Repeater.
ItemCreated Um evento que dispara quando um item é criado no Repeater.
ItemDataBound Um evento que dispara depois de um item ser vinculado, mas antes de ser
renderizado em HTML.

Um exemplo simples vinculado à tabela employees do banco de dados Northwind


ilustrará o uso do controle Repeater e os vários cabeçalhos. A Listagem 27.6 mostra o
evento Load de um Web Form que contém um controle Repeater.

LISTAGEM 27.6 Exemplo de controle Repeater


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM employees';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
Controles iterativos de vinculação de dados 659

LISTAGEM 27.6 Continuação


11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'Employees');
15: dt := ds.Tables['Employees'];
16:
17: Repeater1.DataSource := dt.DefaultView;
18: DataBind;
19: sqlcn.Close;
20: end;

u Localize o código no CD: \Code\Chapter 27\Ex11\.

A Listagem 27.6 não é muito diferente da listagem do exemplo anterior. Semelhante


aos outros controles Web, o Repeater tem uma propriedade Datasource à qual você pode atri-
buir a origem de dados a que ele será vinculado (linha 17). Nesse caso, vamos fazer a vin-
culação ao DataTable para representar a tabela employees. A declaração do controle Repea-
ter dentro do arquivo .aspx é o local em que a maioria das informações sobre como ele
renderizará é especificada. A Listagem 27.7 mostra essa declaração.

LISTAGEM 27.7 Declaração do controle Repeater (.aspx)


1: <asp:repeater id=Repeater1 runat="server">
2: <headertemplate>
3: <table>
4: <tr height=12>
5: <td bgcolor="#e0e0e0" width=100>
6: <b>First Name</b>
7: </td>
8: <td bgcolor="#e0e0e0" width=100>
9: <b>Last Name</b>
10: </td>
11: </tr>
12: </headertemplate>
13: <itemtemplate>
14: <tr>
15: <td><%# ((System.Data.DataRowView)Container.DataItem)["FirstName"]%>
16: </td>
17: <td><%# DataBinder.Eval(Container.DataItem, "LastName")%>
18: </td>
19: </tr>
20: </itemtemplate>
21: <separatortemplate>
22: <tr>
23: <td colspan="2" align="center"> — – – – – – – – – – – – –
24: </td>
25: </tr>
660 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.7 Continuação


26: </separatortemplate>
27: <footertemplate>
28: </table>
29: </footertemplate>
30: </asp:repeater>

u Localize o código no CD: \Code\Chapter 27\Ex11\.

As linhas 2–12 contêm o HeaderTemplate. Esse exemplo especifica a linha de uma tabe-
la com duas colunas para representar um cabeçalho. O FooterTemplate (linhas 27–29)
completa essa tabela. O ItemTemplate (linhas 13–20) especifica a formatação para cada
item no Repeater.
Um controle Repeater contém uma coleção de objetos RepeaterItem. Um objeto Repeater-
Item é vinculado a um item da origem de dados. Nesse exemplo, cada item é um DataRow-
View. As linhas 15 e 17 utilizam a propriedade Container.DataItem, que retorna a classe à
qual o RepeaterItem está vinculado. Você notará que as linhas 15 e 17 são diferentes. Fiz
isso intencionalmente para ilustrar um ponto (ver o quadro lateral sobre esse ponto).

AVALIANDO EXPRESSÕES DE VINCULAÇÃO


A expressão de vinculação de dados <%# data-binding expression %> é avaliada pelo CLR com
base na linguagem especificada n diretiva a <%@ Page %>. Portanto, a sintaxe que você utiliza den-
tro da expressão deve adaptar-se a tudo que a linguagem espera. Por exemplo, em muitos exem-
plos da utilização de Container.DataItem que você talvez veja on-line ou em outras publicações,
você verá a seguinte sintaxe:
<td><%# Container.DataItem("LastName")%>
Isso funcionará se a linguagem utilizada pela página for VB. Se, entretanto, a linguagem for C#, ela
resultará em um erro. Em C#, você deve converter a expressão para o tipo apropriado como mos-
trado na linha 15 da Listagem 27.6. A maneira como você converte isso, naturalmente, depende
da classe que Container.DataItem referencia.
As seguintes linhas ilustram algumas diferenças entre expressões VB e C# quando o Repeater é vin-
culado a um objeto DataView.
// VB:
<%# Container.DataItem("FirstName") %>

// C#:
<%# ((DataRowView)Container.DataItem)["FirstName"] %>
Quando o Repeater é vinculado a um DataTable
// VB:
<%# Container.DataItem("FirstName") %>

// C#:
<%# ((DataRow)Container.DataItem)["FirstName"] %>
Controles iterativos de vinculação de dados 661

Para amenizar a necessidade de ter de lembrar-se de como converter o DataItem, a Microsoft for-
neceu a função auxiliar DataBinder.Eval( ). Essa função descobre a coerção de tipo adequada e
formata a string resultante para você. Entretanto, seja cuidadoso porque essa função conta com
reflection para determinar essas informações. Isso pode ser caro em termos de desempenho. A
melhor opção é descobrir a coerção de tipo (typecast) adequada e utilizá-la. Outra opção seria ex-
por um método no WebForm que retorna o DataRow ou DataRowView atual, aliviando a necessidade
para uma coerção de tipo de script.

As linhas 21–26 ilustram o SeparatorTemplate. Essa template fornece a HTML, um


conjunto de traços que aparecerá entre cada linha. A Figura 27.9 mostra a saída desse
programa.
Alternativamente, para separar linhas, você pode utilizar o AlternatingItemTemplate
substituindo o SeparatorTemplate pelo seguinte código:

<alternatingitemtemplate>
<tr>
<td bgcolor="#fff7d7"><%# DataBinder.Eval(Container.DataItem, "FirstName")%>
</td>
<td bgcolor="#fff7d7"><%# DataBinder.Eval(Container.DataItem, "LastName")%>
</td>
</tr>
</alternatingitemtemplate>

A saída muda para aquela mostrada na Figura 27.10.

FIGURA 27.9 Saída do controle Repeater.


662 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

Controle DataList
O controle DataList é basicamente o controle Repeater, mas com funcionalidade estendi-
da, particularmente em torno das capacidades gráficas de layout. Por exemplo, DataList
adiciona o SelectedItemTemplate e EditItemTemplate. Portanto, suporta a idéia de selecionar e
editar um item. Você pode alterar o layout direcional de itens – vertical ou horizontal.
Ele tem mais eventos que a classe Repeater. Além das templates listadas na Tabela 27.2, Da-
taList tem as templates mostradas na Tabela 27.4.

TABELA 27.4 Templates adicionais do controle DataList


Template Descrição
SelectedItemTemplate A formatação especificada nessa template é renderizada para o item, ou
linha, da origem de dados quando um item é selecionado.
EditItemTemplate A formatação especificada nessa template é renderizada para o item, ou
linha, da origem de dados quando um item é editado.

FIGURA 27.10 Saída do controle Repeater, itens alternativos.

A Tabela 27.5 lista algumas propriedades e eventos-chave da classe DataList.

TABELA 27.5 Propriedades e eventos DataList


Propriedade/evento Descrição
AlternatingItemStyle A propriedade que referencia os estilos do item alternativo.
EditItemIndex A propriedade que referencia o índice do item sendo atualmente
editado.
EditItemStyle A propriedade que referencia as propriedades de estilo do item sendo
atualmente editado.
FooterStyle A propriedade que referencia as propriedades de estilo do rodapé do
DataList.
Controles iterativos de vinculação de dados 663

TABELA 27.5 Continuação


Propriedade/evento Descrição
GridLines A propriedade que referencia as propriedades de estilo da linha do grid.
HeaderStyle A propriedade que referencia as propriedades de estilo de cabeçalho.
Items A propriedade referencia uma coleção de objetos DataListItem – dos
quais cada um se refere a itens individuais no objeto DataList.
ItemStyle A propriedade que referencia as propriedades de estilo do DataListItem.
RepeatColumns A propriedade que especifica o número de colunas a ser exibido.
RepeatDirection A propriedade que determina a exibição vertical ou horizontal do
DataList.
RepeatLayout A propriedade que determina o layout de tabela ou de fluxo.
SelectedIndex A propriedade que referencia o índice do item selecionado no DataList.
SelectedItem A propriedade que referencia o DataListItem selecionado.
SelectedItemStyle A propriedade que referencia os atributos de estilo do DataListItem
selecionado.
SeparatorStyle A propriedade que referencia os atributos de estilo do separador.
CancelCommand O evento que dispara quando o comando Cancel é invocado para o
DataList.
DeleteCommand O evento que dispara quando o comando Delete é invocado para o
DataList.
EditCommand O evento que dispara quando o comando Edit é invocado para o
DataList.
ItemCommand O evento que dispara quando qualquer botão de comando é clicado no
controle DataList.
ItemCreated O evento que dispara quando um DataListItem é criado.
ItemDataBound O evento que dispara quando um DataListItem é vinculado, mas antes
de ser renderizado.
UpdateCommand O evento que dispara quando o comando Update é invocado para o
DataList.

Para ilustrar o uso da classe DataList, utilizarei a tabela employee no banco de dados
Northwind. Esse exemplo realiza a formatação do SelectedItem. Ele também mostra como
responder ao usuário que seleciona um item em que recupera informações adicionais so-
bre cada empregado e exibe essas informações sobre o Page. A Listagem 27.8 mostra a de-
claração DataList do arquivo .aspx desse exemplo.

LISTAGEM 27.8 Declaração de controle DataList (.aspx)


1: <asp:datalist id="DataList1" style="Z-INDEX: 1" runat="server"
2: horizontalalign="Left" height="70px" datakeyfield="EmployeeID">
3: <selecteditemstyle font-bold="True" forecolor="LightGray"
4: backcolor="Black"></selecteditemstyle>
5: <headertemplate>
6: <b>Employees</b>
7: </headertemplate>
664 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.8 Continuação


8: <itemtemplate>
9: <asp:linkbutton commandname="select"
10: text='<%# ((System.Data.DataRowView)Container.DataItem)
➥["FirstName"].ToString( )+" "+
11: ((System.Data.DataRowView)Container.DataItem)
➥["LastName"].ToString( )%>'
12: runat="Server" />
13: </itemtemplate>
14: </asp:datalist>

u Localize o código no CD: \Code\Chapter 27\Ex12\.

Primeiro, observe a tag SelectedItemStyle (linhas 3–4). Dentro dessa tag, as proprieda-
des de exibição para o item selecionado são configuradas. O ItemTemplate declara o item
como um LinkButton que invocará um evento no Page.
A Listagem 27.9 é um trecho do arquivo-fonte do WebForm para essa aplicação.

LISTAGEM 27.9 Exemplo de DataList


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'SELECT * FROM employees';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: dt: DataTable;
10: begin
11: Session.Add('EmpID', System.Object(-1));
12: Image1.Visible := False;
13: sqlcn := SqlConnection.Create(c_cnstr);
14: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
15: ds := DataSet.Create;
16: sqlDA.Fill(ds, 'Employees');
17: dt := ds.Tables['Employees'];
18:
19: DataList1.DataSource := dt.DefaultView;
20: DataBind;
21: sqlcn.Close;
22: end;
23:
24: procedure TWebForm1.btnUnSelect_Click(sender: System.Object;
25: e: System.EventArgs);
26: begin
27: DataList1.SelectedIndex := -1;
28: Label1.Text := '';
Controles iterativos de vinculação de dados 665

LISTAGEM 27.9 Continuação


29: end;
30:
31: procedure TWebForm1.DataList1_SelectedIndexChanged(sender: System.Object;
32: e: System.EventArgs);
33: const
34: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
35: c_sel = 'SELECT FirstName, LastName, Title, TitleOfCourtesy, Notes '+
36: ' FROM employees WHERE employeeid = {0}';
37: var
38: sqlcn: SqlConnection;
39: sqlCmd: SqlCommand;
40: sqlRdr: SqlDataReader;
41: dt: DataTable;
42: selStr: String;
43: EmpID: Integer;
44: Line: String;
45: begin
46: Label1.Text := '';
47: Image1.Visible := True;
48: EmpID := Integer(DataList1.DataKeys[DataList1.SelectedIndex]);
49: sqlcn := SqlConnection.Create(c_cnstr);
50: selStr := System.String.Format(c_sel, [EmpID]);
51: sqlCmd := SqlCommand.Create(selStr, sqlcn);
52:
53: sqlcn.Open;
54: try
55: sqlRdr := sqlCmd.ExecuteReader;
56: sqlRdr.Read;
57: Line := System.String.Format('<b>{0} {1} {2}</b><br>',
58: [sqlRdr['TitleOfCourtesy'], sqlRdr['FirstName'],
➥sqlRdr['LastName']]);
59: Label1.Text := Label1.Text+Line;
60: Label1.Text := Label1.Text + sqlRdr['Title'].ToString+'<hr>';
61: Label1.Text := Label1.Text + sqlRdr['Notes'].ToString;
62: Session.Add('EmpID', System.Object(EmpID));
63: finally
64: sqlcn.Close;;
65: end;
66: end;

u Localize o código no CD: \Code\Chapter 27\Ex12\.

O handler de evento Page_Load( ) é semelhante aos outros que você já viu neste capí-
tulo. Dignas de nota são as linhas 11 e 12, em que coloco um -1 no objeto Session, que
será utilizado por outra página. Discutirei isso em breve.
Quando um item é selecionado dentro do DataList, a propriedade SelectedIndex é
configurada como o índice de itens. O btnUnSelect_Click( ) (linhas 24–29) handler de
666 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

evento é uma resposta para um botão no Page. Esse handler configura o SelectedIndex
como -1, que remove efetivamente a seleção de qualquer item.
Quando um item for selecionado, o handler de evento DataList1_SelectedIndexChan-
ged( ) é invocado. Esse handler de evento recupera informações adicionais sobre o em-
pregado e as exibe em um controle Label. Outra página trata de exibir a foto do emprega-
do em um controle Image. A renderização de Image utiliza a técnica mostrada no Capítulo
26 para renderizar imagens; em termos simples, essa técnica ilustra como se referir ao
controle Image com outra página .aspx que trata a renderização. A linha 62 mostra que o
EmployeeID é adicionado ao objeto Session. Esse valor será utilizado pela página de renderi-
zação da imagem.
A Listagem 27.10 lista o evento Page_Load( ) para o Web Form que renderiza a foto do
empregado.

LISTAGEM 27.10 Renderização de imagem de um banco de dados


1: procedure TImageForm.Page_Load(sender: System.Object;
2: e: System.EventArgs);
3: const
4: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
5: c_selphoto = 'SELECT Photo FROM employees WHERE employeeid = {0}';
6: var
7: sqlcn: SqlConnection;
8: sqlCmd: SqlCommand;
9: selStr: String;
10: EmpID: Integer;
11: photo: TByteArray;
12: begin
13: EmpID := Integer(Session['EmpID']);
14: if EmpID > -1 then
15: begin
16: sqlcn := SqlConnection.Create(c_cnstr);
17: selStr := System.String.Format(c_selphoto, [EmpID]);
18: sqlcn.Open;
19: try
20: sqlCmd := SqlCommand.Create(selStr, sqlcn);
21: photo := TByteArray(sqlCmd.ExecuteScalar);
22: finally
23: sqlcn.Close;
24: end;
25: Response.ContentType := 'image/jpeg';
26: Response.OutputStream.Write(photo, 78, System.Array(photo).Length);
27: end;
28: end;

u Localize o código no CD: \Code\Chapter 27\Ex12\.

Primeiro, a linha 13 recupera o ID do empregado a partir do objeto Session na variável


EmpID. Depois ele utiliza isso para recuperar o campo BLOB que contém a foto do empregado.
Trabalhando com o DataGrid 667

NOTA
Normalmente, um campo BLOB em um banco de dados só contém a imagem. Esse não é o caso
das imagens contidas no banco de dados Northwind. Essas imagens foram inicialmente armaze-
nadas como um objeto OLE e, portanto, contêm 78 bytes de informações de cabeçalho sobre esse
objeto. A imagem segue o cabeçalho. No código da Listagem 27.10 é necessário especificar o des-
locamento desses 78 bytes logo no início do BLOB.

O código restante renderiza a foto dentro do Page. Como o controle Image no Page
principal referencia essa página, ele é exibido com as outras informações, como mostra-
do na Figura 27.11.

Trabalhando com o DataGrid


O DataGrid tem a maior funcionalidade dos controles iterativos. O DataGrid representa os
dados em um formato de tabela em que cada item é representado em uma linha e cam-
pos são representados em colunas. O DataGrid suporta capacidades de template integrais.
Dentro do DataGrid, você pode fornecer a capacidade de selecionar, classificar e editar
(excluir e paginar itens).
Cinco tipos de coluna são suportados no DataGrid – um dos quais permite criar colu-
nas de template personalizadas. Isso torna as possibilidades com as colunas DataGrid ili-
mitadas.
A Tabela 27.6 lista os vários tipos de coluna suportados pelo DataGrid.

FIGURA 27.11 Controles DataList e Image.


668 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

TABELA 27.6 Tipos de coluna Data Grid


Tipo de coluna Descrição
BoundColumn Renderiza uma coluna que é vinculada a um campo da origem de dados.
ButtonColumn Renderiza um botão de comando em cada item nessa coluna, fornecendo a
capacidade de adicionar funcionalidades como uma resposta aos comandos
de botão.
EditCommandColumn Renderiza uma coluna que adiciona botões para itens de edição no DataGrid.
HyperLinkColumn Renderiza o item de coluna como um hyperlink. Esse valor talvez esteja
vinculado a um campo de origem de dados ou talvez contenha texto
estático.
TemplateColumn Renderiza a coluna de acordo com a template especificada. Você utilizaria
esse tipo de coluna para adicionar controles personalizados ao DataGrid.

Você também pode configurar vários estilos de exibição para os seguintes elementos:

AlternatingItemStyle Estilo de linhas alternadas no DataGrid


EditItemStyle A linha sendo editada
FooterStyle Estilo do rodapé do DataGrid
HeaderStyle Estilo do cabeçalho do DataGrid
ItemStyle Estilo de cada item dentro do DataGrid (a menos que um
AlternatingItemStyle seja fornecido)
PagerStyle Estilos da seção de seleção de página do DataGrid
SelectedItemStyle Estilo do item selecionado DataGrid

A lista de propriedade do DataGrid é extensa e, portanto, não discutirei todas elas. Em


vez disso, discutirei algumas propriedades-chave dentro do contexto dos exemplos for-
necidos aqui.

NOTA
Para obter informações adicionais sobre o DataGrid, recomendaria uma leitura atenta da docu-
mentação on-line em www.msdn.microsoft.com ou nos docs do .NET SDK fornecidos na ajuda do
Delphi for .NET.

Paginando o DataGrid
Um dos recursos elegantes do DataGrid é sua capacidade de exibir e de navegar em grandes
resultsets por partes. O próprio DataGrid trata a renderização de cada parte e adiciona uma
barra Pager, uma linha na parte inferior do DataGrid com links para permitir ao usuário
avançar ou retroceder pelas partes de dados. Naturalmente, você tem a opção de perso-
nalizar como o DataGrid recupera partes para exibição e você também pode alterar a apa-
rência e o comportamento da barra Pager.
Deve ser observado, entretanto, que o DataGrid trata a renderização em usabilidade de
paginação. Ele não trata o gerenciamento dos dados dentro da origem de dados. Se pensar
na maneira como o ASP.NET opera, toda vez que uma página é solicitada, os dados obtidos
desde a última solicitação não estão mais presentes. Essa nova solicitação é uma nova pági-
Trabalhando com o DataGrid 669

na; portanto, todas as ações que são necessárias para exibir a nova página devem ocorrer, o
que inclui recuperar dados, reatribuir a origem de dados ao grid de dados e fazer a vincula-
ção novamente. Com a paginação, você adiciona o passo para dizer ao DataGrid que item
deve ser exibido como o item superior no DataGrid. Considerando isso, é possível recuperar
milhares de registros a partir da origem de dados em um conjunto de dados toda vez que o
usuário paginar pelo DataGrid. Em geral, isso não é um problema. Mas se isso fosse proble-
mático, você teria a opção de uma recuperação de dados mais inteligente, que reduziria o
número de registros obtidos a partir do DataSet, ou as opções de cache que serão discutidas
no Capítulo 33. Para a essa discussão, essa recuperação de dados permanecerá simples.
Esse primeiro exemplo ilustra as capacidades de paginação do DataGrid. Além disso,
demonstra o uso do AlternatingItemStyle.
A Listagem 27.11 mostra a declaração do DataGrid dentro do arquivo .aspx.

LISTAGEM 27.11 Declaração de DataGrid (.aspx)


1: <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas"
2: AutoEventWireup="false" Inherits="WebForm1.TWebForm1" %>
3: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:
5: <html>
6: <head>
7: <title></title>
8: <meta name="GENERATOR" content="Borland Package Library 7.1">
9: </head>
10:
11: <body ms_positioning="GridLayout">
12: <form runat="server">
13: <asp:datagrid id=DataGrid1
14: style="Z-INDEX: 1; LEFT: 6px; POSITION: absolute; TOP: 6px"
15: runat="server" height="243" width="523px" font-names="Arial"
16: font-size="X-Small" borderwidth="1px" bordercolor="Gray"
17: borderstyle="Solid" cellspacing="2" cellpadding="2"
18: pagesize="5" allowpaging="True">
19: <alternatingitemstyle borderstyle="Solid" backcolor="#FFFFC0">
20: </alternatingitemstyle>
21: <itemstyle borderstyle="Solid" backcolor="#C0FFFF">
22: </itemstyle>
23: </asp:datagrid>
24: </form>
25: </body>
26: </html>

u Localize o código no CD: \Code\Chapter 27\Ex13\.

A linha 18 mostra a configuração dos dois atributos que permitem fazer paginação
dentro do DataGrid. As linhas 19 e 20 configuram o AlternateItemStyle e a linha 21 configura
o atributo ItemStyle. A fonte do code-behind para esse Page é mostrada na Listagem 27.12.
670 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.12 Página DataGrid


1: TWebForm1 = class(System.Web.UI.Page)
2: procedure DataGrid1_PageIndexChanged(source: System.Object; e:
3: System.Web.UI.WebControls.DataGridPageChangedEventArgs);
4: strict private
5: procedure Page_Load(sender: System.Object; e: System.EventArgs);
6: private
7: { Private Declarations }
8: procedure GetData;
9: end;
10:
11: implementation
12:
13: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
14: begin
15: if not IsPostBack then
16: GetData;
17: end;
18:
19: procedure TWebForm1.DataGrid1_PageIndexChanged(source: System.Object;
20: e: System.Web.UI.WebControls.DataGridPageChangedEventArgs);
21: begin
22: DataGrid1.CurrentPageIndex := e.NewPageIndex;
23: GetData;
24: end;
25:
26: procedure TWebForm1.GetData;
27: const
28: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
29: c_sel = 'select * from customers';
30: var
31: sqlcn: SqlConnection;
32: sqlDA: SqlDataAdapter;
33: ds: DataSet;
34: dt: DataTable;
35: begin
36: sqlcn := SqlConnection.Create(c_cnstr);
37: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
38: ds := DataSet.Create;
39: sqlDA.Fill(ds, 'customers');
40: dt := ds.Tables['customers'];
41:
42: DataGrid1.DataSource := dt.DefaultView;
43: DataBind;
44: sqlcn.Close;
45: end;
46:
47: end.

u Localize o código no CD: \Code\Chapter 27\Ex13\.


Trabalhando com o DataGrid 671

A Listagem 27.12 contém partes do código real. O método GetData( ) recupera infor-
mações da origem de dados. Você notará que esse método é invocado do handler de
evento Page_Load( ) e do handler de evento DataGrid_1_PageIndexChanged( ). Isso ilustra
como você é solicitado a recuperar os dados sempre que gerar uma nova página. A linha
22 mostra como configurar o índice do item para o DataGrid antes de vincular novamente
o DataGrid. O resultado desse código é mostrado na Figura 27.12.

Editando o DataGrid
O DataGrid tem uma coluna especial do tipo EditCommandColumn a partir do qual você fornece
uma única instância se quiser a edição instalada no DataGrid. A declaração dessa coluna
seria semelhante a essa mostrada aqui:

<asp:editcommandcolumn edittext="Edit" updatetext="Update"


canceltext="Cancel" headertext="Edit"
itemstyle-wrap="False">
</asp:editcommandcolumn>

Quando essa coluna estiver presente, uma coluna com um link irá aparecer no DataGrid.
Quando alguém clica nesse link, os controles editáveis no grid tornam-se controles de
texto, com exceção daqueles que são baseados em uma coluna de template. O EditCom-
mandColumn então conterá dois links que referenciam o comando Cancel e Update.
Quando o usuário clica no comando Edit, o evento EditCommand é invocado. Quando o
usuário clica nos comandos Update e Cancel, os eventos UpdateCommand e CancelCommand são in-
vocados, respectivamente.

FIGURA 27.12 Controle DataGrid.


672 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

O DataGrid trata todo o trabalho de converter as colunas em TextBoxes editáveis. Além


disso, ele converte o EditCommandColumn para conter o texto adequado. Ele não inclui o có-
digo para de fato realizar a atualização de origem de dados.
Discutirei esses handlers de evento em breve. Já mostrei a declaração do EditComandCo-
lumn para o próximo exemplo. Considere a seguinte declaração de uma coluna padrão
para o DataGrid:
<asp:boundcolumn datafield="QuantityPerUnit"
headertext="Qty Per Unit" itemstyle-wrap="False"
sortexpression="ORDER BY QuantityPerUnit">
</asp:boundcolumn>

Essa coluna corresponderá aos campos QuantityPerUnit do banco de dados Products


no banco de dados Northwind. Quando o DataGrid for colocado no modo de edição, essa
coluna será convertida em um TextBox. Tome nota do atributo sortexpression. Isso entrará
em cena mais tarde. As colunas restantes são semelhantes a isso.
Considere a declaração do TemplateColumn na Listagem 27.13.

LISTAGEM 27.13 Declaração de um TemplateColumn


1. <asp:templatecolumn runat="server" headertext="Discontinued">
2. <itemtemplate>
3. <asp:checkbox id="CheckBox1" runat="server" enabled="False"
4. checked='<%# Convert.ToBoolean(((System.Data.DataRowView)
➥Container.DataItem)["Discontinued"])%>'/>
5. </itemtemplate>
6. <edititemtemplate>
7. <asp:checkbox id="CheckBox1" runat="server" enabled="True"
8. checked='<%# Convert.ToBoolean(((System.Data.DataRowView)
➥Container.DataItem)["Discontinued"])%>'/>
9. </edititemtemplate>
10. </asp:templatecolumn>

u Localize o código no CD: \Code\Chapter 27\Ex14\.

Em geral, um TemplateColumn consiste em duas partes. A primeira representa a renderi-


zação de leitura de um controle. Nesse caso, esse é um controle CheckBox que aparece den-
tro da tag itemtemplate (linhas 2–5). A segunda representa o controle que estará presente
quando o grid estiver no modo de edição. Esse controle aparece dentro da tag edititem-
template (linhas 6–9). A única diferença entre os dois controles nesse exemplo é o atributo
ativado do CheckBox.
O TemplateColumn na Listagem 12.14 demonstra como os dois controles separados são
utilizados para exibição/edição.

NOTA
O BoundColumn também utiliza dois controles diferentes para exibição/edição. Você simplesmente
não os vê declarados dessa maneira. Ao exibir uma coluna, o controle é um Label. Ao editá-la, ele
é um TextBox.
Trabalhando com o DataGrid 673

LISTAGEM 27.14 A declaração de outro TemplateColumn


<asp:templatecolumn runat="Server" headertext="Suppliers"
itemstyle-wrap="False">
<itemtemplate>
<asp:label id="lblSupplier" runat="server"
text='<%# GetSupplierName(Convert.ToInt32(((System.Data.DataRowView)
➥Container.DataItem)["SupplierID"])) %>'> </asp:label>
</itemtemplate>
<edititemtemplate>
<asp:dropdownlist id="DropDownList1" runat="server"
datasource='<%# htSupplierNames.Values %>'/>
</asp:dropdownlist>
</edititemtemplate>
</asp:templatecolumn>

u Localize o código no CD: \Code\Chapter 27\Ex14\.

Nesse exemplo, o itemtemplate é controle de rótulo (label) e o controle editável é um


DropDownList. O atributo ID desses controles tem relevância, como verá brevemente.
A Listagem 27.15 lista um trecho do programa de exemplo que contém o handler de
evento Page_Load( ) e o método GetData( ).

LISTAGEM 27.15 Page_Load( ) e GetData( )


1: type
2: TWebForm1 = class(System.Web.UI.Page)
3: strict private
4: procedure Page_Load(sender: System.Object; e: System.EventArgs);
5: private
6: procedure GetData;
7: public
8: htSupplierNames: HashTable;
9: htSupplierIDs: HashTable;
10: function GetSupplierName(aSupplierID: Integer): String;
11: end;
12:
13: implementation
14:
15: const
16: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
17: c_sel_prod = 'SELECT * FROM products';
18: c_sel_sup = 'SELECT companyname, supplierid FROM suppliers';
19:
20: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
21: var
22: SelStr: String;
23: begin
24: if not IsPostBack then
25: begin
674 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.15 Continuação


26: SelStr := c_sel_prod;
27: Session.Add('SelStr', SelStr);
28: GetData;
29: end;
30: end;
31:
32: procedure TWebForm1.GetData;
33: var
34: sqlcn: SqlConnection;
35: sqlDA: SqlDataAdapter;
36: sqlCmd: SqlCommand;
37: sqlRdr: SqlDataReader;
38: ds: DataSet;
39: dt: DataTable;
40: SelStr: String;
41: begin
42:
43: SelStr := Session['SelStr'] as System.String;
44: sqlcn := SqlConnection.Create(c_cnstr);
45: sqlDA := SqlDataAdapter.Create(SelStr, sqlcn);
46: ds := DataSet.Create;
47: sqlDA.Fill(ds, 'Products');
48:
49: htSupplierNames := HashTable.Create;
50: htSupplierIDs := HashTable.Create;
51:
52: sqlCmd := SqlCommand.Create(c_sel_sup, sqlcn);
53: sqlcn.Open;
54: try
55: sqlRdr := sqlCmd.ExecuteReader;
56: while sqlRdr.Read do
57: begin
58: htSupplierIDs.Add(sqlRdr['CompanyName'].ToString,
59: sqlRdr['SupplierID'].ToString);
60: htSupplierNames.Add(sqlRdr['SupplierID'].ToString,
61: sqlRdr['CompanyName']);
62: end;
63: finally
64: sqlcn.Close;
65: end;
66:
67: DataGrid1.DataKeyField := 'ProductID';
68: dt := ds.Tables['products'];
69: DataGrid1.DataSource := dt.DefaultView;
70: DataBind;
71:
72: end;

u Localize o código no CD: \Code\Chapter 27\Ex14\.


Trabalhando com o DataGrid 675

O método GetData( ) é responsável por recuperar os dados a partir da origem de da-


dos. Você agora já deve conhecer o método, uma vez que ele é muito semelhante aos dos
exemplos anteriores. Há uma diferença entre as linhas 49–65. Nessa parte do código, o
método recupera informações de fornecedores (SupplierID e CompanyName). Ele, então, adi-
ciona essas informações aos dois HashTables. htSupplierNames será utilizado para preencher
um DropDownList. htSupplierIDs será utilizado para recuperar o ID para um dado fornecedor.
Essas informações são necessárias para atualizar a origem de dados. De fato, essa é uma
maneira muito grosseira de manter esses dados. Isso tem apenas propósitos ilustrativos.
No Capítulo 33, mostrarei um meio mais eficiente de lidar com esse tipo de informações.
O handler de evento Page_Load( ) inicializa a string de seleção que é utilizada em Get-
Data( ) e então chama GetData( ). Você notará que Page_Load( ) armazena a string de sele-
ção no objeto Session. (Como já mencionei, não conversei sobre o objeto Session; ele é um
tópico do Capítulo 33.) Entretanto, para esse exemplo, considere-o um armazenamento
que mantém dados colocados dentro dele por meio de solicitações de página. Na seção
“Classificando o DataGrid” você verá por que é importante salvar a string de seleção.

ATENÇÃO
Ao utilizar a aplicação de exemplo na Listagem 27.15, você ocasionalmente pode receber um erro
que declara que CommandText não foi inicializado quando a linha 47 foi executada. Além disso, nada
no código revela por que o CommandText não tem nenhum valor. O que está acontecendo tem a ver
com o objeto Session que perde dados quando o processo trabalhador do ASP.NET (asp-
net_wp.exe) é reiniciado. Basicamente, quando o Session existir dentro do contexto do processo
trabalhador, desativar o processo trabalhador também elimina quaisquer dados armazenados de
Session. Portanto, sugiro desenvolver suas aplicações com o Session dentro de um State Client Ma-
nager (aspnet_state.exe). Faça isso iniciando o gerenciador no prompt de comando assim
c:\net start aspnet_state
Em seguida, altere a tag <sessionState> no arquivo web.config para utilizar o StateServer como

<sessionState mode="StateServer"
stateConnectionString="tcpip=127.0.0.1:42424" />

O gerenciamento de sessão é ainda mais discutido no Capítulo 33.

Até agora, apenas preenchemos o DataGrid. O seguinte código mostra o que acontece
ao clicar no comando Edit:

procedure TWebForm1.DataGrid1_EditCommand(source: System.Object;


e: System.Web.UI.WebControls.DataGridCommandEventArgs);
begin
DataGrid1.EditItemIndex := e.Item.ItemIndex;
GetData;
end;

Primeiro, a propriedade EditItemIndex é configurada, o que especifica o índice do


item que o DataGrid deve colocar no modo de edição. Então, GetData( ) é invocado nova-
mente. Isso nos faz voltar ao que eu disse anteriormente sobre a necessidade de você re-
676 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

cuperar dados toda vez que uma nova página é renderizada. Quando o DataGrid estiver
em modo de edição, ele aparece como mostrado na Figura 27.13.
Nesse ponto o usuário pode adotar uma das duas ações disponíveis. Ele pode clicar
no comando Update ou no Cancel. O handler de evento para o comando Cancel é seme-
lhante ao do comando Edit, exceto que -1 é atribuído em vez de e.Item.ItemIndex, como
mostrado aqui:

procedure TWebForm1.DataGrid1_CancelCommand(source: System.Object;


e: System.Web.UI.WebControls.DataGridCommandEventArgs);
begin
DataGrid1.EditItemIndex := -1;
GetData;
end;

O comando Update é mais interessante e é mostrado na Listagem 27.16.

FIGURA 27.13 Controle DataGrid no modo de edição.

LISTAGEM 27.16 Handler de evento do comando Update


1: procedure TWebForm1.DataGrid1_UpdateCommand(source: System.Object;
2: e: System.Web.UI.WebControls.DataGridCommandEventArgs);
3: var
4: tb: TextBox;
5: cb: CheckBox;
6: ddl: DropDownList;
7: ddlStr: String;
8: begin
Trabalhando com o DataGrid 677

LISTAGEM 27.16 Continuação


9: GetData;
10:
11: tb := e.Item.Cells[1].Controls[0] as TextBox;
12: Label1.Text := tb.Text+'<br>';
13: tb := e.Item.Cells[2].Controls[0] as TextBox;
14: Label1.Text := Label1.Text + tb.Text+'<br>';
15: tb := e.Item.Cells[3].Controls[0] as TextBox;
16: Label1.Text := Label1.Text + tb.Text+'<br>';
17: cb := e.Item.FindControl('CheckBox1') as CheckBox;
18: Label1.Text := Label1.Text + Convert.ToString(cb.Checked)+'<br>';
19: ddl := e.Item.FindControl('DropDownList1') as DropDownList;
20: ddlStr := ddl.Items[ddl.SelectedIndex].Value;
21: Label1.Text := Label1.Text + ddlStr +
22: ': '+ htSupplierIDs[ddlStr].ToString;
23:
24: // Grava atualizações na origem de dados
25: DataGrid1.EditItemIndex := -1;
26: DataGrid1.DataBind;
27: end;

u Localize o código no CD: \Code\Chapter 27\Ex14\.

A idéia por trás desse método é que você extraia os dados dos controles dentro do Data-
Grid, utilize esses valores para atualizar a origem de dados associada, faça novamente a con-
sulta e a vinculação. As linhas 11–22 ilustram como extrair os dados modificados a partir do
DataGrid. Para BoundColumns, o controle associado ao DataGrid é um TextBox. Você pode obter a
coluna específica pela propriedade Item.Cells do parâmetro DataGridCommandEventArgs. Para
TemplateColumns, você deve utilizar o método Item.FindControl( ) para recuperar a referência ao
controle. Esse método aceita o ID do controle como o parâmetro. Anteriormente, eu disse
que o ID de um controle em um TemplateColumn era relevante – essa é a razão.
Não demonstrei a operação de atualização para o armazenamento de dados porque
isso não é diferente do que é mostrado em qualquer exemplo nos capítulos sobre ADO.
Você simplesmente utiliza os componentes apropriados e realiza a atualização antes de
recuperar os dados.
Além disso, você notará que chamo GetData( ) antes de extrair os valores dos contro-
les. Faço isso porque, de outro modo, o HashTables seria inválido. Entretanto, depois de
atualizar os dados, eu ainda teria de fazer outra chamada a GetData( ). Uma abordagem
opcional, e melhor, seria recuperar os dados que devem ser inseridos no HashTable, uma
vez na inicialização de aplicação e, depois, para persistir essas informações em uma ses-
são ou cache, como foi feito com a string select. Isso é discutido no Capítulo 33.

Adicionando itens ao DataGrid


O DataGrid não suporta a adição de itens. Li as razões dessa criação de DataGrid tão comple-
xa, mas não consegui entendê-las. Francamente, o DataGrid já é bem complexo; o que há
de errado com um pouco mais de complexidade?
678 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

Mas há uma maneira de alcançar esse efeito. Basicamente, envolve adicionar uma
nova linha ao DataSet com valores vazios e valores padrão, posicionar o Grid para esse
item e configurá-lo no modo de edição. Esse cenário funciona melhor ao trabalhar com
dados desconectados e armazenados em cache.

Classificando o DataGrid
Na seção “Editando o DataGrid” indiquei o atributo sortexpression de uma tag de coluna.
Essa expressão é portadora de informações que você precisaria para classificar o DataGrid
programaticamente. O DataGrid fornece o evento SortCommand para que você possa fornecer
um handler de evento como mostrado aqui:
procedure TWebForm1.DataGrid1_SortCommand(source: System.Object;
e: System.Web.UI.WebControls.DataGridSortCommandEventArgs);
begin
selStr := c_sel_prod + ' ' + e.SortExpression;
GetData;
end;

Esse handler de evento é disparado quando um usuário clica no cabeçalho de uma


coluna de um DataGrid com o atributo allowsorting para a coluna configurada como True.
Nesse exemplo, o sortexpression para as várias colunas contém uma expressão ORDER
BY que é acrescentada à string de seleção utilizada para recuperar a tabela de produtos. A
seguinte declaração de coluna demonstra isso:
<asp:boundcolumn datafield="ProductName"
headertext="Product Name" itemstyle-wrap="False"
sortexpression="ORDER BY ProductName">
</asp:boundcolumn>

Na linha 45 da Listagem 27.15 você já viu que o item armazenado SelStr no objeto
Session é utilizado como o SelectCommand para o DataAdapter nesse método. Quando o even-
to SortCommand é invocado, a cláusula ORDER BY apropriada é acrescentada ao SelStr e adicio-
nada novamente ao objeto Session. A próxima chamada GetData( ) recuperará os dados
na ordem de classificação especificada. Quando o DataGrid é vinculado, ele mostra os ele-
mentos na ordem especificada.
O exemplo com que temos trabalhado demonstra essa técnica.

O formulário de solicitação de downloads e


administração orientados a banco de dados
Para ilustrar uma aplicação ASP.NET do mundo real, vamos rever a aplicação de solicita-
ção de download que foi introduzida no Capítulo 26. No Capítulo 26, essa aplicação rea-
lizou algumas funções que iremos alterar. Primeiro, se você se lembra, a aplicação recu-
perou os valores da pesquisa para o estado e país a partir de um arquivo de texto. Além
disso, a aplicação salvou as informações que o usuário inseriu em um arquivo de texto, o
qual a aplicação então faz persistir em disco.
O formulário de solicitação de downloads e administração orientados a banco de dados 679

Esse exemplo irá recuperar os valores de pesquisa para o estado e país a partir de tabe-
las de banco de dados. Além disso, a aplicação salvará as informações do usuário em uma
tabela de um banco de dados. A Figura 27.14 retrata o diagrama de banco de dados de so-
licitação de download. Você observará que a tabela de download contém duas chaves es-
trangeiras, que estão nas tabelas lu_state e lu_country.

FIGURA 27.14 Banco de dados de solicitação de download.

A Listagem 27.17 mostra a origem para a aplicação de download. As partes do código


que não são relevantes foram deixadas de lado.

LISTAGEM 27.17 Aplicação de solicitação de download – o banco de dados ativado


1: unit DwnLdForm;
2:
3: interface
4:
5: uses
6: System.Collections, System.ComponentModel,
7: System.Data, System.Drawing, System.Web, System.Web.SessionState,
8: System.Web.UI, System.Web.UI.WebControls, System.Web.UI.HtmlControls,
9: System.IO, System.Web.Mail, System.Data.SqlClient, Borland.Vcl.Classes;
10:
11: type
12: TWebForm1 = class(System.Web.UI.Page)
13: strict private
14: procedure Page_Load(sender: System.Object; e: System.EventArgs);
15: strict protected
16: procedure SendEmail(aEmail: String);
17: private
18: { Private Declarations }
19: procedure GetData;
20: procedure SaveUserInfo;
680 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.17 Continuação


21: public
22: aryCountryName: array of String;
23: aryCountryAbbr: array of String;
24: aryStateName: array of String;
25: aryStateAbbr: array of String;
26: end;
27:
28: implementation
29:
30: const
31: c_cnstr = 'server=XWING;database=ddg_download;Trusted_Connection=Yes';
32:
33: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
34: begin
35: if IsPostBack then
36: begin
37: SaveUserInfo;
38: SendEMail(tbxEmail.Text);
39: Response.Redirect('ThankYouForm.aspx');
40: end
41: else GetData;
42: end;
43:
44: function GetCommand(aFileName: String): String;
45: var
46: sr: StreamReader;
47: begin
48: if System.IO.File.Exists(aFileName) then
49: begin
50: sr := StreamReader.Create(System.IO.File.OpenRead(aFileName));
51: try
52: Result := sr.ReadToEnd;
53: finally
54: sr.Close;
55: end;
56: end;
57: end;
58:
59: procedure TWebForm1.SaveUserInfo;
60: var
61: sqlCmd: SqlCommand;
62: sqlcn: SqlConnection;
63: idx: Integer;
64: begin
65: GetData;
66: sqlcn := SqlConnection.Create(c_cnstr);
67: sqlCmd := SqlCommand.Create(GetCommand(
68: Request.PhysicalApplicationPath+'c_ins.sql'), sqlCn);
O formulário de solicitação de downloads e administração orientados a banco de dados 681

LISTAGEM 27.17 Continuação


69:
70: sqlcn.Open;
71: try
72: sqlCmd.Parameters.Add('@first_name', SqlDbType.NVarChar, 20);
73: sqlCmd.Parameters.Add('@last_name', SqlDbType.NVarChar, 20);
74: SqlCmd.Parameters.Add('@title', SqlDbType.NVarChar, 40);
75: SqlCmd.Parameters.Add('@email', SqlDbType.NVarChar, 50);
76: SqlCmd.Parameters.Add('@company', SqlDbType.NVarChar, 100);
77: SqlCmd.Parameters.Add('@address_1', SqlDbType.NVarChar, 100);
78: SqlCmd.Parameters.Add('@address_2', SqlDbType.NVarChar, 100);
79: SqlCmd.Parameters.Add('@city', SqlDbType.NVarChar, 50);
80: SqlCmd.Parameters.Add('@postal_code', SqlDbType.NVarChar, 15);
81: SqlCmd.Parameters.Add('@telephone', SqlDbType.NVarChar, 20);
82: SqlCmd.Parameters.Add('@fax', SqlDbType.NVarChar, 20);
83: SqlCmd.Parameters.Add('@url', SqlDbType.NVarChar, 50);
84: SqlCmd.Parameters.Add('@send_free', SqlDbType.Bit);
85: SqlCmd.Parameters.Add('@country_id', SqlDbType.Char, 2);
86: SqlCmd.Parameters.Add('@state_id', SqlDbType.Char, 2);
87: SqlCmd.Parameters.Add('@date_downloaded', SqlDbType.DateTime);
88:
89: // Adiciona Values a Command
90:
91: sqlCmd.Parameters['@first_name'].Value := tbxFirstName.Text;
92: sqlCmd.Parameters['@last_name'].Value := tbxLastName.Text;
93: sqlCmd.Parameters['@title'].Value := tbxTitle.Text;
94: sqlCmd.Parameters['@email'].Value := tbxEmail.Text;
95: sqlCmd.Parameters['@company'].Value := tbxCompany.Text;
96: sqlCmd.Parameters['@address_1'].Value := tbxAddress1.Text;
97: sqlCmd.Parameters['@address_2'].Value := tbxAddress2.Text;
98: sqlCmd.Parameters['@city'].Value := tbxCity.Text;
99: sqlCmd.Parameters['@postal_code'].Value := tbxPostalCode.Text;
100: sqlCmd.Parameters['@telephone'].Value := tbxTelephone.Text;
101: sqlCmd.Parameters['@fax'].Value := tbxFax.Text;
102: sqlCmd.Parameters['@url'].Value := tbxUrl.Text;
103: sqlCmd.Parameters['@send_free'].Value :=
104: System.Object(cbxFreeStuff.Checked);
105:
106: idx := System.Array.IndexOf(aryCountryName,
107: ddlCountry.Items[ddlCountry.SelectedIndex].Text);
108: sqlCmd.Parameters['@country_id'].Value := aryCountryAbbr[idx];
109:
110: idx := System.Array.IndexOf(aryStateName,
111: ddlState.Items[ddlState.SelectedIndex].Text);
112: sqlCmd.Parameters['@state_id'].Value := aryStateAbbr[idx];
113:
114: sqlCmd.Parameters['@date_downloaded'].Value := System.DateTime.Now;
115: sqlCmd.ExecuteNonQuery;
116: finally
682 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

LISTAGEM 27.17 Continuação


117: sqlcn.Close;
118: end;
119: end;
120:
121: procedure TWebForm1.GetData;
122: const
123: c_sel_ccount = 'SELECT count(*) AS count FROM lu_country';
124: c_sel_scount = 'SELECT count(*) AS count FROM lu_state';
125: c_sel_country = 'SELECT * FROM lu_country';
126: c_sel_state = 'SELECT * FROM lu_state;';
127: var
128: sqlcn: SqlConnection;
129: sqlCmd: SqlCommand;
130: sqlRdr: SqlDataReader;
131: count: integer;
132: idx: Integer;
133: begin
134: sqlcn := SqlConnection.Create(c_cnstr);
135:
136: sqlcn.Open;
137: try
138: sqlCmd := SqlCommand.Create(c_sel_ccount, sqlcn);
139: count := Integer(sqlCmd.ExecuteScalar);
140: SetLength(aryCountryName, count);
141: SetLength(aryCountryAbbr, count);
142:
143: sqlCmd := SqlCommand.Create(c_sel_scount, sqlcn);
144: count := Integer(sqlCmd.ExecuteScalar);
145: SetLength(aryStateName, count);
146: SetLength(aryStateAbbr, count);
147: finally
148: sqlcn.Close;
149: end;
150:
151: sqlcn.Open;
152: try
153: sqlCmd := SqlCommand.Create(c_sel_country, sqlcn);
154: sqlRdr := sqlCmd.ExecuteReader;
155: idx := 0;
156: while sqlRdr.Read do
157: begin
158: aryCountryAbbr[idx] := sqlRdr['country_id'].ToString;
159: aryCountryName[idx] := sqlRdr['country_name'].ToString;
160: inc(idx);
161: end;
162: finally
163: sqlcn.Close;
164: end;
O formulário de solicitação de downloads e administração orientados a banco de dados 683

LISTAGEM 27.17 Continuação


165:
166: sqlcn.Open;
167: try
168: sqlCmd := SqlCommand.Create(c_sel_state, sqlcn);
169: sqlRdr := sqlCmd.ExecuteReader;
170: idx := 0;
171: while sqlRdr.Read do
172: begin
173: aryStateAbbr[idx] := sqlRdr['state_id'].ToString;
174: aryStateName[idx] := sqlRdr['state_name'].ToString;
175: inc(idx);
176: end;
177: finally
178: sqlcn.Close;
179: end;
180:
181: DataBind;
182: end;
183:
184: end.

u Localize o código no CD: \Code\Chapter 27\Ex15\.

Primeiro, examine o handler de evento Page_Load( ). Você notará que no post-back,


os métodos SaveUserInfo( ) e SendEmail( ) são invocados antes de redirecionarem o usuá-
rio para uma página de agradecimento. SaveUserInfo( ) é o método que foi modificado no
exemplo do Capítulo 26. Discutirei esse método em breve. SendEmail( ) não foi alterado;
portanto, ele não é mostrado aqui. Se essa for a primeira vez que a página foi carregada, o
método GetData( ) é invocado.
GetData( ) realiza a recuperação de dados a partir de uma origem de dados que é ne-
cessária para a página de download. Duas tabelas são necessárias e, de fato, ambas são
requeridas em dois arranjos. Primeiro, queremos exibir o nome completo dos países e
estados no controle DropDownLists na página. Portanto, declaramos dois membros array,
aryCountryName e aryStateName (linhas 22 e 24), em que esses valores serão armazenados e
aos quais o DropDownLists será vinculado. Segundo, ao salvar dados de volta no banco de
dados, queremos salvar as chaves, que são as abreviações das duas listas. Portanto, sal-
vamos esses valores em dois arrays adicionais, aryCountryAbbr e aryStateAbbr (linhas 23 e
25). Esses arrays adicionais correspondem aos arrays previamente mencionados. Em
outras palavras, o índice de um item no array aryCountryName será o mesmo índice no ar-
ray aryCountryAbbr. Utilizaremos isso para recuperar a abreviação (ou chave) para o item
que o usuário selecionou. O método GetData( ) na Listagem 27.17 envolve recuperar e
configurar esses arrays.
Você deve estar familiarizado com o método SaveUserInfo( ) se tiver lido os capítulos
que discutem o ADO.NET. Basicamente, a função cria um objeto SqlCommand com o texto
do comando para inserir um registro na tabela de download desse banco de dados. A ins-
trução Transact-SQL INSERT reside em um arquivo, c_ins.sql, que é carregado e o conteú-
684 Capítulo 27 Construindo aplicações ASP.NET com acesso a banco de dados

do é atribuído à propriedade CommandText (linhas 67–68). O resto do método SaveUserInfo( )


cria parâmetros e atribui valores a eles. Note como tivemos de chamar o método GetDa-
ta( ) no começo do método SaveUserInfo( ). Lembre-se de que toda vez que uma página
for renderizada, quaisquer dados que ela exigir precisam ser recarregados. Uma informa-
ção como esta não é retida entre solicitações de página. No Capítulo 33 mostrarei alguns
outros métodos para gerenciar essas informações, para que você não tenha de reconsul-
tar o banco de dados a cada solicitação de página.
Por fim, no final do método SaveUserInfo( ), os dados do usuário são salvos no banco
de dados.
Isso deve dar uma idéia de como as aplicações Web para banco de dados são desen-
volvidas no ASP.NET.
NESTE CAPÍTULO
CAPÍTULO 28 — Termos relacionados aos Web
Services

Criando Web Services — Construção de um Web


Service
— Consumindo Web Services

— Segurança em Web Services


Há várias razões pelas quais é importante entender
como desenvolver Web Services. Uma razão tem a ver
com manter-se atualizado com as tendências de
desenvolvimento e tecnologias de ponta. O Capítulo 1
fornece uma visão geral dos Web Services. Outra razão é
que Web Services permitem expor seu serviço (ou
aplicação) de maneira padrão para qualquer cliente,
implementado em qualquer plataforma, potencialmente
acessado de qualquer lugar no mundo. Este capítulo
concentra-se na utilização do Delphi for .NET e .NET
para criar e consumir Web Services.

Termos relacionados aos Web


Services
Os seguintes termos foram introduzidos no Capítulo 1.
Repito-os aqui por referência rápida. Esses são termos
freqüentemente utilizados ao trabalhar com Web
Services.
— XML – Extensible Markup Language. XML é um
formato flexível, baseado em texto,
originalmente derivado da SGML para o
propósito de publicação por meios eletrônicos. A
riqueza e o formato autodefinido da XML a torna
ideal para utilização na passagem de mensagens
entre consumidores de Web Service e servidores.
— SOAP – Simple Object Access Protocol. SOAP é o
protocolo para Web Service baseado no padrão
XML para invocar chamadas de procedimento
remoto pela Internet/intranet. O SOAP especifica
o formato da solicitação/resposta e o formato de
parâmetros passados na solicitação/resposta.
SOAP só é específico à mensagem. Ele só impõe
adesão às especificações SOAP mas é, de outro
modo, agnóstico de plataforma e linguagem.
686 Capítulo 28 Criando Web Services

— WSDL – Web Service Description Language. A WSDL é uma linguagem baseada


em XML utilizada para descrever um Web Service. Ela inclui todos os vários méto-
dos e seus parâmetros bem como a localização do Web Service. Os consumidores
de um Web Service podem entender a linguagem WSDL e podem determinar a
funcionalidade fornecida pelo Web Service. Em geral, a WSDL é utilizada por fer-
ramentas e IDEs para criar automaticamente classes proxy utilizadas para acessar
o serviço.
— UDDI – Universal Description, Discovery, and Integration. O UDDI é um padrão
de registros públicos para armazenar informações sobre a publicação de Web Ser-
vices. Você pode visitar UDDI em www.uddi.org para obter informações sobre o pa-
drão UDDI. Exemplos de registros UDDI são www.xmethods.net e uddi.microsoft.com.

Construção de um Web Service


Nesta seção demonstrarei o processo de criação de três tipos de Web Services. Um será
um Web Service simples que retorna a soma de dois números. O segundo retornará um
DataSet contendo uma tabela do banco de dados Northwind. O terceiro demonstrará o
uso do atributo [WebMethod].
Na maioria, criar os arquivos iniciais para o Web Service é um processo passo a passo
ao utilizar o assistente do Delphi for .NET.
1. Selecione File, New, Other do menu principal para carregar a caixa de diálogo New
Items.
2. Selecione Item Category, Delphi ASP Projects.

3. Selecione o ícone ASP.NET Web Service Application (ver Figura 28.1). Clique em OK.

4. Na caixa de diálogo Application Name (ver Figura 28.2), insira o nome de seu Web
Service, como MyFirstWebService e clique em OK.

FIGURA 28.1 Ícone ASP.NET Web Service Application.


Construção de um Web Service 687

Pronto! Você agora terá os arquivos WebService1.asmx e WebService1.pas. Vejamos o ar-


quivo WebService1.pas. A Listagem 28.1 mostra o conteúdo do arquivo. (As linhas irrele-
vantes a esta discussão foram removidas.) A Listagem 28.1 também contém um código
adicional que acrescentei. Esse código aparece em negrito.

FIGURA 28.2 Nomeando a aplicação ASP.NET Web Service.

LISTAGEM 28.1 Primeiro exemplo de Web Service


1: unit WebService1;
2:
3: interface
4:
5: uses
6: System.Collections, System.ComponentModel,
7: System.Data, System.Diagnostics, System.Web,
8: System.Web.Services;
9:
10: type
11: TWebService1 = class(System.Web.Services.WebService)
12: strict private
13: components: IContainer;
14: procedure InitializeComponent;
15: strict protected
16: procedure Dispose(disposing: boolean); override;
17: public
18: constructor Create;
19: // Método Web Service de exemplo
20: [WebMethod]
21: function HelloWorld: string;
22: [WebMethod]
23: function Add(A, B: Integer): Integer;
24: end;
25:
26: implementation
27:
28: constructor TWebService1.Create;
688 Capítulo 28 Criando Web Services

LISTAGEM 28.1 Continuação


29: begin
30: inherited;
31: InitializeComponent;
32: end;
33:
34: procedure TWebService1.Dispose(disposing: boolean);
35: begin
36: if disposing and (components < > nil) then
37: components.Dispose;
38: inherited Dispose(disposing);
39: end;
40:
41: function TWebService1.HelloWorld: string;
42: begin
43: Result := 'Hello World';
44: end;
45:
46: function TWebService1.Add(A, B: Integer): Integer;
47: begin
48: Result := A + B;
49: end;
50:
51: end.

u Localize o código no CD: \Code\Chapter 28\Ex01.

Você também notará que removi o caractere de comentário do método HelloWorld( )


(linhas 41–44). Nesse ponto você tem um Web Service válido que pode ser implantado e
consumido externamente.
Primeiro, você verá que TWebService1 descende de System.Web.Services.WebService (li-
nha 11). Realmente não é necessário descender dessa classe para criar um Web Service.
Entretanto, fazendo isso, você obtém acesso fácil a objetos ASP.NET comuns como Appli-
cation, Context, Session e assim por diante.
A classe TWebService1 é semelhante a qualquer outra classe Delphi. Uma diferença é
que as declarações de método são precedidas pelo atributo [WebMethod]. Esse atributo é o
responsável por especificar o método Web Service que é exposto externamente. Certas
propriedades desse atributo também podem permitir aos desenvolvedores estender ou
alterar o comportamento do método. Discutirei o atributo [WebMethod] mais detalhada-
mente em breve.

NOTA
Você notará que o atributo [WebMethod] é adicionado pelo IDE para a implementação do exemplo
HelloWorld( ), mas o removi no exemplo. Isso é incorreto. O [WebMethod] só é necessário na se-
ção de interface. Adicioná-lo à implementação resultará em duplicação nos metadados.
Construção de um Web Service 689

Nesse ponto você pode testar o Web Service, executando-o.

DICA
Uma maneira fácil de executar a aplicação sem depurá-la dentro do Delphi IDE é selecionar Select
Run, Run Without Debugging a partir do menu principal. Prossigo e adiciono o botão de ferra-
menta à minha barra de ferramentas porque freqüentemente utilizo essa opção. Você pode fazer
isso clicando com o botão direito do mouse na barra de ferramentas e selecionando a opção Cus-
tomize. A partir da guia Commands da caixa de diálogo Custom, você pode arrastar os botões de
comando que quiser para a barra de ferramentas.

A Figura 28.3 é HMTL Interface que mostra o Web Service.


Quando um URI de base do Web Service é solicitado sem nenhum parâmetro, o
ASP.NET retorna a descrição do serviço no formulário, mostrado na Figura 28.3. Você
pode ver que o nome da classe que implementa o Web Service é mostrado na parte supe-
rior do navegador. Além disso, mostra os nomes dos métodos que estão disponíveis para
o usuário do Web Service. Em seguida, mostra algumas informações relacionadas ao de-
senvolvedor descritivo sobre a alteração do namespace para o Web Service. Clicando no
link Service Description, você é levado à página mostrada na Figura 28.4.
A Figura 28.4 mostra a Service Description em XML, também conhecido como o
WDSL do Web Service. Isso serve para os leitores que examinariam a descrição de serviço
no formato XML. E também serve para ferramentas que podem automaticamente gerar
classes, interfaces de teste com o usuário e assim por diante. Voltando à página anterior
(consulte a Figura 28.3), você realmente pode testar os métodos. De fato, clique no link
Add e você será levado à página mostrada na Figura 28.5.

FIGURA 28.3 Saída a partir de MyFirstWebSerice.


690 Capítulo 28 Criando Web Services

FIGURA 28.4 Descrição de serviço em XML.

FIGURA 28.5 Testando o Web Service.

Agora você pode inserir dois valores de inteiro ao controle TextBoxs e pressionar o bo-
tão Invoke, que retornará o seguinte resultado:
Construção de um Web Service 691

<?xml version="1.0" encoding="utf-8" ?>


<int xmlns="http://tempuri.org/">5</int>

Você pode também invocar o Web Service utilizando o protocolo HTTP-GET pelo se-
guinte URL:

http://localhost/MyFirstWebService/WebService1.asmx/Add?A=3&B=4

Você também pode invocar o Web Service utilizando o protocolo HTTP-POST que utili-
za o seguinte documento .html:
<html>
<form method="post" action="http://localhost/MyFirstWebService
➥/WebService1.asmx/Add">
<input name="A" value ="2">
<input name="B" value ="4">
<input type="submit" value="Add!">
</form>
</html>

Isso é tudo que há a fazer para criar um Web Service – de todo modo, um simples.
Mais adiante, examinaremos o consumo desse Web Service em uma aplicação cliente.
No próximo exemplo, criarei um Web Service que serve um DataSet para o consumidor.
Antes, preciso discutir brevemente o atributo [WebService].

O atributo [WebService]
Quando você cria um Web Service, três propriedades de um Web Service obtém valores
padrão. Você deve alterar esses valores antes de publicar o Web Service. Essas proprieda-
des são description, name e namespace. Na Figura 28.3, você verá que o nome de Web Service
é WebService1 e o namespace é o padrão de http://tempuri.org/. Você deve alterar especial-
mente o namespace padrão antes de liberar o Web Service para a produção. Uma con-
venção a utilizar é formar o namespace utilizando um domínio de empresa combinado
com seu Web Service específico. Por exemplo,
www.xapware.com/MyFirstWebService

Não há a necessidade de haver arquivo físico ou diretório por trás do nome do na-
mespace; ele é utilizado apenas como um identificador lógico.
Você pode alterar essas propriedades por meio do atributo [WebService]. Esse atributo
é colocado acima da declaração de classe do Web Service, como mostrado aqui:
[WebService(
Namespace='www.xapware.com/MyFirstWebService',
Name='My First Web Service',
Description='This is My First Web Service which demonstrates two methods.')]
TWebService1 = class(System.Web.Services.WebService)

Fazendo isso, a saída do Web Service torna-se agora a saída mostrada na Figura 28.6.
692 Capítulo 28 Criando Web Services

FIGURA 28.6 Saída MyFirstWebService depois de alterar o atributo [WebService].

Você notará que o nome e descrição do Web Service agora mostram o que foi especi-
ficado nas propriedades do atributo[WebService]. Além disso, notará também que as des-
crições e o exemplo de código também são removidos. Essas informações de desenvolve-
dor são removidas porque ter um namespace diferente supõe que o Web Service está
pronto para entrar em produção.

Retornando dados a partir de um Web Service


Para criar esse Web Service, segui os mesmos passos do exemplo anterior, exceto pelo
fato de que o que chamei MySecondWebService. Também renomeei a classe de implementa-
ção como WebService2. Nesse Web Service, criei a função mostrada na Listagem 28.2.

LISTAGEM 28.2 Web Service que demonstra um resultado de DataSet


1: function TWebService2.GetEmployees: DataSet;
2: const
3: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
4: c_sel = 'Select * From employees';
5: var
6: sqlcn: SqlConnection;
7: sqlDA: SqlDataAdapter;
8: ds: DataSet;
9: begin
10: try
11: sqlcn := SqlConnection.Create(c_cnstr);
Construção de um Web Service 693

LISTAGEM 28.2 Continuação


12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: ds := DataSet.Create;
14: sqlDA.Fill(ds, 'employees');
15: sqlcn.Close;
16: Result := ds;
17: except
18: Result := nil;
19: Ralse;
20: end;
21: end;

u Localize o código no CD: \Code\Chapter 28\Ex02\.

Você deve reconhecer esse processo de recuperação de um DataSet do Northwind. A


linha 17 é a linha significativa que retorna a classe DataSet, que será empacotada como
XML, como você verá. Ao executar o Web Service, a página na Figura 28.7 será exibida.
Primeiro, note a adição de uma descrição abaixo do nome do método. Discutirei isso
em algum momento. Ao testar o Web Service, você verá a página com XML, como mos-
tra a Figura 28.8.
Você deve reconhecer esses dados a partir da tabela employee no banco de dados
Northwind. Mais adiante, demonstrarei como você consumiria esse DataSet em uma apli-
cação ASP.NET ou Windows Forms.

FIGURA 28.7 Saída MySecondWebService.


694 Capítulo 28 Criando Web Services

FIGURA 28.8 A saída de XML da tabela employees do Northwind.

Explicação sobre o atributo [WebMethod]


No exemplo anterior talvez você tenha notado a seguinte linha acima do método
GetEmployees( ):

[WebMethod(Description='Gets the employees table')]

Já foi mencionado que o atributo [WebMethod] é o responsável por expor um método


externamente. Esse atributo contém propriedades adicionais que podem ser configura-
das, por exemplo, descrição, que foi demonstrada no exemplo anterior. A Tabela 28.1 lis-
ta as várias propriedades de [WebMethod].

TABELA 28.1 Propriedades [WebMethod]


Propriedade Descrição
BufferResponse Determina se a resposta da solicitação é armazenada em buffer na memória
antes de enviá-la de volta para o cliente. Quando True (o padrão), a resposta é
armazenada em buffer na memória até completar ou até que o buffer fique
cheio. Então, ela é enviada para o cliente. Quando False, a resposta é enviada
em partes de 16 KB. Tenha em mente que esse tamanho é um detalhe de
implementação do MS CLR v1.1, que poderia alterar no futuro. Você só
configuraria isso como False se soubesse que a resposta seria excessiva, o que
poderia aprimorar o desempenho, tendo em mente que também poderia
reduzir o uso de memória e aprimorar (diminuir) a latência.
Consumindo Web Services 695

TABELA 28.1 Continuação


Propriedade Descrição
CacheDuration Essa propriedade é útil se você for invocar um método várias vezes utilizando o
mesmo conjunto de parâmetros. Ela faz com que o servidor armazene a
resposta em cache pelo número especificado de segundos. Quando uma
solicitação subseqüente é feita dentro do intervalo de tempo, a resposta é
obtida a partir do cache, aprimorando potencialmente assim o desempenho.
Description Essa propriedade fornece uma descrição String do método.
EnableSession Essa propriedade ativa/desativa o estado de sessão. Quando ativado, o método
Web Service poderia utilizar o objeto Session.
MessageName Essa propriedade permite especificar um alias para o nome de método. É útil
distinguir isso entre os métodos sobrecarregados do mesmo nome.
TransactionOption Essa propriedade permite que o código dentro de um Web Service funcione de
modo semelhante a uma transação de banco de dados. O suporte de transação
é fornecido por meio de um nível COM+ de suporte transacional.

Para demonstrar o uso do atributo [WebMethod], considere a declaração dos seguintes


métodos:

[WebMethod]
function Add(A, B: Integer): Integer; overload;
[WebMethod]
function Add(A, B: Double): Double; overload;

Ao utilizar uma classe diretamente, ter apenas a diretiva de sobrecarga seria suficien-
te para utilizar ambos os métodos. Isso porque o compilador seria capaz de converter os
métodos por seus parâmetros. Entretanto, tentar executar um Web Service contendo es-
ses métodos resultaria em um erro de servidor porque o padrão SOAP não suporta méto-
dos sobrecarregados; ele se baseia no nome do método para escolha do método.
Para corrigir isso você utilizaria a propriedade MessageName, como mostrado aqui:

[WebMethod (
MessageName='AddInt')]
function Add(A, B: Integer): Integer; overload;
[WebMethod(
MessageName='AddDouble')]
function Add(A, B: Double): Double; overload;

Os consumidores do Web Service veriam e invocariam o método de acordo com os


nomes especificados como a propriedade MessageName.

u Localize o código no CD: \Code\Chapter 28\Ex03\.

Consumindo Web Services


Esta seção discute o processo de consumo do Web Service. Diz-se que as aplicações que
utilizam Web Services consomem o Web Service e, portanto, são conhecidas como con-
696 Capítulo 28 Criando Web Services

sumidores. Um consumidor Web Service deve realizar pelo menos três passos a fim de
utilizar o Web Service. Esses passos são a descoberta (um processo de extrair informações
sobre um Web Service), gerar uma classe proxy e utilizar a classe proxy para invocar os
métodos Web Service. Os dois primeiros passos podem ser feitos manualmente; entre-
tanto, o Delphi IDE fornece as ferramentas integradas para tornar o processo muito mais
fácil. Percorrerei o processo passo a passo.

O processo de descoberta
Antes de que possa utilizar um Web Service, você tem de saber utilizá-lo. Você deve des-
cobrir os métodos, propriedades, parâmetros, tipos disponíveis e assim por diante. Lem-
bre-se de que o documento WSDL é como os Web Services descrevem a si próprios para
consumidores. Portanto, para cada Web Service que pretende utilizar, você terá de exa-
minar esse documento. É verdade que você pode examinar o WSDL e escrever o código
direta e manualmente na classe Delphi necessária para utilizar o serviço. Entretanto, isso
é desnecessário porque o Delphi IDE examinará o WSDL do Web Service para você e rea-
lizará os passos necessários para permitir a utilização do Web Service; especificamente,
você criará uma classe proxy. Os seguintes exemplos assumem que conhecem a localiza-
ção do Web Service que você pretende utilizar.

Construindo uma classe proxy


Nesta seção, discuto como criar uma aplicação cliente que consome os dois últimos Web
Services criados anteriormente neste capítulo. Os passos são:
1. Criar a aplicação e salvá-la em um diretório.

2. Adicionar um Web Reference; isso criará a classe proxy.

3. Utilizar a classe proxy.

Supondo que o passo 1 está concluído, adicionar a Web Reference é simples. Sim-
plesmente selecione o projeto dentro do Project Manager no IDE e invoque o menu local
clicando com o botão direito do mouse nele. Uma das opções é Add Web Reference (ver
Figura 28.9).
Isso carrega a caixa de diálogo Add Web Reference. Por essa caixa de diálogo você
pode inserir a URL do Web Service URLs conhecidos ou também pode selecionar de um
dos diretórios UDDI apresentados na caixa de diálogo. Por ora, utilizaremos o URL de um dos
Web Services que criamos. A URL que de que precisamos é

http://localhost/MyFirstWebService/WebService1.asmx?WSDL

Especificar o parâmetro ?WSDL retornará o documento WSDL de que precisamos para


gerar uma classe proxy. Ao clicar no botão de seta azul na caixa de diálogo Add Web Refe-
rence, você verá o WSDL na caixa de diálogo, como mostrado na Figura 28.10.
Nesse ponto você pode clicar no botão Add Reference para gerar a classe proxy. Exa-
minando o Project Manager, você verá que alguns arquivos foram gerados e adicionados
ao seu projeto. Esses arquivos são:
Consumindo Web Services 697

— WebService1.map – O arquivo .map é um arquivo XML que contém informações


sobre as referências WDSL para o Web Service.
— WebService1.wsdl – O arquivo .wsdl é o arquivo Service Description. Ele contém
XML que descreve a interface Web Service.
— WebService1.pas – O arquivo .pas é o arquivo de origem que contém a classe
proxy.

FIGURA 28.9 Menu Add Web Reference do Project Manager.

FIGURA 28.10 A caixa de diálogo Add Web Reference.


698 Capítulo 28 Criando Web Services

A próxima seção discute exatamente o que essa classe proxy é e como utilizá-la a fim
de consumir o Web Service.

Utilizando a classe proxy


Uma classe proxy é uma classe que oculta a implementação interna na invocação de um
Web Service. De outra maneira, a classe proxy é uma camada de classe entre as solicita-
ções HTTP SOAP para o servidor Web e o código que você escreverá para fazer essas solici-
tações. Ela permite trabalhar com uma classe Delphi com a qual você já está acostumado.
Como você viu na seção anterior, a criação da classe proxy é simples utilizando o IDE.
Certamente você pode criar essa classe manualmente, embora isso seja inteiramente des-
necessário porque a IDE realiza essa tarefa perfeitamente.
A Listagem 28.3 mostra a classe proxy criada para o Web Service que consumimos.

LISTAGEM 28.3 Classe proxy WebService1


1: unit localhost.WebService1;
2:
3: interface
4:
5: uses System.Diagnostics,
6: System.Xml.Serialization,
7: System.Web.Services.Protocols,
8: System.ComponentModel,
9: System.Web.Services, System.Web.Services.Description;
10:
11: type
12: [System.Diagnostics.DebuggerStepThroughAttribute]
13: [System.ComponentModel.DesignerCategoryAttribute('code')]
14: [System.Web.Services.WebServiceBindingAttribute(
15: Name='My First Web ServiceSoap',
16: Namespace='www.xapware.com/MyFirstWebService')]
17: MyFirstWebService = class(System.Web.Services.Protocols.
➥SoapHttpClientProtocol)
18: public
19: constructor Create;
20: [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
21: 'www.xapware.com/MyFirstWebService/HelloWorld',
22: RequestNamespace='www.xapware.com/MyFirstWebService',
23: ResponseNamespace='www.xapware.com/MyFirstWebService',
24: Use=System.Web.Services.Description.SoapBindingUse.Literal,
25: ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.
➥Wrapped)]
26: function HelloWorld: string;
27: function BeginHelloWorld(callback: System.AsyncCallback;
Consumindo Web Services 699

LISTAGEM 28.3 Continuação


28: asyncState: System.Object): System.IAsyncResult;
29: function EndHelloWorld(asyncResult: System.IAsyncResult): string;
30: [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
31: 'www.xapware.com/MyFirstWebService/Add',
32: RequestNamespace='www.xapware.com/MyFirstWebService',
33: ResponseNamespace='www.xapware.com/MyFirstWebService',
34: Use=System.Web.Services.Description.SoapBindingUse.Literal,
35: ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.
➥Wrapped)]
36: function Add(A: Integer; B: Integer): Integer;
37: function BeginAdd(A: Integer; B: Integer; callback:
➥System.AsyncCallback;
38: asyncState: System.Object): System.IAsyncResult;
39: function EndAdd(asyncResult: System.IAsyncResult): Integer;
40: end;
41:
42: implementation
43:
44: {$AUTOBOX ON}
45: {$HINTS OFF}
46: {$WARNINGS OFF}
47:
48: constructor MyFirstWebService.Create;
49: begin
50: inherited Create;
51: Self.Url := 'http://localhost/MyFirstWebService/WebService1.asmx';
52: end;
53:
54: function MyFirstWebService.HelloWorld: string;
55: type
56: TSystem_ObjectArray = array of System.Object;
57: TArrayOfSystem_Object = array of System.Object;
58: var
59: results: TArrayOfSystem_Object;
60: begin
61: results := Self.Invoke('HelloWorld', new(TSystem_ObjectArray, 0));
62: Result := (string(results[0]));
63: end;
64:
65: function MyFirstWebService.BeginHelloWorld(callback: System.AsyncCallback;
66: asyncState: System.Object): System.IAsyncResult;
67: type
68: TSystem_ObjectArray = array of System.Object;
69: begin
70: Result := Self.BeginInvoke('HelloWorld', new(TSystem_ObjectArray, 0),
71: callback,asyncState);
72: end;
73:
700 Capítulo 28 Criando Web Services

LISTAGEM 28.3 Continuação


74: function MyFirstWebService.EndHelloWorld(
75: asyncResult: System.IAsyncResult): string;
76: type
77: TArrayOfSystem_Object = array of System.Object;
78: var
79: results: TArrayOfSystem_Object;
80: begin
81: results := Self.EndInvoke(asyncResult);
82: Result := (string(results[0]));
83: end;
84:
85: function MyFirstWebService.Add(A: Integer; B: Integer): Integer;
86: type
87: TSystem_ObjectArray = array of System.Object;
88: TArrayOfSystem_Object = array of System.Object;
89: var
90: results: TArrayOfSystem_Object;
91: begin
92: results := Self.Invoke('Add', TSystem_ObjectArray.Create(A, B));
93: Result := (Integer(results[0]));
94: end;
95:
96: function MyFirstWebService.BeginAdd(A: Integer; B: Integer;
97: callback: System.AsyncCallback;
98: asyncState: System.Object): System.IAsyncResult;
99: type
100: TSystem_ObjectArray = array of System.Object;
101: begin
102: Result := Self.BeginInvoke('Add', TSystem_ObjectArray.Create(A, B),
103: callback, asyncState);
104: end;
105:
106: function MyFirstWebService.EndAdd(asyncResult: System.IAsyncResult):
➥ Integer;
107: type
108: TArrayOfSystem_Object = array of System.Object;
109: var
110: results: TArrayOfSystem_Object;
111: begin
112: results := Self.EndInvoke(asyncResult);
113: Result := (Integer(results[0]));
114: end;
115:
116: end.

u Localize o código no CD: \Code\Chapter 28\Ex04\.

A classe proxy serve como um objeto substituto para o Web Service e declara méto-
dos que podem ser invocados pela aplicação consumidora, métodos esses que invocam
Consumindo Web Services 701

os métodos do Web Service. De fato, ela define duas maneiras de invocar os métodos
Web Service. Você pode fazer isso síncrona ou assincronamente. Os métodos síncronos
recebem o mesmo nome que tinham no Web Service (ou como o MessageName no atributo
[WebMethod]). Por exemplo, as linhas 26 e 36 mostram as declarações de método para os
métodos HelloWorld( ) e Add( ) do Web Service. A invocação de método assíncrona re-
quer dois métodos por método Web Service. Esses são definidos utilizando a forma Be-
ginNomeDaFunção( ) EndNomeDaFunção( ). Você verá essas declarações de métodos nas linhas
27–29 para o método HelloWorld( ) e nas linhas 37–39 para o método Add( ).

NOTA
Talvez você tenha notado a seguinte sintaxe incluída como um parâmetro para Self.Invoke( )
(linha 92):

TSystem_ObjectArray.Create(A, B)
Essa é a nova sintaxe Delphi para criar um array dinâmico. Dado o tipo dinâmico de array declara-
do como,
TSystem_ObjectArray = Array of System.Object;
você pode criar uma instância desse tipo utilizando as formas
TSystem_ObjectArray.Create('hello', 'world');
ou
new(TSystem_ObjectArray, 2);
A primeira forma cria e adiciona dois elementos ao array. A segunda simplesmente cria o Array
com espaço para dois elementos.

Observe que a classe proxy descende da classe de protocolo SoapHttpClientProtocol,


que é definida no namespace System.Web.Services.Protocols. Se quiser utilizar os protoco-
los HTTP-GET ou HTTP-POST, você descenderia das classes apropriadas (HttpGetClient-
Protocol ou HttpPostClientProtocol). Além disso, você teria de especificar o atributo corres-
pondente, que é o HTTPMethodAttribute.
Utilizar o Web Service é simplesmente muito fácil. O próximo código ilustra como
você utilizaria esse Web Service para adicionar dois números inseridos a partir de dois
controles TextBox:
procedure TWinForm1.Button1_Click(sender: System.Object; e: System.EventArgs);
var
mfws: MyFirstWebService;
result: Integer;
begin
mfws := MyFirstWebService.Create;
Result := mfws.Add(Convert.ToInt32(TextBox1.Text),
Convert.ToInt32(TextBox2.Text));
Label1.Text := Label1.Text + Result.ToString;
end;

u Localize o código no CD: \Code\Chapter 28\Ex04\.


702 Capítulo 28 Criando Web Services

Consumindo um DataSet a partir de um Web Service


Neste próximo exemplo, consumi o seguinte Web Service pelo diálogo Add Web Refe-
rence:

http://localhost/MySecondWebService/WebService2.asmx

Isso resultou na classe proxy mostrada na Listagem 28.4.

LISTAGEM 28.4 Classe proxy WebService2


1: unit localhost.WebService2;
2:
3: interface
4:
5: uses System.Diagnostics,
6: System.Xml.Serialization,
7: System.Web.Services.Protocols,
8: System.ComponentModel,
9: System.Web.Services, System.Web.Services.Description, System.Data;
10:
11: type
12: [System.Diagnostics.DebuggerStepThroughAttribute]
13: [System.ComponentModel.DesignerCategoryAttribute('code')]
14: [System.Web.Services.WebServiceBindingAttribute(
15: Name='My Second Web ServiceSoap',
16: Namespace='www.xapware.com/MySecondWebService')]
17: MySecondWebService = class(System.Web.Services.Protocols.
➥SoapHttpClientProtocol)
18: public
19: constructor Create;
20: [System.Web.Services.Protocols.SoapDocumentMethodAttribute(
21: 'www.xapware.com/MySecondWebService/GetEmployees',
22: RequestNamespace='www.xapware.com/MySecondWebService',
23: ResponseNamespace='www.xapware.com/MySecondWebService',
24: Use=System.Web.Services.Description.SoapBindingUse.Literal,
25: ParameterStyle=System.Web.Services.Protocols.
➥SoapParameterStyle.Wrapped)]
26: function GetEmployees: System.Data.DataSet;
27: function BeginGetEmployees(callback: System.AsyncCallback;
28: asyncState: System.Object): System.IAsyncResult;
29: function EndGetEmployees(asyncResult: System.IAsyncResult):
30: System.Data.DataSet;
31: end;
32:
33: implementation
34:
35: {$AUTOBOX ON}
36: {$HINTS OFF}
37: {$WARNINGS OFF}
Consumindo Web Services 703

LISTAGEM 28.4 Continuação


38:
39: constructor MySecondWebService.Create;
40: begin
41: inherited Create;
42: Self.Url := 'http://localhost/MySecondWebService/WebService2.asmx';
43: end;
44:
45: function MySecondWebService.GetEmployees: System.Data.DataSet;
46: type
47: TSystem_ObjectArray = array of System.Object;
48: TArrayOfSystem_Object = array of System.Object;
49: var
50: results: TArrayOfSystem_Object;
51: begin
52: results := Self.Invoke('GetEmployees', new(TSystem_ObjectArray, 0));
53: Result := (System.Data.DataSet(results[0]));
54: end;
55:
56: function MySecondWebService.BeginGetEmployees(callback:
➥System.AsyncCallback;
57: asyncState: System.Object): System.IAsyncResult;
58: type
59: TSystem_ObjectArray = array of System.Object;
60: begin
61: Result := Self.BeginInvoke('GetEmployees', new(TSystem_ObjectArray, 0),
62: callback, asyncState);
63: end;
64:
65: function MySecondWebService.EndGetEmployees(asyncResult:
66: System.IAsyncResult): System.Data.DataSet;
67: type
68: TArrayOfSystem_Object = array of System.Object;
69: var
70: results: TArrayOfSystem_Object;
71: begin
72: results := Self.EndInvoke(asyncResult);
73: Result := (System.Data.DataSet(results[0]));
74: end;
75:
76: end.

u Localize o código no CD: \Code\Chapter 28\Ex05\.

Na Listagem 28.4 você verá a declaração dos métodos GetEmployees( ), Begin-


GetEmployees( ) e EndGetEmployees( ) – cada um dos quais retorna uma instância DataSet.
Utilizar esse Web Service não é diferente do anterior em termos de simplicidade. O próxi-
704 Capítulo 28 Criando Web Services

mo código invoca o método síncrono, GetEmployees( ), e atribui os resultados à sua pró-


pria instância DataSet. Essa instância, que é vinculada a um DataGrid, resulta na saída mos-
trada na Figura 28.11.

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);


var
ds: DataSet;
msws: MySecondWebService;
begin
msws := MySecondWebService.Create;
ds := msws.GetEmployees;
DataGrid1.DataSource := ds.Tables['Employees'];
end;

u Localize o código no CD: \Code\Chapter 28\Ex05\.

NOTA
Ao retornar um DataSet a partir de um método, ela faz o método .NET específico porque outro
tipo de cliente não entenderia como utilizar o resultset.

Invocando um método assíncrono de um Web Service


A classe proxy cria um par de métodos que você utiliza para invocar um método Web Ser-
vice assincronamente. A Listagem 28.5 ilustra como fazer isso utilizando o mesmo exem-
plo da Listagem 28.4.

FIGURA 28.11 Saída de um DataSet de um Web Service.


Segurança em Web Services 705

LISTAGEM 28.5 Método WebService2 invocado assincronamente


1: procedure TWinForm.Button2_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: ds: DataSet;
5: msws: MySecondWebService;
6: ar: IAsyncResult;
7: begin
8: msws := MySecondWebService.Create;
9: ar := msws.BeginGetEmployees(nil, nil);
10:
11: // Faz outro processamento local
12: ar.AsyncWaitHandle.WaitOne;
13:
14: ds := msws.EndGetEmployees(ar);
15: DataGrid1.DataSource := ds.Tables['Employees'];
16: end;

u Localize o código no CD: \Code\Chapter 28\Ex05\.

A maior parte da utilização assíncrona será provavelmente mais complexa do que a


mostrada aqui. O propósito desse exemplo é mostrar como esses métodos são invocados.
A linha 9 chama o método BeginGetEmployees( ), que retorna uma instância IAsyncResult.
IAsyncResult é uma interface que representa o status de uma operação assíncrona. Ela tem
algumas propriedades – uma das quais é AsyncWaitHandle. Você utiliza esse handle para es-
perar uma operação assíncrona completar. Opcionalmente, você poderia fornecer seu
próprio método AsyncCallBack para o método BeginGetEmployees( ).
Na linha 12, o método AsyncWaitHandle.WaitOne( ) é invocado. Esse método bloqueia a
thread atual até que um sinal seja recebido pelo AsyncWaitHandle. Quando um sinal é rece-
bido, a operação está completa e o método EndGetEmployees( ) é invocado. Esse método re-
torna a instância DataSet que é então vinculada ao DataGrid.

Segurança em Web Services


Provavelmente, há várias maneiras de tornar um Web Service seguro. Uma delas envol-
ve a segurança geral do IIS. Outra envolve acesso por parte do usuário ao Web Service
por meio da autenticação. Discutirei a última aqui. A segurança geral será discutida no
Capítulo 31.
A maneira típica de alguém tornar um recurso seguro é por meio de uma combina-
ção de nome de usuário/senha. Em aplicações de banco de dados, por exemplo, você
poderia armazenar uma forma encriptada da senha do usuário no banco de dados.
Quando o usuário tentar efetuar logon da aplicação, a aplicação solicita o nome amigá-
vel da senha, encripta essa senha e a compara com a que reside no banco de dados. Essa
abordagem é mais segura do que utilizar um algoritmo de hash de uma via como o hash
MD5. Caso contrário, invasores seriam capazes de derivar a senha a partir de sua forma
de hash.
706 Capítulo 28 Criando Web Services

Essa mesma abordagem pode ser utilizada para tornar um Web Service seguro. Con-
tanto que você armazene a forma encriptada das credenciais do usuário, você será capaz
de autenticar o usuário.
Uma maneira de realizar isso seria passar o nome de usuário e senha para os méto-
dos; entretanto, como você provavelmente pode imaginar, isso tornaria bem incômodo
ter de definir e esperar os consumidores de seus Web Services passarem o nome de usuá-
rio e a senha a cada invocação de método.
Uma maneira melhor é passar as informações de autenticação no cabeçalho SOAP.
Um cabeçalho SOAP são informações de XML que são anexadas à solicitação SOAP en-
viada para o Web Service. É uma maneira conveniente passar junto com informações
adicionais à solicitação que não faz parte da lista de parâmetros.
Para isso funcionar seu Web Service deve declarar uma classe que descende da clas-
se SoapHeader. SoapHeader é definido no namespace System.Web.Services.Protocols. Essa
classe contém as propriedades que o Web Service precisa para autenticar o usuário. A
classe Web Service também contém uma instância pública do SoapHeader descendente
que será acessível tanto ao Web Service como ao consumidor. A Listagem 28.6 mostra
um Web Service que utiliza uma classe descendente SoapHeader.

LISTAGEM 28.6 Classe descendente SoapHeader para autenticação de usuário


1: unit WebService1;
2:
3: interface
4:
5: uses
6: System.Collections, System.ComponentModel,
7: System.Data, System.Diagnostics, System.Web,
8: System.Web.Services, System.Web.Services.Protocols,
9: System.Security.Cryptography, System.Text;
10:
11: type
12:
13: TAuth = class(SoapHeader)
14: UserName: String;
15: Password: String;
16: end;
17:
18: TWebService1 = class(System.Web.Services.WebService)
19: strict protected
20: procedure Dispose(disposing: boolean); override;
21: private
22: function Authenticate(aUserName, aPassword: String): Boolean;
23: public
24: Auth: TAuth;
25: constructor Create;
26: [WebMethod( ), SoapHeader('Auth')]
27: function HelloWorld: string;
Segurança em Web Services 707

LISTAGEM 28.6 Continuação


28: end;
29:
30: implementation
31:
32: constructor TWebService1.Create;
33: begin
34: inherited;
35: InitializeComponent;
36: end;
37:
38: procedure TWebService1.Dispose(disposing: boolean);
39: begin
40: if disposing and (components < > nil) then
41: components.Dispose;
42: inherited Dispose(disposing);
43: end;
44:
45: function TWebService1.HelloWorld: string;
46: begin
47: if Auth = nil then
48: raise Exception.Create('Invalid Login');
49:
50: if Authenticate(Auth.UserName, Auth.Password) then
51: Result := ' You are logged in.'
52: else
53: Result := 'Incorrect username/password combo';
54: end;
55:
56: function TWebService1.Authenticate(aUserName, aPassword: String): Boolean;
57: var
58: PasswordInDB: array of byte;
59: encoder: UTF8Encoding;
60: md5Hasher: MD5CryptoServiceProvider;
61: HashedBytes: array of byte;
62: dbPWStr: String;
63: aPWStr: String;
64: i: integer;
65: begin
66: try
67: encoder := UTF8Encoding.Create;
68: md5Hasher := MD5CryptoServiceProvider.Create;
69: // Suponha que você obteve a senha de hash do banco de dados
70: PasswordInDB := md5Hasher.ComputeHash(encoder.GetBytes(
➥'ZacharyCamaro'));
71:
72: aPWStr := aUserName + APassword;
73: // Faz um hash da senha passada.
74: HashedBytes := md5Hasher.ComputeHash(encoder.GetBytes(aPWStr));
708 Capítulo 28 Criando Web Services

LISTAGEM 28.6 Continuação


75: aPWStr := '';
76:
77: // Converte em strings regulares
78: for i := Low(PasswordInDB) to High(PasswordInDB) do7979: dbPWStr :=
dbPWStr + Convert.ToString(PasswordInDB[i], 16);
80:
81: for i := Low(HashedBytes) to High(HashedBytes) do
82: aPWStr := aPWStr + Convert.ToString(HashedBytes[i], 16);
83:
84: // Compara
85: Result := System.String.Compare(dbPWStr, aPWStr) = 0;
86:
87: except
88: Result := False;
89: end;
90: end;
91:
92: end.

u Localize o código no CD: \Code\Chapter 28\Ex06\.

As linhas 13–16 mostram a declaração da classe de descendente SoapHeader, TAuth.


Essa classe contém dois campos, UserName e Password. Se quiser passar informações adicio-
nais a esse Web Service, esse é o local em que você declararia esses campos.
Observe também que o Web Service declara uma instância da classe TAuth (linha 24).
Outra modificação para o Web Service é adicionar o atributo SoapHeader aos métodos Web
Service, que permite que o Web Service receba o cabeçalho, crie a instância TAuth e confi-
gure os campos correspondentes. Esse atributo aceita o nome da instância SoapHeader –
nesse caso, 'Auth' – como um parâmetro.
Você também verá a declaração de um método Authenticate( ), que imediatamente
discutirei.
Ao examinar o método HelloWorld( ) (linhas 45–54), você vê que se o Auth não é for-
necido, o Web Service levanta uma exceção. O consumidor deve fornecer uma instância
dessa classe; ou há a possibilidade de acesso não-autorizado. A linha 50 invoca o método
Authenticate( ), passando os membros Auth.UserName e Auth.Password como parâmetros.
Mais adiante você verá como esses parâmetros são inicializados pela aplicação consumi-
dora. Authenticate( ) retornará True ou False dependendo de se as credenciais válidas de
usuário foram fornecidas.
O método Authenticate( ) é onde está o principal. Esse método utiliza a classe para
computar o hash MD5 com as credenciais do usuário, o que ele então compara com
credenciais fictícias, derivadas do banco de dados para ver se há uma correspondên-
cia. Se houver, o usuário é autorizado; caso contrário, o acesso ao Web Service é recu-
sado.
No lado do cliente, o método que utiliza esse Web Service é mostrado no próximo
código:
Segurança em Web Services 709

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);


var
ws: TWebService1;
begin
ws := TWebService1.Create;
ws.TAuthValue := TAuth.Create;
ws.TAuthValue.UserName := tbxUserName.Text;
ws.TAuthValue.Password := tbxPassword.Text;
TextBox1.Text := ws.HelloWorld;
end;

u Localize o código no CD: \Code\Chapter 28\Ex07\.

Nesse código você verá que o cliente é responsável por criar a instância TAuth. O Delphi
IDE gerou esse membro como TAuthValue dentro da classe proxy. Nesse exemplo, as strings
UserName e Password são recuperadas a partir de dois controles TextBoxs. TextBox1 será preenchi-
do com o resultado da função, que indicará a autorização ou rejeição.
Há uma desvantagem para o esquema fornecido aqui. Isto é, as informações que
você está passando para o Web Service estão em formato de texto simples. Obviamente,
alguém curioso seria capaz de examinar o pacote de XML se ele realmente quisesse. Mas
você poderia utilizar um esquema de encriptação semelhante antes de enviar as informa-
ções para o Web Service.
NESTE CAPÍTULO
— Tecnologias remoting CAPÍTULO 29
disponíveis atualmente
— Arquiteturas distribuídas .NET Remoting
— Os benefícios do
desenvolvimento de
aplicações multicamada
e Delphi
— Os princípios básicos do .NET
Remoting
— Sua primeira aplicação .NET R emoting é o processo pelo qual aplicações se
Remoting comunicam por meio de certas froteiras. A forma mais
simples de fronteira é um processo, enquanto a mais
complexa (e mais comum) é uma rede.
COM, DCOM, CORBA são tecnologias remoting que,
apesar dos nomes diferentes e das incompatibilidades,
funcionam seguindo um padrão semelhante: um cliente
empacota o nome e os parâmetros de uma solicitação,
envia-os por meio da fronteira para uma localização
predefinida e espera uma resposta.
As tecnologias de remoting permitem entrar no
mundo dos “sistemas distribuídos” e alcançar um nível
de flexibilidade e escalabilidade impossível de outro
modo.

Tecnologias remoting disponíveis


atualmente
As formas de remoting são utilizadas no Delphi há já
muito tempo.
Você poderia ter utilizado os frameworks
COM/DCOM ou CORBA incluídos desde a versão 3,
o suporte para SOAP WebServices introduzidos na
versão 6 ou até mesmo simples componentes de TCP/IP
como o Indy.
A seção a seguir lista algumas das tecnologias de
remoting mais comuns disponíveis na indústria de IT.

Soquetes
Os soquetes são a base de toda comunicação de rede e
permitem aos desenvolvedores acessar recursos de rede
como se eles fossem streams.
Tecnologias remoting disponíveis atualmente 711

Os soquetes fornecem controle pleno aos desenvolvedores quando se trata de recur-


sos de baixo nível. Infelizmente, os soquetes são difíceis de programar e adicionam com-
plexidade desnecessária ao desenvolvimento de aplicações distribuídas.
Todas as outras tecnologias listadas nesta seção são construídas com base nos soquetes
e fornecem aos desenvolvedores camadas de abstração que simplificam a comunicação.

RPC
A RPC é tanto uma abreviação de Remote Procedure Call como uma especificação proje-
tada pelo Open Group (www.opengroup.org/).
A RPC delineou muitos dos pilares arquitetônicos utilizados em tecnologias remo-
ting como DCOM, SOAP e CORBA.
Estes incluem
— proxies e stubs: código presente em clientes e servidores cujo propósito é fazer
chamadas remotas parecerem chamadas locais
— empacotamento de dados (data marshalling): o processo de empacotar um fluxo
de dados contendo um nome de solicitação e seus parâmetros
— Interface Definition Language (IDL): um tipo de documento que lista o nome e os
parâmetros das procedures que os clientes remotos podem invocar

Consulte o www.opengroup.org/ para obter informações adicionais.

Java RMI
O Java RMI permite aos programadores criar Java distribuído para aplicações Java em que
os métodos de objetos Java remotos podem ser invocados de outras Java Virtual Machi-
nes no mesmo ou em um computador diferente.
É o equivalente da .NET Remoting para a plataforma Java, mas só é acessível a partir
de ambientes Java.
Consulte o http://java.sun.com/products/jdk/rmi/ para obter informações adicionais.

CORBA
CORBA é a abreviação de Common Object Request Broker Architecture e é uma especifi-
cação aberta, independentemente de fornecedores, definida pelo Object Management
Group (www.omg.org).
Empresas como Borland, Iona, PrismTech e 2AB fornecem produtos que implemen-
tam a especificação.
O CORBA é baseado no protocolo IIOP para transmitir mensagens.
Leia mais sobre o CORBA em www.omg.com e sobre o IIOP em http://www.omg.org/
gettingstarted/corbafaq.htm#RemoteInvoke.

XML-RPC
A XML-RPC é uma especificação para um protocolo de sistema de mensagens baseado
em XML projetado para funcionar em HTTP.
712 Capítulo 29 .NET Remoting e Delphi

A codificação XML-RPC é muito simples de entender e utilizar, e fornece suporte


para tipos de dados mais simples (inteiros, strings e assim por diante) e estruturas com-
plexas (objetos).
Há muitas implementações XML-RPC disponíveis para as plataformas Linux, Unix e
Windows.
Consulte o http://www.xmlrpc.com para obter informações adicionais.

DCOM
O Distributed Component Object Model (DCOM) era a tecnologia recomendada pela
Microsoft para construir aplicações distribuídas e, em termos simples, permitir acessar
servidores COM Automation a partir de computadores remotos.
O DCOM foi projetado para funcionar utilizando diferentes transportes de rede (isto
é, TCP/IP e HTTP), para ser compatível com diversas plataformas, e foi baseado na especi-
ficação Open Group DCE-RPC.
Embora o framework forneça suporte para arquiteturas não-x86 e tenha sido porta-
do para algumas plataformas Unix, ele nunca foi bem-sucedido em plataformas não-
Microsoft.
O Delphi suportava DCOM, a partir da versão 3, com um framework que simplifica-
va significativamente o desenvolvimento de servidores e clientes DCOM.

COM-Interop
O COM-Interop é uma tecnologia que permite interoperabilidade entre aplicações .NET
e COM/DCOM. O COM-Interop foi discutido no Capítulo 16.

SOAP
SOAP é o acrônimo de Simple Object Access Protocol.
A definição oficial localizada na especificação 1.2 recente diz:

O SOAP é um protocolo “leve” projetado para trocar informações estruturadas em um ambiente


distribuído descentralizado. O SOAP utiliza tecnologias XML para definir um framework extensível
de troca de mensagens, que fornece uma construção de mensagem que pode ser trocada sobre
uma variedade de protocolos subjacentes. O framework foi projetado para ser independente de
qualquer modelo de programação particular e outra implementação semântica específica.

O SOAP é a base dos Web Services (aplicações programaticamente acessíveis por


clientes remotos via HTTP) e é um padrão de fato, suportado por muitos fornecedores,
inclusive pela Microsoft, IBM, Borland e Sun.
Embora relativamente novo, o SOAP é o protocolo mais aceito para comunicação
em diversas plataformas. Ele é um dos melhores candidatos para escrever sistemas acessí-
veis por aplicações escritas em qualquer linguagem, a partir de qualquer plataforma. A Fi-
gura 29.1 ilustra esse relacionamento.
Arquiteturas distribuídas 713

Cliente
Java

Servidor Cliente
Delphi 8 Delphi 6/7

Cliente
.Net

FIGURA 29.1 Comunicação para diversas plataformas SOAP.

O Microsoft .NET SDK fornece uma infra-estrutura e muitas classes predefinidas para
criar e consumir Web Services que são completamente acessíveis no Delphi.
Você pode construir servidores e clientes SOAP tanto no Delphi 7 como no Delphi
for .NET, embora a abordagem que você deva utilizar seja muito diferente.

.NET Remoting
As tecnologias .NET utilizam o termo.NET Remoting para indicar duas coisas diferentes:
— Uma forma de remoting utiliza o sistema binário de mensagens e é nativa ao .NET

— O princípio básico de classes que permite qualquer tipo de remoting no .NET


(SOAP, binário ou definido pelo usuário)
Por todo este capítulo e pelo Capítulo 30, utilizaremos a segunda definição.
O protocolo do sistema binário de mensagens disponível no framework .NET Remo-
ting é a substituição oficial da Microsoft para COM/DCOM. É a maneira mais eficiente de
fazer aplicações .NET comunicar-se entre si, mas, ao contrário de SOAP, não é projetada
para comunicação entre diversas plataformas porque só funciona entre aplicações .NET.
Veremos como criar aplicações que utilizam essa forma de remoting mais adiante
neste capítulo e no Capítulo 30.

Arquiteturas distribuídas
Diz-se que um sistema dividido em dois ou mais módulos executáveis que operam em di-
ferentes máquinas, processos ou AppDomains utiliza uma arquitetura distribuída. Os
processos e AppDomains são mencionados porque o empacotamento (marshalling) ain-
da seria necessário.
Embora fosse possível argumentar que um único executável dividido em várias uni-
dades também representa um exemplo de uma arquitetura distribuída, vamos nos ater a
exemplos em que alguma forma de empacotamento é necessária.
714 Capítulo 29 .NET Remoting e Delphi

A arquitetura distribuída já existia muito tempo antes de o .NET Remoting ser cria-
do. Esses sistemas e as tecnologias utilizadas para construí-los foram os princípios básicos
para o projeto e implementação do que é agora disponível no Delphi for .NET e Micro-
soft .NET Framework. Cada uma dessas arquiteturas utiliza uma ou múltiplas tecnologias
de remoting para realizar a comunicação remota.

Cliente/Servidor
Essa é a primeira e a mais bem conhecida arquitetura distribuída. As aplicações Delphi
que utilizam tecnologias como dbGo, Interbase Express ou Borland Database Engi-
ne/SQL Links são aplicações cliente/servidor.
Esse tipo é normalmente referido como arquitetura duas camadas em que um clien-
te é responsável pela apresentação dos dados (isto é, utilizando controles data aware
como TDBGrid) e um servidor para a obtenção e armazenamento desses dados.
Embora essa arquitetura seja mais simples de entender e utilizar que as outras, ela
tem vários problemas que são críticos no ativo mundo da Internet de hoje:
— Normalmente ela só funciona em rede(s) local(is).

— Ela não pode atender eficientemente milhares de usuários concorrentes.

— Não é fácil atualizá-la e mantê-la: como cada cliente requer uma conexão perma-
nente com o banco de dados e a maior parte da lógica do negócio está contida nos
próprios clientes, sempre que uma alteração é esperada, todos os clientes preci-
sam ser reimplementados.

A Figura 29.2 ilustra um modelo cliente/servidor.

Cliente
rede local
Cliente
rede local Banco de
dados
Cliente
rede local

FIGURA 29.2 Modelo cliente-servidor.

Peer-to-peer
A arquitetura pear-to-pear indica redes sem um servidor central em que cada computador
atua como um cliente e como um servidor ao mesmo tempo. Um grupo de trabalho
Windows sem um servidor de domínio é uma rede pear-to-pear, por exemplo.
Geralmente, esse tipo de arquitetura só funciona em redes locais e depende de fre-
qüentes transmissões UDP para tornar cada nó ciente do status de outros nós. Essas
transmissões não são normalmente possíveis pela Internet. A Figura 29.3 retrata uma
rede pear-to-pear.
Arquiteturas distribuídas 715

Cliente
rede local

Cliente Cliente
rede local rede local

Cliente
rede local

FIGURA 29.3 Uma rede pear-to-pear.

Gnutella ou Napster são formas híbridas de sistemas pear-to-pear em que cada nó se


comunica com um servidor para descobrir a existência de outros nós e então iniciar a co-
municação direta com cada um.
Embora conveniente para compartilhamento de arquivos e programas de bate-
papo, essa arquitetura não consegue fornecer benefícios para aplicações comuns orienta-
das a dados.

Multicamada
A arquitetura multicamada é o tipo preferido de arquitetura distribuída atualmente. As
aplicações Delphi criadas com tecnologias como DataSnap ou Windows DNA são aplica-
ções multicamada. Você utilizará .NET Remoting para desenvolver sistemas multicama-
da no Delphi for .NET.
Esse tipo de arquitetura é comumente referido como arquitetura de três camadas,
em que um cliente se comunica com um processo em um servidor remoto e este, por sua
vez, comunica-se com um RDBMS localizado em um servidor diferente. Isso é mostrado
na Figura 29.4.

Cliente
rede local
Camada
interme- Banco
diária de dados

Cliente
Internet

FIGURA 29.4 Modelo distribuído multicamada.


716 Capítulo 29 .NET Remoting e Delphi

O aspecto-chave desse tipo de arquitetura é a presença da camada intermediária. As


camadas intermediárias (middle tiers) normalmente são compostas de duas layers (cama-
das lógicas):
— Acesso a dados – Responsável por consultar e atualizar a origem de dados associa-
da. Você pode fazer cache de dados para melhorar o desempenho e minimizar o
acesso ao banco de dados. Você pode abstrair o dialeto do mecanismo RDBMS e
fornecer uma API orientada a objetos simples para a camada de negócio.
— Negócio – Normalmente composto por objetos acessíveis a partir dos clientes por
meio do Remoting. Esses objetos realizam a validação de dados, transformação,
cálculos na memória e assim por diante.
As camadas de acesso a dados e de negócio poderiam residir em diferentes domínios
de aplicação ou simplesmente ser posicionadas em um mesmo domínio. Em geral, utili-
zar um domínio de aplicação torna as coisas mais fáceis, embora você possa optar por
uma divisão mais marcada em algum caso.
Um exemplo poderia ser o fornecimento de uma camada de acesso a dados hot-
swappable que funciona como um plug-in e pode ser substituída em tempo de execução
ou durante a instalação sem a necessidade de recompilar a camada de negócio.
A próxima seção lista algumas das vantagens mais evidentes dos sistemas multicamada.

Os benefícios do desenvolvimento
de aplicações multicamada
O projeto multicamada oferece várias vantagens em relação aos projetos cliente/servidor
duas camadas, embora provavelmente seu entendimento e implementação sejam mais
complexos.

Escalabilidade e tolerância a falhas


A primeira e mais proeminente vantagem das arquiteturas multicamada é a escalabilidade.
Os sistemas cliente/servidor dependem de um servidor de banco de dados central e
exigem que conexões permanentes estejam presentes. Quanto maior o número de clien-
tes, pior é o desempenho do servidor por causa do aumento de consumo de recursos (isto
é, conexões de rede constantemente abertas, acesso freqüente a tabelas, cursores e assim
por diante). Os sistemas cliente/servidor normalmente não são capazes de tratar mais de
duzentos usuários concorrentes de maneira eficiente.
Tendo uma camada intermediária composta de múltiplos servidores (cluster), você
será capaz de dividir a carga de trabalho entre eles e apenas carregar o servidor do bancos
de dados quando realmente necessário. Implementando técnicas de cache, você tam-
bém poderia minimizar acesso ao que é estritamente necessário, aumentando o desem-
penho total.
A Figura 29.5 mostra dois (ou mais) clientes que acessam um cluster de dois (ou
mais) servidores, que, por sua vez, são conectados a um único servidor de bancos de
dados.
Os benefícios do desenvolvimento de aplicações multicamada 717

Camada
intermediária

Cliente Servidor 1

Cliente Servidor 2 Banco


de dados

Cliente Servidor 3

FIGURA 29.5 Modelo de cluster multicamada.

A fim de se beneficiar da tecnologia de cluster, seus clientes precisarão operar em um


modo desconectado e só conectar-se à camada intermediária quando precisarem ler da-
dos ou enviar alguma atualização. Independentemente do status da máquina remota –
que poderia ter se tornado inoperante ou ter sido desligada para manutenção – isso asse-
gura que o cliente continuará a funcionar utilizando outro servidor.
De uma perspectiva de servidor, essa abordagem é chamada stateless. O servidor só
tem um meio de comunicar-se com os clientes quando estes iniciam uma chamada. Uma
vez que isso é concluído, o cliente deixa de existir para o servidor.
Se estiver familiarizado com o desenvolvimento Web, você descobrirá muitas analo-
gias: o projeto stateless é quase obrigatório quando você precisa de alta escalabilidade e
tem um grande número de clientes concorrentes.
Explicaremos como os objetos remotos são instanciados mais adiante nas seções “Ser-
ver Activation” e “Client Activation.” A fim de assegurar que seu sistema seja altamente es-
calonável, você terá de utilizar um dos dois modelos Server Activation (SingleCall ou Sin-
gleton). Se precisar de uma abordagem mais acoplada, mas menos escalonável e certamen-
te não-clusterizável, em vez disso você poderia decidir associar um cliente a uma instância
específica de um objeto que reside em um servidor, utilizando client activation.

NOTA
Você pode localizar artigos específicos de .NET em arquiteturas distribuídas em:
http://msdn.microsoft.com/architecture/

Desenvolvimento e distribuição
Em sistemas cliente/servidor o processamento de negócio é feito principalmente nas
aplicações clientes – às vezes com a ajuda de stored procedures que executam no servidor
do banco de dados.
718 Capítulo 29 .NET Remoting e Delphi

Infelizmente, como as stored procedures são mais complexas de escrever e depurar


que o código Delphi, elas não são extensamente utilizadas – especialmente em empresas
que não têm uma equipe de administração de banco de dados dedicada.
Se o número de clientes for grande, atualizar cada área de trabalho com uma nova
cópia do executável do cliente pode ser um procedimento trabalhoso e propenso a erros.
Em sistemas multicamada, uma grande parte (se não todo) do processamento de ne-
gócio acontece na camada intermediária. As aplicações que executam na camada inter-
mediária são simples de escrever (código Delphi), e, uma vez atualizadas, todos os clien-
tes automaticamente se beneficiam de correções ou aprimoramentos sem a necessidade
de redistribuição.
Talvez alguém argumente que colocar um executável em uma unidade de rede com-
partilhada poderia ser tão fácil quanto atualizar, mas considere o que aconteceria se uma
estação de trabalho estivesse mantendo o arquivo bloqueado ou se você precisasse aces-
sá-la a partir do escritório de uma outra cidade.

Segurança
Como os clientes não se comunicam diretamente com o banco de dados, torna-se muito
mais fácil para o administrador impor segurança programática na aplicação e proteger o
banco de dados contra acesso não-autorizado.
A solicitação feita por clientes pode viajar, encriptada, em conexões de SSL. Os mé-
todos de objetos de negócio podem retornar menos dados para clientes baseados em in-
formações de sessão: isso seria transparente para o chamador e alterável ao longo do tem-
po sem a necessidade de redistribuir os clientes.

Os princípios básicos do .NET Remoting


As seções a seguir fornecem uma visão geral da tecnologia .NET Remoting.

Visão geral da arquitetura


A arquitetura .NET Remoting é extremamente flexível e fornece aos desenvolvedores um
framework fácil de personalizar e estender.
Esta seção fornece uma breve descrição dos elementos mais importantes do frame-
work e introduz alguns conceitos presentes apenas no .NET Remoting.

Application Domains
Application Domains estão na base da infra-estrutura de remoting e representam as fron-
teiras para a comunicação de interprocessos (Interprocess Communication – IPC).
No Win32 clássico, o Windows cria um novo processo quando um executável é car-
regado. Os processos estão no nível de isolamento mais baixo e não podem compartilhar
a memória diretamente. Os endereços de memória são relativos ao processo: os pontei-
ros para memória em um processo não têm significado um para o outro.
Os princípios básicos do .NET Remoting 719

Application Domains, ou AppDomains, são para o .NET o que os processos são para
o Win32. Os AppDomains fornecem um nível mais granular de separação e melhor segu-
rança do que os processos do Win32.
Você pode executar vários application domains em um único processo com o mesmo
nível de isolamento que existiria em processos separados, mas sem incorrer ao overhead
adicional de fazer chamadas entre processos ou alternar entre processos. A Figura 29.6
ilustra esse conceito.
.NET Remoting é necessário para fazer os objetos em um domínio se comunicarem
com aqueles hospedados em outro domínio, independentemente de o domínio estar no
mesmo processo ou em processos que executam em máquinas diferentes.

O namespace System.Runtime.Remoting
Para desenvolver aplicações que utilizam .NET Remoting, você precisará referenciar o
namespace System.Runtime.Remoting tanto nos clientes como nos servidores.
O namespace System.Runtime.Remoting e aqueles que dependem dele fornecem classes
e interfaces que permitem aos desenvolvedores criar e configurar aplicações distribuídas.

A classe RemotingConfiguration
A classe RemotingConfiguration contém métodos de classe estáticos para interfacear com
definições de configuração e registrar objetos de modo que eles possam ser remotamente
invocados.
O seguinte trecho de código registra a classe TBankManager:

RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);

Mais sobre esse método é explicado na seção “Server Activation”.


As configurações Remoting podem ser programaticamente configuradas ou lidas a
partir de arquivos de configuração externos.

Application Domain 1 Application Domain 2

Objeto A Cópia do objeto A


(empacotamento (empacotamento
por valor) por valor)

Serializado para fluxo Infra-estrutura Desserializado


Infra-estrutura Fluxo que contém objeto A serializado
.NET Remoting .NET Remoting

FIGURA 29.6 Comunicação entre AppDomains utilizando .NET Remoting.


720 Capítulo 29 .NET Remoting e Delphi

O método RemotingConfiguration.Configure( ) permite aos desenvolvedores configurar


a infra-estrutura de Remoting pelo uso de arquivos de configuração com formato XML.
Essa é a maneira como você o utilizaria:

RemotingConfiguration.Configure('MyConfiguration.config');

Os arquivos de configuração podem conter informações como o protocolo de rede


para utilização de comunicação remota, a porta TCP ou HTTP utilizada, o tipo de forma-
tação da mensagem (SOAP ou binária) e assim por diante.

NOTA
Consulte a seção chamada “Remoting Settings Schema” no MSDN Online Library para obter in-
formações adicionais sobre:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gnconR
emotingSettingsSchema.asp

A classe ChannelServices
A classe ChannelServices fornece métodos de classe estáticos para registrar canais remoting
e é utilizada tanto em clientes como em servidores.
O seguinte trecho de código mostra como criar e registrar um canal de comunicação
HTTP que ouve a porta 9088 para utilização no lado do servidor:

var Channel : HttpChannel;


begin
Channel := HttpChannel.Create(9088);
ChannelServices.RegisterChannel(Channel);

Os clientes não especificam um número de porta ao registrar canais. As informações


de URI necessárias para acessar objetos remotos são especificadas posteriormente na cha-
mada do objeto Activator e essas também incluirão um número da porta.
O seguinte trecho de código mostra como criar um canal cliente e adquirir uma refe-
rência para o serviço remoto da aplicação cliente:

fChannel := HttpChannel.Create( );
ChannelServices.RegisterChannel(fChannel);

fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');

Objetos Remotable
Os objetos projetados para serem acessados por diferentes domínios são chamados obje-
tos remotable. Dois tipos de objetos remotable existem em .NET: Marshal-By-Reference e
Marshal-By-Value.
Os princípios básicos do .NET Remoting 721

NOTA
Um terceiro tipo, Context-bound, também está disponível, mas não será discutido neste livro por
causa da extensão e complexidade do tópico. Você pode pensar em um contexto .NET como uma
subdivisão de um application domain em que os objetos context-bound residem. Para obter infor-
mações adicionais sobre contextos, consulte a .NET Framework SDK Documentation em
"ms-help://MS.NETFrameworkSDKv1.1/ cpguidenf/html/cpconremotableobjects.htm".

Objetos Marshal-By-Reference
Em termos simples, as instâncias desse tipo podem ser vistas como uma coleção de méto-
dos que pode ser invocada a partir de clientes remotos.
Os objetos Marshal-By-Reference (empacotados por referência) são criados no servi-
dor em que eles residem até o fim de uma chamada de método (ver “Instanciação sin-
gle-call” mais adiante) ou são compartilhados entre diferentes clientes (ver “Ativação sin-
gleton” mais adiante) até o fim de um lease (ver “Leases e Sponsors” mais adiante).
A fim de criar um objeto Marshal-By-Reference, você precisa fazer sua classe descen-
der de MarshalByRefObject, o que é definido no namespace System.
O exemplo a seguir é um exemplo de uma classe Marshal-By-Reference:

type
TCalculator = class(MarshalByRefObject, ICalculator)
private
protected
// Icalculator
function Sum(A,B : integer): integer;

public
end;

Os métodos deste tipo de objetos podem ter qualquer tipo de dados simples (isto é,
inteiros, strings e assim por diante) ou parâmetros de objeto (contanto que eles sejam ob-
jetos Marshal-By-Value).

Objetos Marshal-By-Value
As instâncias deste tipo atravessam limites de application domain depois de serem seria-
lizadas em um formato transportável.
Quando elas alcançam o application domain alvo, elas são desserializadas e uma
nova instância da classe original é criada no application domain alvo.
Para criar um objeto serializável você precisa marcar a classe com o atributo [Seriali-
zable], assim:

{ TAccount }
[Serializable]
TAccount = class
private
fNumber : integer;
722 Capítulo 29 .NET Remoting e Delphi

fBalance: double;
fName: string;

public
constructor Create(const aNumber : integer;
const aName : string;
const aBalance : double);

property Number : integer read fNumber;


property Name : string read fName;
property Balance : double read fBalance write fBalance;
end;

O propósito desses tipos de objetos é, geralmente, compartilhar dados entre aplica-


ções de maneira estruturada. Normalmente, eles são utilizados como parâmetros de en-
trada ou saída de métodos remotos.

Object activation
Antes que você possa adquirir uma referência para um objeto remotable que executa em
um application domain diferente, o objeto precisa ser criado na máquina remota.
Há duas maneiras de criar uma instância de um objeto remotable: utilizando server
activation e utilizando client activation.

Server activation
Os objetos Server-activateds são referidos como objetos bem conhecidos porque são re-
gistrados no sistema .NET Remoting e publicados em um ponto final específico e bem
conhecido ou URI.
Objetos bem conhecidos podem ser ativados de duas maneiras: singleton e sin-
gle-call.

Ativação singleton A ativação singleton significa que apenas uma instância de um


objeto será criada e acessível a qualquer dada hora. Se dois clientes solicitarem uma refe-
rência a um objeto configurado no modo singleton, ambos receberão a mesma referên-
cia e suas chamadas serão serializadas.
O código a seguir mostra como registrar um objeto configurado no modo singleton:

RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);

Ativação single-call A instanciação single-call é o modo de ativação mais escaloná-


vel porque se adapta melhor aos sistemas stateless. Quando você registrar um objeto
como single-call, uma instância será criada na solicitação de cada cliente; uma vez com-
pletada a chamada, ela será liberada para o garbage collection.
Os princípios básicos do .NET Remoting 723

O seguinte código mostra como permitir instanciação single-call:

RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.SingleCall);

Client activation
Os objetos client-activated são ativados individualmente por cliente. Cada cliente terá
sua própria referência, que também pode permanecer ativa entre as chamadas de méto-
do (statefull).
Embora client-activated ofereça algumas vantagens e seja mais simples de utilizar
que a instanciação do lado do servidor, ela é menos escalonável. Ela utiliza mais recursos
no servidor, amarra clientes a um servidor, e, por causa disso, não funciona em “fazendas
Web”.
Para permitir esse tipo de ativação, seu servidor terá de incluir uma chamada seme-
lhante à seguinte:

RemotingConfiguration.RegisterActivatedServiceType(typeof(TBankManager));

E o código correspondente no cliente seria semelhante a:


RemotingConfiguration.RegisterActivatedClientType(typeof(TBankManager),
'http://someurl');
Você verá em mais detalhes como criar objetos ativados por cliente na seção “Client
Activation” no Capítulo 30.

Leases e sponsors
O tempo de vida de um objeto remotable é gerenciado por garbage collection baseado
em lease. Cada application domain contém seu próprio gerenciador de lease (lease ma-
nager) e monitora o acesso a objetos. Se um objeto não for acessado por certo período de
tempo, é então passado para o garbage collector que o destrói.
O gerenciamento de tempo de vida de objeto no .NET é radicalmente diferente da
maneira como o DCOM trata tempos de vidas de objeto. Em DCOM uma combinação de
contagem de referência e ping de rede era utilizada para determinar quando os objetos
poderiam ser destruídos. Isso resultava em congestionamento de rede e outros efeitos co-
laterais indesejáveis em grandes instalações. O .NET Remoting foi projetado para resol-
ver esses problemas.
Leases são utilizadas para objetos singleton server-activated e objetos client-activated.
Depois de o .NET Framework criar uma instância desses objetos, ele chama o método vir-
tual InitializeLifeTimeServices (herdado de MarshalByRefObject e possivelmente sobrescri-
to), que retorna um objeto que implementa a interface ILease. Esse objeto então será con-
sultado para determinar se o lease no objeto expirou e ele pode ser destruído.
Leases podem ser renovadas por sponsors. Você pode definir uma classe que atua
como um sponsor implementando a interface ISponsor. Você então precisa associar o
sponsor a uma locação chamando o método Ilease.Register.
724 Capítulo 29 .NET Remoting e Delphi

O .NET Framework define a classe ClientSponsor, que fornece uma implementação


padrão para classes sponsor, que podem extender o de tempo de vida de um objeto.

Proxies
Os clientes comunicam-se com objetos remotables utilizando proxies. Um proxy é um
objeto que reside no espaço de endereçamento do cliente e atua como um substituto do
objeto remoto.
Em .NET, temos dois tipos de proxies: Transparente e real.
O proxy transparente é o objeto que adquirimos diretamente ao escrever código
como

fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');

Depois de uma chamada de método ser emitida, o proxy transparente empacota o


nome e os parâmetros em um objeto mensagem e a passa para o proxy real.
O proxy real então despacha o objeto de mensagem para o .NET Framework, que en-
tão utilizará um canal remoting para transportá-lo.

Canais
Os canais remoting transportam mensagens por meio de application domains. O .NET
SDK fornece dois tipos de canais que você pode utilizar: TCP/IP e HTTP.
Você utilizaria um canal TCP/IP principalmente em redes locais ou onde a comuni-
cação tem de ser a mais rápida possível.
Em vez disso, você utilizaria o canal HTTP para Internet ou, em geral, o tipo de co-
municação amigável ao firewall.
Ambos os canais podem ser utilizados independentemente do formato de troca de
mensagens. Isso significa que você pode decidir utilizar o formato SOAP ou binário sobre
TCP/IP ou HTTP.

Sua primeira aplicação .NET Remoting


Desenvolver aplicações Delphi que utilizam .NET Remoting é um processo relativamen-
te fácil, e, se utilizava COM, você achará que é conceitualmente muito semelhante a de-
senvolver servidores OLE automation. Felizmente, o registro de objetos remotable é mui-
to mais simples. Ele pode ser feito programaticamente (chamadas de método Remoting-
Configuration) ou declarativamente (utilizando um arquivo de configuração XML). Você
não precisa mais negociar com type libraries.
O seguinte exemplo, embora simples, mostra como estruturar melhor os projetos
.NET Remoting e contém certa quantidade de lógica do negócio do mundo real.
O servidor desse exemplo representa o serviço de um banco. Ele contém uma lista de
contas bancárias (duas, para ser preciso) e permite consultar os detalhes dessas contas (nú-
mero de conta, nome da pessoa associada e seu saldo) e fazer uma transferência bancária.
Sua primeira aplicação .NET Remoting 725

Configurando o projeto
Vamos iniciar o desenvolvimento de sistema de banco criando um grupo de projeto e
três projetos vazios (um Package, uma Console Application e uma Windows Forms
Application) que concluiremos nas seções a seguir.
Selecione File, New, Other. Clique em Other Files e selecione Project Group, como
mostrado na Figura 29.7.
Salve o grupo de projeto como BankExample.
É muito conveniente utilizar grupos de projeto ao desenvolver aplicações distribuí-
das, especialmente nas primeiras etapas. Os grupos de projeto fornecem uma visão ime-
diata de todos os elementos dos seus sistemas e permitem alternar entre eles mais eficien-
temente do que com projetos individuais.
Adicione o Package ao grupo clicando no botão New dentro do painel Project Mana-
ger e selecionando em seguida Package, como mostrado na Figura 29.8.

FIGURA 29.7 Configurando um grupo de projeto.

FIGURA 29.8 Selecionando um Package.


726 Capítulo 29 .NET Remoting e Delphi

Repita a operação e adicione uma Console Application e uma Windows Forms


Application ao grupo de projeto.
Uma vez concluída essa etapa, salve e nomeie os projetos, como mostrado na Tabela 29.1.

TABELA 29.1 Nomes de projeto no grupo de projeto


Tipo de projeto Nome do projeto
Package BankPackage.dll
Console Application BankServer.exe
WinForms Client BankClient.exe

Adicionando referências
Para que as aplicações utilizem packages Delphi, a própria package e as aplicações que a
utilizam devem incluir uma referência ao assembly Borland.Delphi.
Isso é automaticamente adicionado para você pelo Delphi, mas em caso de você ter
removido a referência, simplesmente entre no Project Manager, clique com o botão direi-
to do mouse no nó Requires sob o projeto BankPackage.dll e selecione Add Reference.
Quando a caixa de diálogo Add Reference aparecer, selecione o assembly Bor-
land.Delphi, como mostrado na Figura 29.9.
Clique em OK, compile a package e selecione o projeto Server.
A aplicação servidor precisará de referências para o assembly Borland.Delphi, o pró-
prio BankPackage.dll e System.Runtime.Remoting.
Reabra a caixa de diálogo Add Reference e selecione os assemblies mostrados na Fi-
gura 29.10. Você terá de navegar para o diretório que contém BankPackage.dll para selecio-
ná-la. Uma vez que esses passos foram concluídos, você o verá na página Project Referen-
ces dessa caixa de diálogo. A página .NET Assemblies só contém assemblies colocados no
GAC.

FIGURA 29.9 Adicionando a referência Borland.Delphi.


Sua primeira aplicação .NET Remoting 727

FIGURA 29.10 Adicionando a referência ao Assembly do projeto BankServer.

Uma vez que você concluiu a adição dessas referências, o grupo de projeto deve ser
semelhante ao mostrado na Figura 29.11.

FIGURA 29.11 O grupo de projeto BankExample.

BankPackage.dll: comparação entre clientes e servidores


Se já conhece o COM, então está acostumado a criar type libraries. A type library é um re-
curso binário padronizado vinculado no executável do seu aplicativo servidor ou DLL
que o Windows requer para permitir a comunicação entre processos. A type library re-
728 Capítulo 29 .NET Remoting e Delphi

presenta o contrato entre seu servidor e clientes: ela instrui os clientes sobre o que eles
podem fazer e que tipos de dados complexos eles utilizarão; mas ela não contém nenhu-
ma implementação.
Quando você desenvolve servidores .NET Remoting, as type libraries não são mais
necessárias: os metadados do .NET incluídos dentro do package compilado atenderão
esse propósito. O BankPackage.dll que acabamos de criar é um assembly .NET que conterá
as interfaces compartilhadas e classes utilizadas pelo servidor e a aplicação cliente.
Adicionamos uma nova unit ao BankPackage.dll e o salvamos como BankShared.pas.
Essa unit é mostrada na Listagem 29.1.

LISTAGEM 29.1 A unit BankShared.pas


1: unit BankShared;
2:
3: interface
4:
5: type
6: { TAccount }
7: [Serializable]
8: TAccount = class
9: private
10: fNumber : integer;
11: fBalance: double;
12: fName: string;
13:
14: public
15: constructor Create(const aNumber : integer;
16: const aName : string;
17: const aBalance : double);
18:
19: property Number : integer read fNumber;
20: property Name : string read fName;
21: property Balance : double read fBalance write fBalance;
22: end;
23:
24: { IBankManager }
25: TAccountNumberArray = array of integer;
26:
27: IBankManager = interface
28: function GetAccountNumbers : TAccountNumberArray;
29: function GetAccount(const AccountNumber : integer) : TAccount;
30: procedure TransferMoney(const Origin, Destination : integer;
31: const Amount : double);
32: end;
33:
34: implementation
35:
36: { TAccount }
Sua primeira aplicação .NET Remoting 729

LISTAGEM 29.1 Continuação


37:
38: constructor TAccount.Create(const aNumber : integer;
39: const aName : string;
40: const aBalance : double);
41: begin
42: inherited Create;
43:
44: fNumber := aNumber;
45: fName := aName;
46: fBalance := aBalance;
47: end;
48:
49: end.

u Localize o código no CD: \Code\Chapter 29\Ex01\.

Na Listagem 29.1 a interface IBankManager é um aspecto crítico dessa unit (linhas


24–32). Como você pode ver, não há nenhuma classe que a implementa. Esse pacote vai
ser compartilhado entre servidor e cliente, mas o cliente não precisa conhecer (ou con-
ter) nenhum detalhe de implementação. Tudo que ele precisa saber é do que e como cha-
má-lo.
A única classe definida e implementada nessa unit é TAccount (linhas 6–22 e 38–47).
Além disso, o array TAccountNumberArray também é declarado (linha 25).
Como esses dois tipos são referenciados pelos métodos de IBankManager, eles precisam
ser incluídos no assembly compartilhado.
A interface IBankManager é implementada no projeto de servidor.
A outra coisa importante a notar é o uso do atributo [Serializable] acima da classe
TAccount (linha 7). Se a classe TAccount não tiver sido marcada como serializável, IBankMana-
ger.GetAccount falharia em tempo de execução quando chamado remotamente.
TAccount é utilizada como um objeto Marshal-By-Value (empacotado por valor).
Quando o cliente remoto chama GetAccount, uma cópia do objeto é passada por meio dos
limites do application domain.

Implementando o servidor
O servidor é composto de duas partes:
— O arquivo-fonte do projeto em que abriremos um canal remoting HTTP e o regis-
tro da classe que implementa a interface IbankManager.
— Uma unit que contém a classe TBankManager. Essa classe implementa a interface
IBankManager.

Adicionamos uma nova unit para o projeto de servidor e a salvamos como BankSer-
ver_Impl.pas. Essa unit é mostrada na Listagem 29.2.
730 Capítulo 29 .NET Remoting e Delphi

LISTAGEM 29.2 A unit BankServer_Impl.pas


1: unit BankServer_Impl;
2:
3: interface
4:
5: uses
6: BankShared;
7:
8: type
9: TBankManager = class(MarshalByRefObject, IBankManager)
10: private
11: fAccount1,
12: fAccount2 : TAccount;
13: protected
14: // IBankManager
15: function GetAccountNumbers : TAccountNumberArray;
16: function GetAccount(const AccountNumber : integer) : TAccount;
17: procedure TransferMoney(const Origin, Destination : integer;
18: const Amount : double);
19: public
20: constructor Create;
21: end;
22:
23: implementation
24:
25: { TBankManager }
26:
27: constructor TBankManager.Create;
28: begin
29: inherited Create;
30: fAccount1 := TAccount.Create(1, 'John Smith', 1999);
31: fAccount2 := TAccount.Create(2, 'Jack Rockwell', 249);
32: end;
33:
34: function TBankManager.GetAccount(const AccountNumber: integer): TAccount;
35: begin
36: case AccountNumber of
37: 1 : result := fAccount1;
38: 2 : result := fAccount2;
39: else raise Exception.Create('Invalid account number!');
40: end;
41:
42: Console.WriteLine('A client requested account
➥'+result.Number.ToString+
43: ' ('+result.Name+')');
44: end;
45:
46: function TBankManager.GetAccountNumbers: TAccountNumberArray;
47: begin
Sua primeira aplicação .NET Remoting 731

LISTAGEM 29.2 Continuação


48: SetLength(result, 2);
49: result[0] := fAccount1.Number;
50: result[1] := fAccount2.Number;
51:
52: Console.WriteLine('A client requested the list of accounts');
53: end;
54:
55: procedure TBankManager.TransferMoney(const Origin, Destination: integer;
56: const Amount: double);
57: var origin_acct, destination_acct : TAccount;
58: begin
59: origin_acct := GetAccount(Origin);
60: destination_acct := GetAccount(Destination);
61:
62: if (origin_acct.Balance<Amount) or (Amount<0)
63: then raise Exception.Create('Insufficient funds or
➥invalid amount specified');
64:
65: destination_acct.Balance := destination_acct.Balance+Amount;
66: origin_acct.Balance := origin_acct.Balance-Amount;
67:
68: Console.WriteLine('Transferred ${0} from account {1} to account {2}',
69: Amount.ToString, origin_acct.Number.ToString,
70: destination_acct.Number.ToString);
71: end;
72:
73: end.

u Localize o código no CD: \Code\Chapter 29\Ex01\.

Preste atenção à declaração de TBankManager na linha 9. MarshalByRefObject é a classe bá-


sica para objetos que se comunicam por meio de limites de um application domain tro-
cando mensagens com um proxy.
O proxy é criado na primeira vez que o objeto é acessado. As chamadas subseqüentes
no proxy são empacotadas de volta para o objeto que reside no application domain do
servidor.
As classes devem ser herdadas de MarshalByRefObject quando suas instâncias precisa-
rem ser utilizadas por intermédio de application domains e seu estado não precisa ou
não pode ser copiado.
No arquivo-fonte do projeto (ver Listagem 29.3 mais adiante) adicionamos os na-
mespaces System.Runtime.Remoting, System.Runtime.Remoting.Channels e System.Runtime.Remo-
ting.Channels.HTTP. O arquivo-fonte do projeto agora contém

uses
BankServer_Impl in 'BankServer_Impl.pas',
System.Runtime.Remoting,
732 Capítulo 29 .NET Remoting e Delphi

System.Runtime.Remoting.Channels,
System.Runtime.Remoting.Channels.HTTP;

Nós também definimos uma variável do tipo HTTPChannel chamada Channel e a iniciali-
zamos da seguinte maneira:

var def_HTTPPort : integer = 8099;


Channel : HttpChannel;
Begin
[..]
Channel := HttpChannel.Create(def_HTTPPort);
ChannelServices.RegisterChannel(Channel);

Por fim, registramos o tipo TBankManager utilizando a classe RemotingConfiguration:

RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TBankManager),
'BankManager.soap',
WellKnownObjectMode.Singleton);
// Começa a aceitar solicitações
Writeln('Waiting for requests...');
Readln;

A fonte completa do arquivo BankServer.dpr é mostrada na Listagem 29.3.

LISTAGEM 29.3 Arquivo BankServer.dpr


1: program BankServer;
2:
3: {$APPTYPE CONSOLE}
4:
5: {%DelphiDotNetAssemblyCompiler 'BankPackage.dll'}
6: {%DelphiDotNetAssemblyCompiler [..]}
7: {%DelphiDotNetAssemblyCompiler [..]}
8:
9: uses
10: BankServer_Impl in 'BankServer_Impl.pas',
11: System.Runtime.Remoting,
12: System.Runtime.Remoting.Channels,
13: System.Runtime.Remoting.Channels.HTTP;
14:
15: var def_HTTPPort : integer = 8099;
16: Channel : HttpChannel;
17: begin
18: Console.WriteLine('Initializing server...');
19:
20: // Inicializa o servidor para ouvir solicitações HTTP em uma porta específica
21: Channel := HttpChannel.Create(def_HTTPPort);
22: ChannelServices.RegisterChannel(Channel);
Sua primeira aplicação .NET Remoting 733

LISTAGEM 29.3 Continuação


23: Console.WriteLine('HTTP channel created. Listening on port '+
24: System.Convert.ToString(def_HTTPPort));
25:
26: // Registra o serviço de TBankManager
27: RemotingConfiguration.RegisterWellKnownServiceType(
28: typeOf(TBankManager),
29: 'BankManager.soap',
30: WellKnownObjectMode.Singleton);
31:
32: // Começa a aceitar solicitações
33: Console.WriteLine('Waiting for requests...');
34: Console.ReadLine;
35: end.

u Localize o código no CD: \Code\Chapter 29\Ex01\.

As linhas 21–22 mostram como fazer a aplicação servidor criar um canal remoting
que ouve mensagens HTTP na porta 8099. As linhas 27–30 registram o objeto TBankMana-
ger como um singleton pronto para ser acessado remotamente.
Note como você não precisa associar uma classe a um canal. Se você tiver outras clas-
ses remotas, você só precisar registrá-las da maneira como registramos o TBankManager (li-
nhas 27–30). O canal HTTP que foi criado previamente ouve qualquer tipo de mensagem
remota vinda de clientes e o encaminha para o objeto alvo correto.
Nesse ponto você pode compilar a aplicação servidor e carregar fora do IDE. Exibirá
o conteúdo como mostrado na Figura 29.12.

FIGURA 29.12 Saída da aplicação servidor.

Implementando o cliente
No projeto cliente, a cláusula uses do formulário principal contém os seguintes namespaces:

uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data,
734 Capítulo 29 .NET Remoting e Delphi

// relativo a Remoting e Bank


BankShared,
System.Runtime.Remoting,
System.Runtime.Remoting.Channels,
System.Runtime.Remoting.Channels.HTTP;

O handler de evento OnLoad do formulário contém o seguinte código:

fChannel := HttpChannel.Create( );
ChannelServices.RegisterChannel(fChannel);

fBankManager := Activator.GetObject(
typeof(IBankManager),
'http://localhost:8099/BankManager.soap');

Como você provavelmente adivinhou, o código anterior cria uma instância do obje-
to BankManager remoto, que podemos utilizar agora de dentro da aplicação cliente.
Agora é possível testar os métodos remotos. A Figura 29.13 mostra o formulário da
aplicação de exemplo que faz isso em tempo de projeto.

FIGURA 29.13 Formulário principal da aplicação cliente.

O handler de evento Click do botão de Refresh contém o código mostrado na Lista-


gem 29.4.

LISTAGEM 29.4 Handler de evento Click do botão Refresh


1. procedure TWinForm2.bRefresh_Click(sender: System.Object;
2. e: System.EventArgs);
3. var accountarray : TAccountNumberArray;
Sua primeira aplicação .NET Remoting 735

LISTAGEM 29.4 Continuação


4. i : integer;
5. begin
6. accountarray := fBankManager.GetAccountNumbers;
7. cbBankAccounts.Items.Clear;
8. cbOrigin.Items.Clear;
9. cbDestination.Items.Clear;
10.
11. for i := 0 to High(accountarray) do begin
12. cbBankAccounts.Items.Add(accountarray[i].ToString);
13. cbOrigin.Items.Add(accountarray[i].ToString);
14. cbDestination.Items.Add(accountarray[i].ToString);
15. end;
16. cbBankAccounts.SelectedIndex := 0;
17. end;

u Localize o código no CD: \Code\Chapter29\Ex01\.

Como você pode ver a partir da primeira linha desse método, o uso do objeto remo-
to apontado por fBankManager não é nada diferente de utilizar uma cópia local. Declara-
mos uma variável de tipo TAccountNumberArray na linha 3 e a utilizamos na linha 6.
Tenha em mente que as semelhanças terminam no nível de código, especialmente
quando se trata de desempenho.
Quando o código
accountarray := fBankManager.GetAccountNumbers;

é executado, a aplicação cliente está criando uma mensagem SOAP, enviando-a para o
servidor via HTTP e desempacotando a resposta. Esse overhead não é óbvio ao fazer uma
única chamada ao método, mas você verá imediatamente uma grande diferença no de-
sempenho, logo que colocar esse código dentro de um loop.
O handler de evento Click do botão Retrieve contém o código mostrado na Lista-
gem 29.5.

LISTAGEM 29.5 Handler de evento Click do botão Retrieve


1. procedure TWinForm2.bRetrieve_Click(sender: System.Object;
2. e: System.EventArgs);
3. var acctnumber : integer;
4. account : TAccount;
5. begin
6. acctnumber := System.Convert.ToInt32(cbBankAccounts.Text);
7. account := fBankManager.GetAccount(acctnumber);
8. tbAcctNumber.Text := account.Number.ToString;
9. tbAcctName.Text := account.Name;
10. tbAcctBalance.Text := account.Balance.ToString;
11. end;

u Localize o código no CD: \Code\Chapter29\Ex01\.


736 Capítulo 29 .NET Remoting e Delphi

Na linha 6 obtemos o número da conta selecionado a partir da caixa de combinação


cbBankAccounts e o atribuímos à variável de inteiro acctnumber. Então passamos acctnumber
para o método BankManager.GetAccount (linha 7), que gerará uma chamada remota para o
objeto BankManager que executa no servidor. Por fim (linha 8–10), exibimos as informa-
ções retornadas contidas na conta pelo servidor.
Por fim, o handler de evento Click do botão Transfer contém

procedure TWinForm2.bTransfer_Click(sender: System.Object;


e: System.EventArgs);
var originacct_acct, destination_acct : integer;
amount : double;
begin
originacct_acct := System.Convert.ToInt32(cbOrigin.Text);
destination_acct := System.Convert.ToInt32(cbDestination.Text);
amount := System.Convert.ToDouble(tbAmount.Text);
fBankManager.TransferMoney(originacct_acct,
destination_acct,
amount);
MessageBox.Show('Transfer completed');
end;

O cliente, quando carregado, exibe o formulário mostrado na Figura 29.14.

FIGURA 29.14 Aplicação cliente – remoting em ação.


NESTE CAPÍTULO
CAPÍTULO 30 — Projeto de template

— Rastreando mensagens
.NET Remoting — Analisando os pacotes SOAP

em ação — Client Activation

— Gerenciamento de Lifetime

por Alessandro Federici — Falha ao renovar o Lease

— Arquivos de configuração

O s exemplos apresentados neste capítulo ilustram — Alternando entre a


vários recursos do framework do .NET Remoting. Cada comunicação HTTP e TCP
exemplo ajudará a alcançar um bom entendimento do — Alternando entre codificação
conceito em discussão. SOAP e binária
— Diferenças entre a codificação
Projeto de template SOAP e binária.
O grupo de projeto contém um assembly
compartilhado, um servidor e uma aplicação console
cliente prontos para serem compilados e carregados.
A Figura 30.1 mostra a aparência do projeto
template.
O serviço incluído no template é muito básico e ele
implementa a seguinte interface:

ISimpleServer = interface
function GetValue : integer;
procedure SetValue(Value : integer);
property Value : integer read GetValue write SetValue;
end;

u Localize o código no CD: \Code\Chapter 30\Ex01\.

Esse template poupará um tempo precioso ao fazer


os primeiros testes com .NET Remoting para você.
Para iniciar uma nova aplicação remoting,
simplesmente copie a pasta do template, renomeie seus
arquivos e a estenda com seu próprio código
personalizado.
738 Capítulo 30 .NET Remoting em ação

FIGURA 30.1 Projeto template.

Rastreando mensagens
Ao desenvolver aplicações distribuídas, é freqüentemente útil ver o que trafega pela rede
em baixo nível. Observando o tamanho dos pacotes de dados, seu conteúdo e assim por
diante, você terá um melhor entendimento da tecnologia em uso e estará em uma posi-
ção melhor para entender qualquer erro se ocorrer algum problema.

DICA
Há maneiras diferentes de fazer isso, mas a mais simples é, provavelmente, utilizar uma ferramenta
de captura de pacote como o utilitário tcpTrace.
Você pode fazer download desse utilitário freeware em www.pocketsoap.com.
tcpTrace captura pacotes TCP enviados por aplicações clientes em certa porta, exibe-os em uma
janela e então os encaminha para um destino especificado. Uma vez que o destino recebe e pro-
cessa o pacote, tcpTrace captura a resposta e a encaminha de volta para o cliente.

O servidor desse exemplo foi construído no Capítulo 29. Ele escuta mensagens
HTTP enviadas para a porta 8099.
Isso é retratado na Figura 30.2.

HTTP 8099
Servidor Cliente
do banco do banco

FIGURA 30.2 Sistema de mensagens HTTP de BankSample.


Rastreando mensagens 739

A fim de registrar em log o tráfego utilizando tcpTrace, precisamos mudar da porta


de escuta original 8099 para uma diferente (isto é, para a 8231). Isso é ilustrado na Fi-
gura 30.3.

HTTP 8231 HTTP 8099


Servidor Cliente
tcpTrace
do banco do banco

FIGURA 30.3 Redirecionando mensagens de HTTP enviadas da porta 8099 para a porta
8231.

No projeto, a linha

const def_HTTPPort : integer = 8099;

deve ser mudada para

const def_HTTPPort : integer = 8231;

antes de recompilada e executada.


Para fazer tcpTrace capturar pacotes, você deve carregá-lo e inserir valores, como
mostrado na Figura 30.4, na caixa de diálogo tcpTrace Settings.

FIGURA 30.4 Caixa de diálogo tcpTrace Settings.

Depois de clicar em OK, o tcpTrace está pronto para capturar pacotes.


Depois de carregar BankClient.exe e clicar no botão Refresh, o tcpTrace exibirá as infor-
mações mostradas na Figura 30.5.
O ListBox na esquerda mostra as conexões atualmente abertas. Os painéis à esquerda
mostram os dados que saem (parte superior) e os que chegam (parte inferior) para o
cliente.
Depois de clicar no botão Refresh (ou em qualquer outro referente a isso) no cliente
do banco, você não verá novas entradas no ListBox porque a conexão TCP está sendo
mantida aberta. tcpTrace continuará a adicionar dados às caixas memo à direita. Para ver
os novos dados, simplesmente selecione o memo e role para baixo.
740 Capítulo 30 .NET Remoting em ação

FIGURA 30.5 Janela tcpTrace.

Analisando os pacotes SOAP


Como você talvez tenha adivinhado ao examinar os dados registrados em log, o cliente e
o servidor no projeto Bank utilizam o SOAP como protocolo de mensagens. Esse é a codi-
ficação padrão que o .NET utiliza para remoting. Você verá como mudar para a codifica-
ção binária mais adiante neste capítulo.
Uma mensagem SOAP é um documento XML chamado formalmente de Envelope
que tem a estrutura mostrada na Figura 30.6.
A parte do cabeçalho SOAP é opcional e muito aberta para qualquer utilização como
a atual. Você pode utilizar cabeçalhos para passar informações adicionais relacionadas
com o processamento da mensagem e permitir que uma mensagem SOAP seja estendida
de maneira específica ao aplicativo.

FIGURA 30.6 Estrutura do envelope.


Analisando os pacotes SOAP 741

NOTA
Utilizações futuras de cabeçalhos SOAP talvez incluam sistema de mensagens de múltiplos servi-
dores (isto é, o servidor A recebe o pacote, faz algo e então encaminha a mensagem para o servi-
dor B).

O corpo SOAP é obrigatório e basicamente descreve a chamada de procedure remota


(o serviço alvo e nome do método junto com seus parâmetros).
Selecione a conta bancária 1 e clique no botão Retrieve. A Listagem 30.1 mostra o
envelope SOAP que será gerado e resultará em uma chamada para o método BankMana-
ger.GetAccount( ).

LISTAGEM 30.1 A solicitação SOAP


1: <SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
➥xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-
➥ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-
➥ENV="http://schemas.xmlsoap.org/soap/envelope/"
➥xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-
➥ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
2: <SOAP-ENV:Body>
3: <i2:GetAccount id="ref-1"
➥xmlns:i2="http://schemas.microsoft.com/clr/nsassem/
➥BankShared.IBankManager/BankPackage">
4: <AccountNumber>1</AccountNumber>
5: </i2:GetAccount>
6: </SOAP-ENV:Body>
7: </SOAP-ENV:Envelope>

Como você pode ver, os envelopes SOAP contêm informações como o nome do méto-
do remoto (linha 3) e seus parâmetros (linha 4) junto com seus valores. Essas informações
serão utilizadas pelo servidor para localizar o objeto correto e o método certo a invocar.
A mensagem de resposta é outro envelope SOAP que contém os resultados. A Lista-
gem 30.2 mostra um exemplo de uma mensagem de resposta.

LISTAGEM 30.2 A resposta SOAP


1: <SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
➥xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://
➥schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://
➥schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.
➥microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://
➥schemas.xmlsoap.org/soap/encoding/">
2: <SOAP-ENV:Body>
3: <i2:GetAccountResponse id="ref-1" ➥xmlns:i2="http://
➥schemas.microsoft.com/clr/nsassem/
➥BankShared.IBankManager/BankPackage">
4: <return href="#ref-4"/>
742 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.2 Continuação


5: </i2:GetAccountResponse>
6: <a1:TAccount id="ref-4"
➥xmlns:a1="http://schemas.microsoft.com/clr/nsassem/BankShared/
➥BankPackage%2C%20Version%3D1.0.1436.24713%2C%20Culture%3Dneutral
➥%2C%20PublicKeyToken%3Dnull">
7: <fNumber>1</fNumber>
8: <fBalance>1999</fBalance>
9: <fName id="ref-5">John Smith</fName>
10: </a1:TAccount>
11: </SOAP-ENV:Body>
12: </SOAP-ENV:Envelope>

Se examinar atentamente a Listagem 30.2 você verá uma representação de XML de


uma instância TAccount e o valor de suas propriedades (linhas 6 a 10). Essa instância é refe-
renciada na linha 4 e associada com o valor de retorno da chamada para o método
GetAccount (linha 5).
Por fim, se ocorrer uma exceção, o SOAP define um tipo de envelope especial que se-
gue a estrutura mostrada na Figura 30.7.

FIGURA 30.7 Envelope com exceção.

Para gerar um erro, simplesmente digite “13333” no Bank Accounts ComboBox e cli-
que em Refresh. O servidor levantará uma exceção porque não há nenhuma conta que
corresponda a esse código. A exceção então será empacotada em uma mensagem de erro
SOAP e levantada novamente no lado do cliente.
Apenas como um lembrete, o método no lado do servidor que retorna as informa-
ções de conta e a exceção levantada era tão simples quanto

function TBankManager.GetAccount(const AccountNumber: integer): TAccount;


begin
case AccountNumber of
1 : result := fAccount1;
2 : result := fAccount2;
Client Activation 743

else raise Exception.Create('Invalid account number!');


end;

with result do
Writeln('A client requested account '+result.Number.ToString
➥+' ('+result.Name+')');
end;

Como você pode ver, a formatação e a propagação de exceção do SOAP são feitas in-
teiramente fora do código de negócio.

NOTA
Se estiver interessado, você pode ler mais sobre a especificação SOAP em http://www.w3c.org/
2000/xp/Group/.

Client Activation
O serviço BankManager mostrado no Capítulo 29 é um exemplo de um Server Activated
Object (SAO).
Embora os SAOs sejam suficientes para a maioria das utilizações, eles têm várias res-
trições:
— Os SAOs não permitem que clientes controlem quando eles são criados.

— Eles são apenas compartilhados (singleton) ou criados por demanda (por chamada).

— Os SAOs não permitem que o estado cliente seja preservado entre as chamadas de
método subseqüentes.
— Você só pode criar SAOs utilizando o construtor padrão.

Há casos em que essas limitações são muito restritivas.


Os objetos client activated (CAOs) fornecem uma solução alternativa. Client activa-
tion permite você
— controlar diretamente quando os objetos remotos são criados

— fornecer a cada cliente uma cópia única do objeto e, portanto, permite estado por
cliente no servidor
— emitir chamadas de método subseqüentes e certificar-se de que o estado é preser-
vado entre cada uma
— criar os objetos remotos com qualquer construtor

O padrão Factory
Os objetos client-activated são mais bem criados utilizando o padrão factory (factory pattern).
O padrão factory é uma abordagem comum utilizada para ajudar a desacoplar a cria-
ção de objeto de um cliente. A idéia básica é que os clientes invoquem um método de um
744 Capítulo 30 .NET Remoting em ação

objeto especializado (a fábrica) cujo propósito é criar e retornar outros objetos (o CAO
em nosso caso) para eles. O método na factory pode ter qualquer número de parâmetros,
que geralmente corresponde àqueles do construtor da classe CAO.
O exemplo ClientActivated incluído na pasta CAO contém o exemplo de um objeto
factory e como você pode utilizá-lo para criar duas instâncias separadas de objetos Tsim-
pleServer client-activated.
O grupo de projeto para esse exemplo é mostrado na Figura 30.8.
A classe factory está contida na nova unit SimpleServer_Factory.pas e é declarada as-
sim:

TSimpleFactory = class(MarshalByRefObject, ISimpleFactory)


protected
function NewInstance: ISimpleServer;
function NewInstanceWithValue(Value: Integer): ISimpleServer;
end;

A interface ISimpleFactory havia sido previamente declarada e adicionada ao Shared-


Package como

ISimpleFactory = interface
function NewInstance : ISimpleServer;
function NewInstanceWithValue(Value : integer) : ISimpleServer;
end;

O cliente utilizará uma instância remota do objeto factory (SAO, singleton) para criar
explicitamente instâncias de SimpleServer utilizando um dos dois métodos NewInstanceXXX.
Para permitir que o cliente acesse o objeto SimpleFactory, precisaremos registrar como
um serviço bem conhecido como fizemos no BankExample em relação ao TBankManager.

FIGURA 30.8 Grupo de projeto do exemplo ClientActivated.


Client Activation 745

Esse código registra TSimpleFactory:

RemotingConfiguration.RegisterWellKnownServiceType(
typeOf(TSimpleFactory),
'SimpleFactory.soap',
WellKnownObjectMode.Singleton);

Observe que não colocamos nenhum código de registro para a classe TsimpleServer.
Não é necessário tornar a infra-estrutura remoting ciente de sua existência porque Sim-
pleFactory será aquele que instancia os objetos SimpleServer(s).
Esse é o código cliente:
SimpleFactory := Activator.GetObject(
typeof(ISimpleServer),
'http://localhost:8099/SimpleFactory.soap');

SimpleSrv1 := SimpleFactory.NewInstanceWithValue(5);
SimpleSrv2 := SimpleFactory.NewInstance;

Como você pode ver, a sintaxe necessária para utilizar CAOs é idêntica àquelas para
os objetos locais.
As Listagens 30.3–30.7 mostram o código completo das units mais importantes no
grupo de projeto.
A Listagem 30.3 contém as definições das interfaces ISimpleServer e ISimpleFactory que
são necessárias e referenciadas tanto pela aplicação cliente como pela aplicação servidor.

LISTAGEM 30.3 As interfaces compartilhadas


1: unit SharedInterfaces;
2:
3: interface
4:
5: type
6: ISimpleServer = interface
7: function GetValue : integer;
8 procedure SetValue(Value : integer);
9:
10: property Value : integer read GetValue write SetValue;
11: end;
12:
13: ISimpleFactory = interface
14: function NewInstance : ISimpleServer;
15: function NewInstanceWithValue(Value : integer) : ISimpleServer;
16: end;
17:
18: implementation
19:
20: end.

u Localize o código no CD: \Code\Chapter 30\Ex02\.


746 Capítulo 30 .NET Remoting em ação

A Listagem 30.4 contém a implementação de TSimpleServer. A única diferença da


unit TSimpleServer original contida no RemotingTemplate são as saídas de console, que per-
mitirão monitorar o que acontece durante a criação de instâncias da classe e atribuir um
valor inteiro à propriedade Value. Essa saída é mostrada mais adiante na Figura 30.9.

LISTAGEM 30.4 Implementação de TSimpleServer


1: unit SimpleServer_Impl;
2:
3: interface
4:
5: uses
6: SharedInterfaces;
7:
8: type
9: { TSimpleServer }
10: TSimpleServer = class(MarshalByRefObject, ISimpleServer)
11: private
12: fValue : integer;
13:
14: protected
15: function GetValue: Integer;
16: procedure SetValue(Value: Integer);
17:
18: public
19: constructor Create; overload;
20: constructor Create(Value : integer); overload;
21: end;
22:
23: implementation
24:
25: { TSimpleServer }
26:
27: constructor TSimpleServer.Create;
28: begin
29: inherited;
30:
31: Console.WriteLine('Creating TSimpleServer with empty constructor');
32: end;
33:
34: constructor TSimpleServer.Create(Value: integer);
35: begin
36: inherited Create;
37:
38: fValue := Value;
39:
40: Console.WriteLine('Creating TSimpleServer with value '+
➥Int32(Value).ToString);
41: end;
Client Activation 747

LISTAGEM 30.4 Continuação


42:
43: function TSimpleServer.GetValue: Integer;
44: begin
45: result := fValue;
46:
47: Console.WriteLine('Getting value '+Int32(result).ToString);
48: end;
49:
50: procedure TSimpleServer.SetValue(Value: Integer);
51: begin
52: fValue := Value;
53:
54: Console.WriteLine('Setting value '+Int32(Value).ToString);
55: end;
56:
57: end.

u Localize o código no CD: \Code\Chapter 30\Ex02\.

A Listagem 30.5 contém a implementação da classe TSimpleFactory que é registrada


como um tipo bem conhecido.

LISTAGEM 30.5 Implementação de TSimpleFactory


1: unit SimpleServer_Factory;
2:
3: interface
4:
5: uses
6: SharedInterfaces;
7:
8 type
9: TSimpleFactory = class(MarshalByRefObject, ISimpleFactory)
10: protected
11 function NewInstance: ISimpleServer;
12: function NewInstanceWithValue(Value: Integer): ISimpleServer;
13:
14: end;
15:
16:
17: implementation
18:
19: uses SimpleServer_Impl;
20:
21: { TSimpleFactory }
22:
23: function TSimpleFactory.NewInstance: ISimpleServer;
748 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.5 Continuação


24: begin
25: result := TSimpleServer.Create;
26: end;
27:
28: function TSimpleFactory.NewInstanceWithValue(
29: Value: Integer): ISimpleServer;
30: begin
31: result := TSimpleServer.Create(Value);
32: end;
33:
34: end.

u Localize o código no CD: \Code\Chapter 30\Ex02\.

A classe concreta TSimpleFactory só é utilizada na aplicação servidor e é responsável


por criar instâncias do CAO TSimpleServer. As instâncias de TSimpleServer são retornadas
como ISimpleServer pelos métodos NewInstance e NewInstanceWithValue.
A Listagem 30.6 contém o arquivo DPR do projeto servidor e mostra como registrar
o objeto TSimpleFactory na infra-estrutura remoting (ver linha 27).
Como dito anteriormente, a classe TSimpleServer nunca é registrada.
As únicas coisas que os clientes precisam para criar CAOs utilizando o padrão factory
são: (1) sua interface (incluída no pacote compartilhado) e (2) uma classe factory (regis-
trada como WellKnownServiceType da mesma maneira mostrada na linha 28).

LISTAGEM 30.6 Arquivo DPR do servidor


1: program Server;
2:
3: {$APPTYPE CONSOLE}
4:
5: {...}
6: {...}
7: {%DelphiDotNetAssemblyCompiler 'SharedPackage.dll'}
8:
9: uses
10: SharedInterfaces,
11: SimpleServer_Impl in 'SimpleServer_Impl.pas',
12: System.Runtime.Remoting,
13: System.Runtime.Remoting.Channels,
14: System.Runtime.Remoting.Channels.HTTP,
15: SimpleServer_Factory in 'SimpleServer_Factory.pas';
16:
17: const def_HTTPPort : int32 = 8099;
18: var Channel : HttpChannel;
19: begin
20: Writeln('Initializing server...');
Client Activation 749

LISTAGEM 30.6 Continuação


21:
22: // Inicializa o servidor para ouvir solicitações HTTP em uma porta específica
23: Channel := HttpChannel.Create(def_HTTPPort);
24: ChannelServices.RegisterChannel(Channel);
25: Console.WriteLine('HTTP channel created. Listening on port '+
➥int32(def_HTTPPort).ToString);
26:
27: // Registra o serviço TsimpleFactory
28: RemotingConfiguration.RegisterWellKnownServiceType(
29: typeOf(TSimpleFactory),
30: 'SimpleFactory.soap',
31: WellKnownObjectMode.Singleton);
32:
33: // Começa a aceitar solicitações
34: Writeln('Waiting for requests...');
35: Readln;
36: end.

u Localize o código no CD: \Code\Chapter 30\Ex02\.

A Listagem 30.7 contém o arquivo de DPR da aplicação cliente. Ela mostra como re-
gistrar um canal HTTP para permitir comunicação remota com o servidor (linhas 21 e
22) e como adquirir uma referência ao objeto SimpleFactory (linhas 24–26).

LISTAGEM 30.7 O arquivo DPR do cliente


1: program Client;
2:
3: {$APPTYPE CONSOLE}
4:
5: {..}
6: {..}
7: {%DelphiDotNetAssemblyCompiler 'SharedPackage.dll'}
8:
9: uses
10: SysUtils,
11: SharedInterfaces,
12: System.Runtime.Remoting,
13: System.Runtime.Remoting.Channels,
14: System.Runtime.Remoting.Channels.HTTP;
15:
16: var SimpleFactory : ISimpleFactory;
17: SimpleSrv1,
18: SimpleSrv2 : ISimpleServer;
19: Channel : HttpChannel;
20: begin
21: Channel := HttpChannel.Create(0);
750 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.7 Continuação


22: ChannelServices.RegisterChannel(Channel);
23:
24: SimpleFactory := Activator.GetObject(
25: typeof(ISimpleFactory),
26: 'http://localhost:8099/SimpleFactory.soap');
27:
28: SimpleSrv1 := SimpleFactory.NewInstanceWithValue(5);
29: SimpleSrv2 := SimpleFactory.NewInstance;
30:
31: Writeln('SimpleServer1''s value is '+IntToStr(SimpleSrv1.Value));
32: Writeln('SimpleServer2''s value is '+IntToStr(SimpleSrv2.Value));
33: Writeln('Setting simpleServer2''s value to 2');
34: SimpleSrv2.Value := 2;
35: Writeln('SimpleServer2''s value is '+IntToStr(SimpleSrv2.Value));
36: Writeln('SimpleServer1''s value is '+IntToStr(SimpleSrv1.Value));
37: Writeln('Press enter to terminate');
38: Readln;
39: end.

u Localize o código no CD: \Code\Chapter 30\Ex02\.

SimpleFactory então será utilizado para criar instâncias de CAOs SimpleServer utilizan-
do os dois métodos NewInstanceXXX (linhas 28 e 29).
Por fim, o código irá demonstrar como as duas variáveis SimpleSrv1 e SimpleSrv2 estão
de fato apontando para duas instâncias do lado servidor separadas e a atribuição à pro-
priedade Value resulta em diferentes leituras (linhas 31–37).

O exemplo em tempo de execução


As telas capturadas a seguir mostram o que acontece quando aplicações servidor e cliente
são executadas.
A Figura 30.9 mostra a saída da aplicação servidor.

FIGURA 30.9 O servidor em tempo de execução.


Gerenciamento de Lifetime 751

Compare a saída de tela com o código mostrado nas Listagens 30.4 e 30.6.
A Figura 30.10 mostra a saída da aplicação cliente. Consulte o código mostrado na
Listagem 30.7 ao examinar essa tela.
Note como os valores são corretamente preservados pelas duas instâncias separadas
de SimpleServer.

FIGURA 30.10 O cliente em tempo de execução.

Problemas dos CAOs


Os CAOs são simples de escrever e utilizar, mas – de uma perspectiva de servidor – eles
podem consumir muitos recursos.
Para ilustrar isso, imagine se você tivesse 1.000 usuários concorrentes, cada um deles
executando a aplicação cliente: o servidor precisaria criar 2.000 instâncias de TSimpleSer-
ver, nenhuma das quais seria destruída até: (1) seu lease expirar e (2) o garbage collector
.NET decidir atuar.
Um exemplo simples semelhante ao mostrado neste capítulo provavelmente não
apresentaria problemas nem mesmo com 2.000 clientes. Entretanto, se você começar a
utilizar conexões de banco de dados, alocar memória em cada CAO e assim por diante, o
desempenho e a responsividade serão rapidamente afetados.
Os sistemas distribuídos baseados em CAOs (ou objetos com estado em geral) não
são escalonáveis. Cada CAO é vinculado a um servidor particular até ele ser destruído e as
chamadas de método subseqüentes não podem passar por um equilíbrio de carga.
Seja conservador ao utilizar CAOs e tente evitar utilizá-los, exceto quando estrita-
mente necessário ou tiver um número pequeno de clientes concorrentes.

Gerenciamento de Lifetime
Gerenciar o tempo de vida de um objeto remoto é complexo, mas ter controle sobre
quando liberar recursos de servidor é essencial à escalabilidade e eficiência de qualquer
sistema distribuído.
Historicamente, o DCOM utilizava uma combinação de contagem de referência e
ping de rede a fim de determinar quando liberar objetos. Embora essa técnica funcionas-
se de modo decente o bastante em redes locais com poucos clientes, ela resultava em pro-
752 Capítulo 30 .NET Remoting em ação

blemas com o aumento do número de clientes (pense em milhares) e tornou-se quase


impraticável pela Internet.
O .NET utiliza uma abordagem completamente diferente e oferece controle direto
do tempo de vida de seus objetos.
Sempre que um objeto MarshalByRefObject é criado, o .NET Framework chama seu mé-
todo virtual InitializeLifetimeService( ), que, por sua vez, retorna um objeto implemen-
tando a interface ILease.
ILease é declarada como

ILease = interface
function get_CurrentLeaseTime: TimeSpan;
function get_CurrentState: LeaseState;

procedure set_InitialLeaseTime(value: TimeSpan);


function get_InitialLeaseTime: TimeSpan;

function get_RenewOnCallTime: TimeSpan;


procedure set_RenewOnCallTime(value: TimeSpan);

function get_SponsorshipTimeout: TimeSpan;


procedure set_SponsorshipTimeout(value: TimeSpan);

procedure Register(obj: ISponsor; renewalTime: TimeSpan); overload;


procedure Register(obj: ISponsor); overload;
function Renew(renewalTime: TimeSpan): TimeSpan;
procedure Unregister(obj: ISponsor);

property CurrentLeaseTime: TimeSpan read get_CurrentLeaseTime;


property CurrentState: LeaseState read get_CurrentState;
property InitialLeaseTime: TimeSpan read get_InitialLeaseTime
write set_InitialLeaseTime;
property RenewOnCallTime: TimeSpan read get_RenewOnCallTime
write set_RenewOnCallTime;
property SponsorshipTimeout: TimeSpan read get_SponsorshipTimeout
write set_SponsorshipTimeout;
end;

A interface ILease contém todos os métodos necessários para a infra-estrutura remo-


ting determinar quando um objeto remoto está pronto para ser pego pelo garbage collec-
tor. Cada application domain incorpora um gerenciador de lease que está ciente de cada
lease no domínio e que verifica periodicamente seu tempo de expiração. Sempre que um
lease expirar, a infra-estrutura remoting solicitará um sponsor (um objeto que imple-
menta ISponsor como o TSimpleFactory em nosso exemplo) para renovar o lease invocando
o método Renewal.
ISponsor é declarada como
Gerenciamento de Lifetime 753

ISponsor = interface
function Renewal(lease: ILease): TimeSpan;
end;

Se a chamada para Renewal não resultar na renovação do lease, o objeto ILease é remo-
vido da lista de lease gerenciada pelo gerenciador de lease. Isso assegurará que o objeto
remoto seja pego pelo garbage collector.
Os sponsors são objetos que são periodicamente solicitados a renovar seus leases. Se
isso não acontece e o lease expira, o objeto sponsor é desconectado da infra-estrutura re-
moting. Depois disso, ele estará disponível para o garbage collection.
O projeto SponsorAndLease mostra como adicionar suporte ISponsor ao MarshalByRef-
Objects.
A unit SimpleServerFactory_Impl.pas contém as seguintes alterações:

uses
SharedInterfaces,
SimpleServer_Impl,
System.Runtime.Remoting.Lifetime; // Contém a definição de Isponsor

type
TSimpleFactory = class(MarshalByRefObject, ISimpleFactory, ISponsor)
[..]
public
function InitializeLifetimeService: System.Object; override;
function Renewal(lease: ILease): TimeSpan;
end;

u Localize o código no CD: \Code\Chapter 30\Ex03\.

Essa é a implementação de InitializeLifetimeService( ) em que obtemos uma refe-


rência para um lease e a inicializamos para os últimos cinco minutos:

function TSimpleFactory.InitializeLifetimeService: System.Object;


var lease : ILease;
begin
Writeln('Initializing lifetime service for TSimpleFactory');
lease := inherited InitializeLifetimeService( ) as ILease;
if (lease.CurrentState = LeaseState.Initial) then begin
lease.InitialLeaseTime := TimeSpan.FromMinutes(5);
lease.Register(Self);
end;
result := lease;
end;

u Localize o código no CD: \Code\Chapter 30\Ex03\.

O seguinte código é a implementação do método Renewal em que renovamos cons-


tantemente o lease para mais cinco minutos de cada vez:
754 Capítulo 30 .NET Remoting em ação

function TSimpleFactory.Renewal(lease: ILease): TimeSpan;


begin
writeln('Request for renewal for TSimpleFactory');
lease.Renew(TimeSpan.FromMinutes(5));
end;

u Localize o código no CD: \Code\Chapter 30\Ex03\.

Código semelhante é adicionado à implementação do CAO TSimpleServer.


A Figura 30.11 mostra a saída desse servidor.
Note como a inicialização do serviço de tempo de vida é realizada imediatamente
depois da criação dos objetos.
A Figura 30.12 mostra a atividade do cliente.
Note como esperar dois segundos antes de reler os valores SimpleServer(s) não resul-
tou em nenhuma destruição ou reinicialização. Essas duas instâncias permanecerão ati-
vas e continuarão a renovar seus leases a cada cinco minutos.

Falha ao renovar o Lease


Como dito anteriormente, se você não conseguir renovar um lease, o objeto remoto é re-
movido da infra-estrutura remoting e liberado para garbage collection.

FIGURA 30.11 Saída do servidor.

FIGURA 30.12 Atividade do cliente.


Arquivos de configuração 755

Embora isso não assegure que o objeto seja liberado imediatamente, garante que
qualquer chamada subseqüente irá falhar.
Para ilustrar isso, você pode modificar a unit SimpleServer_Impl.pas, desativando os
caracteres de comentário da linha em que ela renova o lease. Isso será semelhante a:

function TSimpleServer.Renewal(Lease: ILease): TimeSpan;


begin
writeln('Request for renewal for TSimpleServer');
// Desative a linha a seguir para evitar renovação de locação
// Lease.Renew(TimeSpan.FromMinutes(LeaseDu
end;

u Localize o código no CD: \Code\Chapter 30\Ex03\.

Fazendo isso, o lease para o CAO SimpleServer não será agora renovado. Se você agora
esperar cinco minutos (ou modificar o horário de seu computador para simular isso),
quando o cliente pedir para você clicar em Enter para ler os valores, ele levantará uma ex-
ceção.
A Figura 30.13 demonstra isso.

FIGURA 30.13 Exceção do cliente.

Arquivos de configuração
Embora seja simples especificar as configurações HTTP e TCP que você quer utilizar com
.NET remoting, os sistemas de produção freqüentemente precisam ser configuráveis.
O .NET Remoting inclui suporte para os chamados arquivos de configuração. Esses
são arquivos XML formatados de acordo com a estrutura mostrada na Listagem 30.8.

LISTAGEM 30.8 A estrutura de arquivos XML de configuração .NET Remoting


<configuration>
<system.runtime.remoting>
<application>
<lifetime>
756 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.8 Continuação


<channels> (Instance)
<channel> (Instance)
<serverProviders> (Instance)
<provider> (Instance)
<formatter> (Instance)
<clientProviders> (Instance)
<provider> (Instance)
<formatter> (Instance)
<client>
<wellknown> (Client Instance)
<activated> (Client Instance)
<service>
<wellknown> (Service Instance)
<activated> (Service Instance)
<soapInterop>
<interopXmlType>
<interopXmlElement>
<preLoad>
<channels> (Template)
<channel> (Template)
<serverProviders> (Instance)
<provider> (Instance)
<formatter> (Instance)
<clientProviders> (Instance)
<provider> (Instance)
<formatter> (Instance)
<channelSinkProviders>
<serverProviders> (Template)
<provider> (Template)
<formatter> (Template)
<clientProviders> (Template)
<provider> (Template)
<formatter> (Template)
<customErrors>
<debug>

NOTA
Consulte o http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/
html/gnconRemotingSettingsSchema.asppara obter uma descrição detalhada de cada elemento.

Os vários nós nesses arquivos permitem configurar todos os aspectos remoting – dos
canais que você quer utilizar (HTTP ou TCP, por exemplo), os serviços que você quer ex-
por ou acessar, suas configurações de tempo de vida e assim por diante.
Você pode aplicar arquivos XML de configuração chamando o método RemotingCon-
figuration.Configure( ) e especificando o nome de um arquivo como parâmetro.
Arquivos de configuração 757

O exemplo ConfigFiles mostra como utilizar arquivos de configuração remoting em


extremidades tanto do servidor como do cliente.
O exemplo também ilustrará como utilizar arquivos de configuração no cliente.

Configuração do servidor
A Listagem 30.9 mostra o documento XML com o conteúdo do arquivo Server.Config.xml
contido na pasta do projeto.

LISTAGEM 30.9 Arquivo de configuração do servidor


1: <configuration>
2: <system.runtime.remoting>
3: <application>
4: <channels>
5: <channel ref="tcp" port="8231" />
6: <channel ref="http" port="8099" />
7: </channels>
8: <service>
9: <wellknown type="SimpleServer_Factory.TSimpleFactory, Server"
10: mode="Singleton"
11: objectUri="SimpleFactory.soap" />
12: </service>
13: </application>
14: </system.runtime.remoting>
15: </configuration>

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Quando utilizado, esse arquivo de configuração instruirá a infra-estrutura remoting a


criar um canal TCP e canal um HTTP que escutam respectivamente a porta 8231 e a 8099.
Ele também registrará a classe TSimpleFactory como WellKnown, objeto singleton que
trata solicitações enviadas que referenciam o URI "SimpleFactory.soap".
A Listagem 30.10 mostra como o servidor se torna mais simples ao utilizar arquivos
de configuração semelhantes aos mostrados na Listagem 30.9.

LISTAGEM 30.10 O DPR do servidor utilizando arquivos de configuração


1: uses
2: SharedInterfaces,
3: SimpleServer_Impl in 'SimpleServer_Impl.pas',
4: System.Runtime.Remoting,
5: System.Runtime.Remoting.Channels,
6: System.Runtime.Remoting.Channels.HTTP,
7: SimpleServer_Factory in 'SimpleServer_Factory.pas';
8:
9: begin
758 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.10 Continuação


10: Writeln('Initializing server by reading the configuration file');
11: RemotingConfiguration.Configure('Server.config.xml');
12:
13: // Começa a aceitar solicitações
14: Writeln('Waiting for requests...');
15: Readln;
16: end.

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Compare esse código com o mostrado na Listagem 30.6 para ver as vantagens de uti-
lizar arquivos de configuração.
Vale notar que os arquivos de configuração não têm necessariamente de conter in-
formações de serviço e canal. Você poderia utilizá-los para configurar somente o que
você precisa tornar dinâmico.
Por exemplo, você poderia omitir o nó "<service>" do arquivo de configuração XML
e ainda registrá-lo como fizemos na linha 29 da Listagem 30.6.
Você agora pode iniciar o cliente do exemplo Client Activation acessando esse servi-
dor e ele simplesmente funcionará bem, como com o servidor original.

Configuração do cliente
Os arquivos de configuração do cliente são estruturalmente semelhantes àqueles utiliza-
dos no lado servidor, mas contêm informações ligeiramente diferentes.
A Listagem 30.11 mostra o arquivo de configuração XML utilizado no lado cliente.

LISTAGEM 30.11 Arquivo de configuração do cliente


1: <configuration>
2: <system.runtime.remoting>
3: <application>
4: <client>
5: <wellknown type="SharedInterfaces.ISimpleFactory, SharedPackage"
6: url="http://localhost:8099/SimpleFactory.soap" />
7: </client>
8: </application>
9: </system.runtime.remoting>
10: </configuration>

u Localize o código no CD: \Code\Chapter 30\Ex04\.

A primeira coisa a notar é que a configuração cliente registra o tipo de interface ISim-
pleFactory em vez da classe TSimpleFactory como fez o servidor.
Os clientes não têm nenhum conhecimento da classe real que implementa as inter-
faces remotas. Para torná-los cientes da existência de classes como TSimpleServer, teríamos
de implantar os seus assemblies nos clientes.
Arquivos de configuração 759

Embora factível, isso violaria o princípio mais básico das arquiteturas distribuídas, o
que é veementemente não-recomendado.
Antes de continuarmos, devemos voltar um passo e examinar código a seguir pego
do arquivo .dpr do cliente exemplo Client Activation:

var SimpleFactory : ISimpleFactory;


[..]
Channel : HttpChannel;
begin
Channel := HttpChannel.Create(0);
ChannelServices.RegisterChannel(Channel);
SimpleFactory := Activator.GetObject(
typeof(ISimpleFactory),
'http://localhost:8099/SimpleFactory.soap');

Nesse código o método Activator.GetObject( ) cria um proxy para um objeto bem


conhecido ativado por servidor que implementa ISimpleFactory, que então é atribuído à
variável SimpleFactory.
O Activator define duas versões de sobrecarga do método GetObject( ).
A que utilizamos tem a seguinte declaração:

function GetObject(type : System.Type; url : string) : System.Object;

A segunda é definida como

function GetObject(type : System.Type; url : string;


➥state : System.Object) : System.Object;

O problema com as duas versões é que elas requerem que os parâmetros de URL se-
jam especificados. Configurar o URL como uma string vazia irá gerar uma exceção e a
aplicação terminará.
O objetivo nesse exemplo é remover a necessidade do parâmetro de URL e ter essa
leitura a partir do arquivo de configuração.
Não parece existir uma maneira direta de fazer isso utilizando a classe Activator, e a
solução para esse problema é criar e utilizar uma classe auxiliar: TRemotingHelper.

Como criar proxies sem especificar URLs utilizando a classe TRemotingHelper


Quando você chama o método RemotingConfiguration.Configure, a infra-estrutura remoting
é atualizada e torna-se ciente das definições de configuração especificadas no arquivo
XML.
Utilizando as propriedades e métodos de RemotingConfiguration, você então pode aces-
sar essas informações dinamicamente a partir de sua aplicação.
TRemotingHelper estende a funcionalidade da classe Activator padrão acessando a con-
figuração remoting e permite criar proxies sem especificar nenhum URL graças ao méto-
do GetRemoteObjectRef( ).
GetRemoteObjectRef( ) é declarado assim:
760 Capítulo 30 .NET Remoting em ação

function GetRemoteObjectRef(aType : system.type) : TObject;

As aplicações clientes invocarão esse método simplesmente especificando o tipo de


interface de que precisam (nesse caso, ISimpleFactory) e fazendo a coerção de tipo (typecas-
ting) do TObject resultante para essa interface. Este é um exemplo de sua utilização:

SimpleFactory := RemotingHelper.GetRemoteObjectRef(
typeof(SharedInterfaces.ISimpleFactory));

A classe TRemotingHelper é muito simples. Para utilizá-la simplesmente emita uma cha-
mada RemotingConfiguration.Configure, crie uma instância de TRemotingHelper e invoque o
método GetRemoteObjectRef( ).
O código a seguir ilustra isso:

RemotingConfiguration.Configure('Client.Config.xml');
RemotingHelper := TRemotingHelper.Create;
SimpleFactory := RemotingHelper.GetRemoteObjectRef(
typeof(SharedInterfaces.ISimpleFactory));

Na primeira vez que você chamar o método GetRemoteObjectRef( ), TRemotingHelper lerá


a lista dos nomes e URLs de todos os tipos remoting conhecidos pelo cliente (nesse caso
seria ISimpleFactory e 'http://localhost:8099/SimpleFactory.soap') e armazenará ambas as
entradas (system.type e URL) em um campo IDictionary chamado fWellKnownTypes.
fWellKnownTypes então será utilizado para localizar o URL alvo utilizando o parâmetro
system.type especificado na chamada para GetRemoteObjectRef.
Depois de GetRemoteObjectRef( ) coletar essas informações, TRemotingHelper tem todas
as informações necessárias para chamar o original Activator.GetObject( ), especificando o
URL necessário a nosso favor.
A Listagem 30.12 mostra o código do arquivo DPR do servidor. (O arquivo Ser-
ver.config.xml foi mostrado anteriormente na Listagem 30.9.)

LISTAGEM 30.12 O arquivo DPR do servidor


1: program Server;
2:
3: {$APPTYPE CONSOLE}
4:
5: {…}
6: {...}
7: {%DelphiDotNetAssemblyCompiler 'SharedPackage.dll'}
8:
9: uses
10: SharedInterfaces,
11: SimpleServer_Impl in 'SimpleServer_Impl.pas',
12: System.Runtime.Remoting,
13: System.Runtime.Remoting.Channels,
14: System.Runtime.Remoting.Channels.HTTP,
15: SimpleServer_Factory in 'SimpleServer_Factory.pas';
Arquivos de configuração 761

LISTAGEM 30.12 Continuação


16:
17: var args : System.Array;
18: filename : string = 'Server.Config.xml';
19: begin
20: // Lê os argumentos da linha de comando
21: args := Environment.GetCommandLineArgs;
22: if args.Length>1 then
23: filename := args.GetValue(1).ToString+filename;
24:
25: // Configura a infra-estrutura remoting
26: Console.WriteLine('Initializing the server by reading the file '+filename);
27:
28: RemotingConfiguration.Configure(filename);
29:
30: // Começa a aceitar solicitações
31: Console.WriteLine('Waiting for requests...');
32: Console.ReadLine;
33: end.

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Como você pode ver, o registro do canal HTTP e o TSimpleFactory foram completamen-
te substituídos pela chamada para RemotingConfiguration.Configure('Server.config.xml').
A Listagem 30.13 contém todo o código-fonte da classe TRemotingHelper.

LISTAGEM 30.13 A classe RemotingHelper


1: unit RemotingHelper;
2:
3: interface
4:
5: uses
6: System.Collections,
7: System.Runtime.Remoting;
8:
9: type
10: { TRemotingHelper }
11: TRemotingHelper = class
12: private
13: fIsInitialized : boolean;
14: fWellKnownTypes : IDictionary;
15:
16: procedure InitTypeCache;
17: public
18: function GetRemoteObjectRef(aType : system.type) : TObject;
19: end;
20:
762 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.13 Continuação


21:
22: implementation
23:
24: { TRemotingHelper }
25:
26: function TRemotingHelper.GetRemoteObjectRef(aType: system.type): TObject;
27: var entry : WellKnownClientTypeEntry;
28: begin
29: if not fIsInitialized
30: then InitTypeCache;
31:
32: entry := fWellKnownTypes[aType] as WellKnownClientTypeEntry;
33:
34: if (entry=NIL)
35: then raise Exception.Create('Type not found!');
36:
37: result := Activator.GetObject(entry.ObjectType,entry.ObjectUrl);
38: end;
39:
40: procedure TRemotingHelper.InitTypeCache;
41: var i : integer;
42: arr : array of WellKnownClientTypeEntry;
43: entry : WellKnownClientTypeEntry;
44: begin
45: fWellKnownTypes := Hashtable.Create;
46:
47: arr := RemotingConfiguration.GetRegisteredWellKnownClientTypes;
48: for i := 0 to Length(arr)-1 do begin
49: entry := arr[i];
50: fWellKnownTypes.Add(entry.ObjectType, entry);
51: end;
52:
53: fIsInitialized := TRUE;
54: end;
55:
56: end.s

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Essa classe utiliza um IDictionary .NET padrão (campo privado fWellKnownTypes) para
representar a coleção de pares System.Type e url associados com os serviços remotos. O di-
cionário é criado utilizando a classe HashTable na linha 45, que implementa IDictionary.
Os valores armazenados no dicionário são lidos diretamente da configuração remo-
ting por meio da chamada para RemotingConfiguration.GetRegisteredWellKnownClientTypes na
linha 47.
GetRegisteredWellKnownClientTypes é declarada como
Arquivos de configuração 763

Function RemotingConfiguration.GetRegisteredWellKnownClientTypes :
array of WellKnownClientTypeEntry:

Tenha em mente que para utilizar essa classe, você precisa primeiro chamar Remoting-
Configure.Configure. Isso é feito no arquivo DPR do cliente mostrado na linha 30 da Lista-
gem 30.14.
A Listagem 30.14 mostra a fonte completa da aplicação cliente. Note como lemos o
arquivo de configuração; nunca precisamos criar um canal e não utilizamos mais direta-
mente a classe Activator.
Esses parâmetros remoting utilizados pela aplicação cliente estão inteiramente con-
tidos no arquivo XML mostrado na Listagem 30.11.

LISTAGEM 30.14 Os arquivos DPR do cliente


1: program Client;
2:
3: {$APPTYPE CONSOLE}
4:
5: {..}
6: {..}
7: {%DelphiDotNetAssemblyCompiler 'SharedPackage.dll'}
8:
9: uses
10: SharedInterfaces,
11: System.Runtime.Remoting,
12: System.Runtime.Remoting.Channels,
13: System.Runtime.Remoting.Channels.HTTP,
14: RemotingHelper in 'RemotingHelper.pas';
15:
16: var SimpleFactory : ISimpleFactory;
17: SimpleSrv1,
18: SimpleSrv2 : ISimpleServer;
19: RemotingHelper : TRemotingHelper;
20: var args : System.Array;
21: filename : string = 'Client.Config.xml';
22: begin
23: // Lê os argumentos da linha de comando
24: args := Environment.GetCommandLineArgs;
25: if args.Length>1 then
26: filename := args.GetValue(1).ToString+filename;
27:
28: // Configura a infra-estrutura remoting
29: Console.WriteLine('Initializing the client by reading the file '+
➥filename);
30: RemotingConfiguration.Configure(filename);
31:
32: RemotingHelper := TRemotingHelper.Create;
33:
764 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.14 Continuação


34: SimpleFactory := RemotingHelper.GetRemoteObjectRef(
35: typeof(SharedInterfaces.ISimpleFactory));
36:
37: SimpleSrv1 := SimpleFactory.NewInstanceWithValue(5);
38: SimpleSrv2 := SimpleFactory.NewInstance;
39:
40: Console.WriteLine('SimpleServer1''s value is '+Int32(SimpleSrv1.Value).
➥ToString);
41: Console.WriteLine('SimpleServer2''s value is '+Int32(SimpleSrv2.Value).
➥ToString);
42: Console.WriteLine('Setting simpleServer2''s value to 2');
43: SimpleSrv2.Value := 2;
44: Console.WriteLine('SimpleServer2''s value is '+Int32(SimpleSrv2.Value).
➥ToString);
45: Console.WriteLine('SimpleServer1''s value is '+Int32(SimpleSrv1.Value).
➥ToString);
46: Console.WriteLine('Press enter to terminate');
47: Console.ReadLine;
48: end.

u Localize o código no CD: \Code\Chapter 30\Ex04\.

É importante notar que também fizemos uma pequena alteração nas units SimpleSer-
ver_Factory.pas e SimpleServer_Impl.pas adicionando as diretivas de compilador:

[assembly: RuntimeRequiredAttribute(TypeOf(TSimpleFactory))]

[assembly: RuntimeRequiredAttribute(TypeOf(TSimpleServer))]

Essas diretivas foram adicionadas logo abaixo da palavra-chave implementation nas


respectivas units e instruem o linker do Delphi a não remover as referências às classes
TSimpleServer e TSimpleServerFactory, mesmo se as classes nunca tiverem sido direta ou in-
diretamente referenciadas no arquivo .dpr.

Alternando entre a comunicação HTTP e TCP


O protocolo HTTP é ideal para tentar enviar dados por firewalls ou Internet, mas quando
seu objetivo é alcançar a velocidade máxima de comunicação, a comunicação TCP pro-
vavelmente será um candidato melhor. O .NET remoting permite alternar entre canais
de comunicação simplesmente alterando alguns parâmetros de configuração.

NOTA
O arquivo de configuração do servidor mostrado na Listagem 30.16 instrui o servidor a ouvir men-
sagens HTTP na porta 8099 e mensagens TCP na porta 8231.
Alternando entre codificação SOAP e binária 765

O cliente é configurado para utilizar mensagens HTTP por causa do valor do atributo
url no nó well-known (http://localhost:8099/SimpleFactory.soap).
Para fazê-lo utilizar um canal TCP, simplesmente mude isso para "tcp://localhost:8231/
SimpleFactory. soap" como na Listagem 30.15.

LISTAGEM 30.15 Arquivo de configuração do cliente utilizando um canal de TCP na


porta 8231
1: <configuration>
2: <system.runtime.remoting>
3: <application>
4: <client>
5: <wellknown type="SharedInterfaces.ISimpleFactory, SharedPackage"
6: url="tcp://localhost:8231/SimpleFactory.soap" />
7: </client>
8: </application>
9: </system.runtime.remoting>
10: </configuration>

u Localize o código no CD: \Code\Chapter 30\Ex04\.

DICA
Se você não quiser que o servidor ouça o canal de TCP, simplesmente remova o nó "<channel
ref="tcp" port="8231" />" do arquivo de configuração server.

Alternando entre codificação SOAP e binária


Por meio do uso de arquivos de configuração, também podemos alternar entre a codifi-
cação SOAP (o padrão utilizado pelo .NET Framework) e a codificação binária, mais efi-
ciente.
Para fazer isso precisamos registrar o provedor binário sob o nó "channels" em ambos
os arquivos de configuração.
O novo arquivo de configuração de servidor é mostrado na Listagem 30.16.

LISTAGEM 30.16 Arquivo de configuração do servidor para codificação binária


1: <configuration>
2: <system.runtime.remoting>
3: <application>
4: <channels>
5: <channel ref="tcp" port="8231" />
6: <channel ref="http" port="8099">
7: <serverProviders>
8: <formatter ref="binary" />
9: </serverProviders>
10: </channel>
766 Capítulo 30 .NET Remoting em ação

LISTAGEM 30.16 Continuação


11: </channels>
12: <service>
13: <wellknown type="SimpleServer_Factory.TSimpleFactory, Server"
14: mode="Singleton"
15: objectUri="SimpleFactory.soap" />
16: </service>
17: </application>
18: </system.runtime.remoting>
19: </configuration>

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Como você pode ver, parâmetros como números da porta (linha 5 e 6), o modo de
ativação (linha 14) e outros podem ser facilmente especificados simplesmente utilizando
arquivos de configuração XML.
O arquivo de configuração do cliente correspondente é mostrado na Listagem 30.17.

LISTAGEM 30.17 Arquivo de configuração do cliente para codificação binária


1: configuration>
2: <system.runtime.remoting>
3: <application>
4: <channels>
5: <channel ref="http">
6: <clientProviders>
7: <formatter ref="binary" />
8: </clientProviders>
9: </channel>
10: </channels>
11:
12: <client>
13: <wellknown type="SharedInterfaces.ISimpleFactory, SharedPackage"
14: url="http://localhost:8099/SimpleFactory.soap" />
15: </client>
16: </application>
17: </system.runtime.remoting>
18: </configuration>

u Localize o código no CD: \Code\Chapter 30\Ex04\.

Diferenças entre codificação SOAP e binária


Vale a pena examinar as diferenças entre codificação SOAP e binária utilizando agora o
tcpTrace.
A pasta de exemplo contém dois pares de arquivo de configuração (Binary*.Con-
fig.xml e SOAP*.Config.xml).
Diferenças entre codificação SOAP e binária 767

Você pode instruir a aplicação servidor e aplicação cliente a carregar qualquer um


deles especificando o argumento de linha de comando "SOAP" ou "Binary".
Esses arquivos instruirão o cliente a enviar mensagens via HTTP utilizando a porta
8001 e o servidor a escutar nas portas 8099 como de costume.
Configure tcpTrace para utilizar as opções mostradas na Figura 30.14 e carregue as
aplicações para ver os dados trocados.
A Figura 30.15 é uma captura de tela de uma troca de dados SOAP.
Compare os valores Content-Type e Content-Length circulados com aqueles da Fi-
gura 30.16 para entender o overhead do sistema de mensagens SOAP.
A Figura 30.16 é uma captura de tela de uma troca de dados feita utilizando sistema
binário de mensagens.
Compare os valores Content-Type e Content-Length circulados com aqueles da Fi-
gura 30.15 para ver como o sistema binário de mensagens pode ajudar a economizar lar-
gura de banda.

FIGURA 30.14 A caixa de diálogo tcpTrace Settings.

FIGURA 30.15 Captura de tela da troca de dados SOAP.


768 Capítulo 30 .NET Remoting em ação

FIGURA 30.16 Captura de tela da troca de dados binários.

A diferença de tamanho entre os dois pacotes de dados (ver o cabeçalho Con-


tent-Length) é significativa: 607 bytes versus 163 para a solicitação e 2735 versus 1103 para
a resposta.
A escolha do formatador correto é crucial no desenvolvimento de aplicações .NET
eficientes. Embora o SOAP talvez seja suficientemente rápido em redes locais, seu over-
head provavelmente criará problemas ao utilizar conexões de modem.
Considerações semelhantes se aplicam a canais TCP versus para canais HTTP: TCP é
mais rápido do que o HTTP e pode fornecer uma solução melhor para sistemas de desem-
penho crítico. O HTTP se adapta melhor a sistemas em que os firewalls monitoram o trá-
fego de rede.
Em geral, é boa prática fazer seus próprios clientes .NET utilizar o sistema binário de
mensagens sobre TCP e utilizar SOAP sobre HTTP sempre que seus serviços precisarem
ser acessados a partir do Java, Win32 ou de qualquer outra aplicação não-.NET.
Como as aplicações servidor podem ter mais de um canal ativo ao mesmo tempo, na
realidade não é difícil tirar o máximo proveito dos dois mundos ao mesmo tempo.

DICA
Considerando as restrições dos Capítulos 29 e 30, não podemos desviar para tópicos mais avança-
dos. Entretanto, se quiser tornar-se mais informado sobre o assunto, você pode utilizar os seguin-
tes recursos:
MSDN Online em www.msdn.microsoft.com
Advanced .NET Remoting de Ingo Rammer, APress e seu site da Web em http://www.ingo-
rammer.com
Aplicações distribuídas Microsoft .NET: Integrating XML Web Services and .NET Remoting de Matthew
Macdonald, Microsoft Press
NESTE CAPÍTULO
CAPÍTULO 31 — Métodos de segurança do
ASP.NET

Tornando aplicações — Autenticação

— Autorização
ASP.NET seguras — Finalizando

A segurança é um tópico importante, particularmente


quando diz respeito às aplicações baseadas na Web. As
aplicações Web que não são seguras são altamente
vulneráveis ao roubo de IP, ataques de rede, corrupção de
dados e outros. Esses são somente alguns dos perigos
potenciais que podem ocorrer quando suas camadas de
segurança foram rompidas. Várias camadas de segurança
devem ser empregadas e este capítulo toca em uma
delas – isto é, tornar suas aplicações de ASP.NET seguras.

NOTA
Para obter informações sobre as várias medidas de segurança
que você pode adotar para ir além de apenas escrever aplica-
ções seguras, visite www.microsoft.com/security.

Métodos de segurança do ASP.NET


Há dois aspectos principais da segurança do ASP.NET
que deve conhecer – autenticação e autorização.
Autenticação é o processo de descobrir e verificar a
identidade e os papéis de um usuário. Por meio da
autenticação o usuário tem ou não tem acesso ao
recurso. A autorização utiliza a identidade e papéis de
um usuário para determinar a que recursos e em que
nível o usuário tem acesso.

NOTA
Para o propósito desta discussão, refiro-me ao acesso de
usuário a sistemas externos. Nós desenvolvedores temos a
tendência de escrever aplicações para nós mesmos e a segu-
rança é um recurso freqüentemente negligenciado ou adia-
do. Ao desenvolver aplicações que não vão executar somente
no seu computador, é de extrema importância que a segu-
rança seja um recurso tratado cuidadosamente durante o
projeto e implementação e não deixado de lado para quando
houver mais tempo.
770 Capítulo 31 Tornando aplicações ASP.NET seguras

Autenticação
Há três formas de autenticação no ASP.NET. Essas são a Integrated Windows Authentica-
tion, Forms Authentication e Passport Authentication. Uma quarta, que não é realmente
um modo de autenticação, é nenhuma autenticação. Este capítulo discute principalmente
os dois primeiros métodos. Ele apenas tocará no modelo de autenticação Passport.

Configurando o modelo de autenticação do ASP.NET


Você pode especificar o modelo de autenticação que a sua aplicação ASP.NET utilizará no
arquivo web.config como mostrado aqui:

<configuration>
<system.web>
<authentication mode="Windows" />
</system.web>
</configuration>

Autenticação do Windows
Antes de discutir como tornar sua aplicação segura pelo ASP.NET, agora é uma boa
hora para apresentar uma cartilha sobre o modelo de segurança, baseado em papéis, do
Windows.
No Windows, você concede a acesso de usuários ao sistema via uma identidade.
Algumas pessoas referem-se a isso como uma conta de usuário. Você tem a capacidade de
conceder o acesso de uma identidade a vários recursos dentro do sistema ou ao próprio
sistema (autenticação). Depois de o acesso ser concedido, você pode fornecer a cada
identidade capacidades variáveis (autorização). Por exemplo, uma identidade pode ter
acesso a certo diretório em um sistema, mas essa identidade pode ter somente acesso de
leitura a arquivos desse diretório. Por outro lado, outra identidade pode ter acesso de lei-
tura/gravação ao mesmo diretório. Isso representa diferentes níveis de autorização.
Em vez de conceder aos usuários capacidades dentro do sistema com base em suas
identidades, é preferível atribuir uma identidade a um papel ou a múltiplos papéis. A Fi-
gura 31.1 mostra a caixa de diálogo Users, que contém o grupo a que eles pertencem.
Há três formas de autenticação Windows – Basic, Digest e NTLM. Esta seção discute
cada uma dessas opções.
A autenticação NTLM do Windows só é válida para o Internet Explorer. Portanto, ela
só é adequada quando você pode controlar os usuários que alcançam seu site. A autenti-
cação NTLM é amplamente utilizada com intranets porque não funciona por meio de
servidores proxy.
Na autenticação Windows, quando o IIS recebe a solicitação de um cliente, ele pode
tratar a própria autenticação ou pode adiar a autenticação para o ASP.NET. Esta seção dis-
cute a primeira abordagem. Quando o IIS trata a autenticação, ele utiliza as contas de
usuário do Windows para determinar acesso de usuário.
Para configurar o site com a autenticação Windows, você deve primeiro modificar o
arquivo web.config para o site, como mostrado aqui:
Autenticação 771

<configuration>
<system.web>
<authentication mode="Windows" />
</system.web>
</configuration>

u Localize o código no CD: \Code\Chapter 31\Ex01\.

Além disso, você deve desativar o acesso anônimo ao site por meio do IIS carregan-
do a caixa de diálogo de propriedades para o seu site. Você deve selecionar a guia Direc-
tory Security. A partir daí, clique no botão Edit na seção de ferramenta Anonymous
Access and Authentication. Isso carrega a caixa de diálogo Authentication Methods
(ver Figura 31.2).
Você deve desmarcar a caixa de seleção Anonymous Access. Você também deve mar-
car a caixa de seleção Basic Authentication e desmarcar a caixa de seleção Integrated
Windows Authentication.

FIGURA 31.1 A caixa de diálogo Users.

FIGURA 31.2 Caixa de diálogo Authentication Methods.


772 Capítulo 31 Tornando aplicações ASP.NET seguras

Depois de realizar esses passos, ao tentar acessar o site Web, a caixa de diálogo mos-
trada na Figura 31.3 será apresentada.
Depois de o usuário inserir as credenciais corretas, ele recebe a permissão de visuali-
zar a página Web solicitada. As credenciais inseridas pelo usuário são comparadas com as
existentes dentro das contas de usuário mantidas no servidor que hospeda o site Web.
É isso o que realmente ocorre com a autenticação básica. Quando o navegador solici-
ta uma página a partir do IIS no servidor, o IIS responde com um código “401 Unauthori-
zed”. O navegador entende isso e apresenta ao usuário a caixa de diálogo de login mos-
trada na Figura 31.3. Se o usuário inserir as credenciais corretas, o acesso à página Web é
concedido. Se as credenciais do usuário forem incorretas, o navegador exibe novamente
a caixa de diálogo login.
Uma desvantagem da autenticação Basic é que as informações são enviadas do nave-
gador cliente para o servidor no formato de texto. Esse é um nível de segurança inaceitá-
vel em muitas circunstâncias. Uma forma alternativa de autenticação seria utilizar a au-
tenticação Digest. Essa forma é semelhante à autenticação Basic, mas tem a vantagem de
as credenciais de usuário sofrerem hash antes de serem enviadas para o servidor.
Ativar a autenticação Digest envolve marcar a caixa de seleção Digest Authentica-
tion for Windows Domain Servers na caixa de diálogo Authentication Methods mostra-
da na Figura 31.2. Essa opção será desativada se você não se conectar a um domínio.
A última forma de autenticação do Windows é a autenticação NTLM. Esse método
obtém as informações de login de usuário utilizadas quando o usuário efetuar logon no
computador. O usuário não verá uma caixa de diálogo login porque as informações da
credencial já são conhecidas. Esse método exige que tanto o usuário como o servidor
executem em sistemas operacionais Windows. Além disso, na Figura 31.2, a caixa de sele-
ção Windows Integrated deve ser marcada para ativar NTLM.

FIGURA 31.3 Caixa de diálogo que solicita o nome e senha de usuário de um site Web.

Autenticação baseada em formulários


Agora que expliquei a autenticação baseada no Windows, posso dizer que autenticação
baseada no Windows é realistamente complicada para qualquer aplicação Web no nível
de produção real. Primeiro, ela impõe ao usuário requisitos sobre como utilizar o Inter-
Autenticação 773

net Explorer no caso de NTLM e/ou de estar em um computador com o Windows como o
sistema operacional. O problema mais evidente com a autenticação baseada em IIS é que
a verificação do lado do servidor é baseada em contas de usuário do Windows. Embora
possa funcionar bem em algumas implantações simples, uma aplicação Web real requer
mais controle sobre a segurança e menos restrições em ambientes do usuário. O ASP.NET
traz a autenticação baseada em formulários para abordar isso.
Uma vantagem da autenticação baseada em formulários (Forms-based) é que ela for-
nece a você, o desenvolvedor, a capacidade de personalizar o formulário utilizado para
recuperar as credenciais do usuário. Ela também fornece a flexibilidade sobre como au-
tenticar o usuário em relação ao local em que as credenciais de usuários são armazenadas
e ao tipo de modelo de segurança a ser utilizado (usuário/papel, por exemplo).
Para que a autenticação baseada em formulário seja efetiva, você deve autenticar o
usuário pelas credenciais em cada página de suas aplicações Web. Isso não significa que você
deva pedir as credenciais do usuário em todas as páginas. Significa que você utiliza as cre-
denciais já inseridas pelo usuário em um formulário de login. Idealmente, se por alguma ra-
zão o usuário tentar acessar uma página e as credenciais de login forem incorretas ou ausen-
tes, o usuário deve ser redirecionado de volta para o formulário de login em que ele pode re-
inserir o nome e senha de usuário (os típicos elementos das credenciais de um usuário).
Quando o usuário insere suas informações de login, essas informações não são ape-
nas utilizadas para validar o usuário com as credenciais conhecidas/armazenadas, mas
também são retornadas de volta para o usuário como um cookie. Esse cookie é então uti-
lizado para validar as credenciais do usuário em páginas subseqüentes da aplicação Web.
Se o navegador do usuário tiver cookies desativados, Viewstate será utilizada. Vejamos
um exemplo simples desse modelo.

Entradas em web.config
Primeiro, é necessário colocar as entradas adequadas no arquivo web.config. As configura-
ções são inseridas na seção <authentication> do arquivo web.config como mostrado aqui:
<authentication mode="Forms">
<forms
name="logincookie"
loginUrl="LoginForm.aspx"
timeout="30">
</forms>
</authentication>

A Tabela 31.1 lista os vários atributos que podem aparecer na seção <forms>.

TABELA 31.1 Atributos da seção <forms>


Atributo Descrição
name Nome do cookie a ser utilizado para autenticação. Isso assume padrão de
.ASPXAUTH.
loginUrl A URL para a qual uma solicitação é redirecionada quando nenhum cookie de
autenticação é localizado. Isso assume padrão de default.aspx.
774 Capítulo 31 Tornando aplicações ASP.NET seguras

TABELA 31.1 Continuação


Atributo Descrição
protection Especifica o tipo de encriptação a ser utilizada para o cookie de autenticação.
Valores válidos são All, None, Encryption e Validation.
timeout Especifica o período de tempo em minutos depois que a autenticação cookie
expira.
path Especifica um caminho para cookies emitidos pela aplicação ASP.NET.
requireSSL Especifica se o SSL é necessário para a transmissão do cookie.
slidingExpiration Quando verdadeira, a expiração do cookie é reinicializada em cada solicitação.

Autenticando páginas individuais


Relembrando, mais uma vez, a idéia ao autenticar uma aplicação é que a autenticação de
usuário seja verificada a cada solicitação em todas as páginas. Se autenticado, o usuário
tem permissão de acesso à página solicitada. Caso contrário, o usuário é redirecionado à
caixa de diálogo de login. A seguir é apresentado um exemplo simples do código que
pode aparecer no evento Load de cada Web Form:

procedure TWebForm2.Page_Load(sender: System.Object;


e: System.EventArgs);
begin
if not User.Identity.IsAuthenticated then
Response.Redirect('LoginForm.aspx');
end;

As informações de um usuário autenticado são representadas pela interface IIden-


tity, implementada pela classe FormsIdentity. O código anterior ilustra como acessar essa
classe pela propriedade User.Identity. Uma das propriedades dessa classe é IsAuthentica-
ted, que retorna um valor booleano que representa se um usuário foi autenticado ou não.
Você executaria o código semelhante a esse em cada página da aplicação ASP.NET. A Lis-
tagem 31.1 mostra um exemplo mais extenso que exibe informações sobre o usuário a
partir da classe FormsIdentity.

LISTAGEM 31.1 Verificando a autenticação de usuário


1: procedure TWebForm2.Page_Load(sender: System.Object;
2: e: System.EventArgs);
3: var
4: formsId: FormsIdentity;
5: begin
6: if not User.Identity.IsAuthenticated then
7: Response.Redirect('LoginForm.aspx')
8: else
9: begin
10: Response.Write('Welcome: '+User.Identity.Name+'<br>');
11: Response.Write('Authentication Type: '+
12: User.Identity.AuthenticationType+'<br>');
Autenticação 775

LISTAGEM 31.1 Continuação


13: formsId := FormsIdentity(User.Identity);
14: Response.Write('Cookie Path: '+ formsId.Ticket.CookiePath+'<br>');
15: Response.Write('Expiration: '+
16: Convert.ToString(formsID.Ticket.Expiration)+'<br>');
17: end;
18: end;

u Localize o código no CD: \Code\Chapter 31\Ex02\.

A Listagem 31.1 mostra o uso de algumas propriedades da classe FormsIdentity. Uma


dessas propriedades é a propriedade Ticket, que representa o bilhete de autenticação. Essa
classe é a classe FormsAuthentication. Basicamente ela representa o cookie que é criado
quando o usuário efetua logon.
Agora que você entende como verificar a autenticação de usuário em cada página,
vejamos realmente como autenticar o usuário.
Em geral, isso é tratado por um formulário de login. O seguinte código é uma respos-
ta para um botão Login. O formulário utilizado aqui tem dois controles TextBox em que o
usuário insere a combinação de nome de usuário e senha.
procedure TLoginForm.btnLogin_Click(sender: System.Object;
e: System.EventArgs);
begin
if UserLoginValid(txbxUserName.Text, txbxPassword.Text) then
FormsAuthentication.RedirectFromLoginPage(txbxUserName.Text, False);
end;

u Localize o código no CD: \Code\Chapter 31\Ex02\.

No código anterior, a função UserLoginValid( ) é um método que escrevi que executa


o código para autenticar o usuário. Ele retorna um valor booleano baseado no sucesso ou
não da autenticação do usuário. O seguinte exemplo é apenas um marcador de lugar uti-
lizado para demonstrar a lógica de execução:
function TLoginForm.UserLoginValid(aUserName, aPassword: &String): Boolean;
begin
Result := (aUserName = 'Elvis') and (aPassword = 'Presley');
end;

Mostrarei exemplos realistas dessa função. Certamente, você pode simplesmente co-
locar o mesmo código no handler de evento btnLogin_Click( ). Entretanto, tê-lo em um
método separado permite demonstrar as várias possibilidades de autenticar usuários uti-
lizando esse mesmo exemplo.
A classe FormsAuthentication fornece métodos auxiliares estáticos para trabalhar com
o bilhete de autenticação. Um desses métodos é RedirectFormLoginPage( ). Esse método re-
direciona um usuário autenticado de volta para página em que ele tinha originalmente
feito a solicitação. Esse método tem duas versões sobrecarregadas. A versão mostrada
aqui passa o nome de usuário como o primeiro parâmetro, que é armazenado no bilhete
776 Capítulo 31 Tornando aplicações ASP.NET seguras

de autenticação. A Listagem 31.1 exibe essas informações a partir do objeto de usuário. O


segundo parâmetro determina se cria ou não um cookie que persiste por meio das sessões
de navegador. Uma segunda forma do método RedirectFormLoginPage( ) adota um terceiro
parâmetro de string que especifica o caminho do cookie.

Utilizando a seção <credentials>


Continuando no exemplo fornecido anteriormente, ilustrarei como você pode armaze-
nar definições de nome de usuário e senha no arquivo web.config. Demonstrarei também
uma versão da função UserLoginValid( ) que valida um usuário com base nas entradas
contidas dentro de web.config.
Você pode modificar o arquivo web.config para armazenar as combinações de nome
de usuário e senha, fazendo isso dentro da seção <credentials>, como ilustrado aqui:
<authentication mode="Forms">
<forms
name="AuthCred" loginUrl="LoginForm.aspx" timeout="30">
<credentials passwordFormat="Clear">
<user name="Bobby" password="87pizza"/>
<user name="Marsha" password=" bi45rd"/>
<user name="Greg" password="Greg"/>
</credentials>
</forms>
</authentication>

O elemento <credentials> contém um único atributo, passwordFormat. Esse elemento espe-


cifica o formato de encriptação para as senhas armazenadas. Os formatos de suporte são:
— Clear – Nenhuma encriptação realizada em senhas

— MD5 – Senhas encriptadas com o algoritmo de hash MD5


— SHA1 – Senhas encriptadas com o algoritmo de hash SHA1

O subtag <user> da seção <credentials> define os nomes de usuário e senhas dos usuá-
rios que devem receber acesso ao sistema. Se eu especificasse um esquema de encriptação
para o atributo passwordFormat da seção <credentials>, as senhas teriam de estar em seu for-
mato encriptado.

DICA
O método FormsAuthentication.HashPasswordForStoringInConfigFile( ) pode ser utilizado
para produzir senhas de hash.

NOTA
A idéia de armazenar usuários no arquivo web.config é conveniente e útil para demonstrar as ca-
pacidades do ASP.NET. Entretanto, é altamente complicado armazená-los aqui, exceto com algu-
mas implantações simples. Você realmente não quer que os administradores editem os arquivos
web.config sempre que você quiser adicionar/remover um usuário do sistema. Existem aborda-
gens melhores que são ilustradas neste capítulo.
Autenticação 777

Depois que os usuários estão definidos no arquivo web.config, tudo que é necessário é
reescrever o método UserLoginValid( ) do último exemplo. Essa é uma instrução de uma
linha:

function TWebForm1.UserLoginValid(aUserName, aPassword: &String): Boolean;


begin
Result := FormsAuthentication.Authenticate(aUserName, aPassword);
end;

u Localize o código no CD: \Code\Chapter 31\Ex03\.

A função FormsAuthentication.Authenticate( ) autentica convenientemente as strings


do nome de usuário e da senha inseridas pelo usuário contra aquelas mantidas no arqui-
vo web.config.

Utilizando um arquivo XML para hospedar credenciais


Outra área em que você pode armazenar informações de usuário é dentro de um arquivo
XML separado. Considerando o arquivo .xml, mostrado aqui,

<root>
<user>
<username>mary</username>
<password>marypassword</password>
<user>
<username>joe</username>
<password>joepassword</password>
</user>
</root>

podemos mudar a função UserLoginValid( ) para esta mostrada na Listagem 31.2.

LISTAGEM 31.2 UserLoginValid( ) com um arquivo XML


1: function TLoginForm.UserLoginValid(aUserName, aPassword: String): Boolean;
2: var
3: dsUsers: DataSet;
4: fsUsers: FileStream;
5: srUsers: StreamReader;
6: drArray: array of DataRow;
7: pwString: String;
8: begin
9: fsUsers := FileStream.Create(Server.MapPath('users.xml'),
10: FileMode.Open, FileAccess.Read);
11: try
12: dsUsers := DataSet.Create;
13: srUsers := StreamReader.Create(fsUsers);
14: dsUsers.ReadXml(srUsers);
15: drArray := dsusers.Tables[0].Select(
778 Capítulo 31 Tornando aplicações ASP.NET seguras

LISTAGEM 31.2 Continuação


16: System.String.Format('username=''{0}''', aUserName));
17:
18: if (System.Array(drArray).Length > 0) then
19: begin
20: pwString := String(drArray[0]['password']);
21: Result := System.String.Compare(pwString, aPassword) = 0;
22: end
23: else
24: Result := False;
25:
26: if Result = False then
27: Label4.Text := 'Invalid username or password.';
28:
29: finally
30: fsUsers.Close;
31: end;
32: end;

u Localize o código no CD: \Code\Chapter 31\Ex04\.

Nesse exemplo o método carrega o arquivo .xml em uma stream e em seguida o lê em


um DataSet (linha 14). O usuário é localizado invocando o método Select( ) na tabela
dentro do DataSet. Se uma linha é localizada, a string de senha é extraída e é comparada
com aquela que o usuário inseriu (linhas 20–21).
Essa técnica tem as mesmas desvantagens do armazenamento das combinações de
nome de usuário/senha no arquivo web.config. Ela requer que o administrador gerencie a
entrada dos usuários e as senhas nesse arquivo de texto. Isso se torna especialmente pro-
blemático se as senhas armazenadas estiverem em algum tipo de formato encriptado
como o MD5. Certamente, alguém poderia desenvolver uma ferramenta que pudesse
tornar essa entrada de dados mais fácil e gerar a forma encriptada da senha.

Utilizando o SQL Server para hospedar credenciais


Uma abordagem preferida para autenticar usuários é armazenar nomes de usuário e se-
nhas dentro de um banco de dados. A função UserLoginValid( ) a seguir demonstra como
isso pode ser feito. Ela também demonstra a utilização de uma das classes a partir do na-
mespace System.Security.Cryptography para encriptar a senha do usuário utilizando o algo-
ritmo de hash MD5. Esse exemplo assume a existência de um banco de dados no SQL Ser-
ver chamado ddg_users com uma tabela gerada a partir do seguinte script:

CREATE TABLE ddgusers (


user_name VARCHAR(40),
password VARCHAR(100)
)
GO

Você localizará os scripts create.sql e populate.sql no exemplo no CD. A Listagem


31.3 ilustra essa técnica.
Autenticação 779

LISTAGEM 31.3 UserLoginValid( ) com SQL Server


1: function BytesToHex(hash: array of Byte): String;
2: var
3: sb: StringBuilder;
4: i: integer;
5: begin
6: sb := StringBuilder.Create(Length(Hash)*2);
7: for i := Low(hash) to High(hash) do
8: sb.Append(hash[i].ToSTring('X2'));
9: Result := sb.ToString.ToLower;
10: end;
11:
12: function TLoginForm.UserLoginValid(aUserName, aPassword: String): Boolean;
13: const
14: cn = 'server=XWING;database=ddg_users;Trusted_Connection=Yes';
15: sel = 'select password from ddgusers where user_name = ''{0}''';
16: var
17: sqlCn: SqlConnection;
18: sqlCmd: SqlCommand;
19: dbPwStr: String;
20: selStr: String;
21:
22: encoder: UTF8Encoding;
23: md5Hasher: MD5CryptoServiceProvider;
24: HashedBytes: array of byte;
25: begin
26: sqlCn := SqlConnection.Create(cn);
27: selStr := System.String.Format(sel, aUserName);
28: sqlCmd := SqlCommand.Create(selStr, sqlCn);
29:
30: sqlCn.Open;
31: try
32: dbPwStr := sqlCmd.ExecuteScalar as System.String;
33: finally
34: sqlCn.Close;
35: end;
36: encoder := UTF8Encoding.Create;
37: md5Hasher := MD5CryptoServiceProvider.Create;
38:
39: // Faz um hash da senha passada.
40: HashedBytes := md5Hasher.ComputeHash(encoder.GetBytes(aPassword));
41: aPassword := BytesToHex(HashedBytes);
42:
43: Result := System.String.Compare(dbPWStr, aPassword) = 0;
44: end;

u Localize o código no CD: \Code\Chapter 31\Ex05\.


780 Capítulo 31 Tornando aplicações ASP.NET seguras

As linhas 1–10 são uma função auxiliar que converte um array de bytes em uma
string hexadecimal. Você verá em breve onde isso entra em cena. Nessa versão da função
UserLoginValid( ) há duas partes fundamentais. As linhas 26–35 envolvem extrair a senha
do usuário do banco de dados. Assume-se que essa senha deva estar encriptada com o al-
goritmo MD5.
As linhas 36–41 envolvem a utilização da classe MD5CryptoServiceProvider para fazer
hash da senha que foi passada pelos usuários. A classe retorna um array de bytes que re-
presenta a senha de hash. O banco de dados armazena os bytes como uma string em for-
ma hexadecimal. A fim de converter o array de origem de bytes em uma string hexadeci-
mal, o método utiliza a função BytesToHex( ) auxiliar.
Certamente existem outras maneiras de realizar a autenticação. Essas mostradas
aqui são as maneiras comuns que você encontrará.

Autenticação do Passport
A Microsoft tem seu próprio mecanismo de autenticação centralizado chamado Pass-
port. Esse serviço permite que os participantes se inscrevam na iniciativa Passaport.
Quando os usuários efetuarem login em um dos sites participantes, eles são autenticados
em outros participantes do Passport.
Além das informações de autenticação, a Microsoft hospeda certas informações so-
bre você que seriam úteis em outros sites participantes. A idéia é dar a você uma expe-
riência mais simples de como navegar entre sites participantes sem a necessidade de
reautenticar e inserir informações de usuário.
O passaporte utiliza tecnologias de segurança como encriptação SSL e Triple DES
para gerenciar os bilhetes de autenticação do usuário.
Para ativar o suporte de passaporte da sua aplicação, o uso do Passport SDK é neces-
sário. O .NET suporta completamente o passaporte por meio do PassportAuthenticationMo-
dule e é ativado com a seguinte entrada no arquivo web.config:

<configuration>
<system.web>
<authentication mode="Passport"/>
</system.web>
</configuration>

O passaporte opera de uma maneira relativamente simples e direta. Um usuário faz


uma solicitação de um site compatível com o sistema Passport. Esse site determina se o
usuário tem um bilhete Passport válido. Se tiver, o usuário tem permissão de acesso ao
site. Se não tiver, um código de status de 302 é retornado e o usuário é redirecionado ao
serviço de login do Passport. Esse serviço, por meio de SSL, recupera informações sobre o
usuário, autentica esse usuário e o redireciona de volta para o site de origem se a autenti-
cação for bem-sucedida.

NOTA
O download da última versão do Passport SDK pode ser feito em http://www.micro-
soft.com/net/services/passport/.
Autorização 781

Você deve estar ciente de que toda a iniciativa Passport tem sido controversa. Muitos
são céticos quanto à confiabilidade e quais são as intenções da Microsoft em relação às
suas informações pessoais. Faça uma pesquisa simples utilizando os termos “Micro-
soft+Passport+controversy” no Google. Você encontrará um artigo que discute falhas
técnicas em vários tipos de teorias da conspiração. Assim como pode ser controversa, é
uma tecnologia muito fascinante, com muitos participantes.

Autorização
A autenticação identifica um usuário. A autorização determina o nível de acesso que um
usuário tem a um recurso ou a conjunto de recursos. Discuto quatro formas de autoriza-
ção nas seções a seguir.

Autorização de arquivo
A autenticação do arquivo é baseada nas contas do Windows, NTFS e listas de controle de
acesso (Access Control Lists – ACLs). Ela fornece acesso a usuários de um sistema pela
guia Security da caixa de diálogo Folder Properties. Você pode acessar essa caixa de diálo-
go clicando com o botão direito do mouse em qualquer pasta e selecionando Properties.
A Figura 31.4 mostra essa forma.
Quando o usuário tentar acessar um recurso que reside em uma pasta a qual não tem
nenhum acesso, aparece uma mensagem de erro: 403.2 Access Denied.
Essa técnica está em ambientes contidos em que a segurança de arquivo de rede é su-
ficiente para controlar acesso à segurança de recursos. Talvez você localize uma aplicação
de intranet nesse cenário. A desvantagem dessa técnica é que gerenciar ACLs é incômodo
e requer muita administração.

FIGURA 31.4 A guia Security da caixa de diálogo Folder Properties.


782 Capítulo 31 Tornando aplicações ASP.NET seguras

Autorização de URL – a seção <authorization>


A autenticação de URL é controlada pelo arquivo web.config. Portanto, ela controla acesso
à aplicação Web com base na maneira como as páginas são definidas com a estrutura de
diretório. O acesso do cliente em cada nível é controlado via seção <authorization>. O có-
digo a seguir é um exemplo de como pode ser a seção:

<configuration>
<system.web>
<authorization>
<allow users="bob, martial, gabriel, robin, amanda"/>
<deny users="sam orie, blake, jones"/>
</authorization>
</system.web>
</configuration>

Dentro da seção <authorization> estão as subtags <allow> e <deny>. A subtag <allow> su-
porta uma lista, separada por vírgulas de nomes de usuário, papéis ou verbos, que especi-
fica quem tem acesso ao sistema governado por esse arquivo web.config. A subtag <deny>
suporta uma lista semelhante, mas especifica quem não tem acesso aos recursos governa-
dos por esse arquivo web.config. Portanto, nesse exemplo, os usuários bob, martial e assim
por diante têm acesso a todos os diretórios governados por esse arquivo web.config, desde
que nenhum arquivo web.config de nível inferior sobrescreva essas configurações.
Observe que as duas subtags também podem conter nomes de papel, como mostra-
do aqui:

<authorization>
<allow roles="manager, admins, technicians"/>
<deny roles="executives, employees, contractors"/>
</authorization>

Há duas identidades adicionais que aparecem dentro das duas subtags – um ponto
de interrogação (?) e um asterisco (*). O ponto de interrogação significa os usuários anô-
nimos. O asterisco significa todos os usuários. Por exemplo, considere a seguinte seção
<authorization>:

<authorization>
<allow users="?">
</authorization>

Essa configuração especifica que os usuários anônimos têm permissão de acessar os re-
cursos governados por esse arquivo web.config. O seguinte,

<authorization>
<deny users="*">
</authorization>

especifica que todos os usuários não têm acesso ao recurso.


Autorização 783

Você também pode controlar os métodos HTTP de acesso aos recursos pela seção
<authorization>. Basicamente, ele utiliza o atributo verbs seguido por uma lista delimitada
por vírgulas de métodos HTTP. Verbos válidos são mostrados no seguinte exemplo:

<authorization>
<allow verbs="GET, HEAD, POST, DEBUG">
</authorization>

Autorização baseada em papéis


Um modelo de autorização baseado em papel daria aos usuários permissão de entrar no
sistema mas limitaria o acesso de modo que as páginas que eles visualizam diferissem
com base em seu papel.
A maneira como os papéis são armazenados é realmente uma questão de requisitos
de sistema. Uma abordagem típica é armazenar usuários e papéis em um banco de dados.
A idéia é que a aplicação ASP.NET recupere essas informações do banco de dados e modi-
fique as páginas com base nos papéis de um usuário.
Examinaremos um exemplo simples que realiza esse tipo de lógica. Utilizaremos o
arquivo users.xml do exemplo anterior para armazenar usuários. A Listagem 31.2 ilustra a
rotina para validar usuários. Depois da validação dos usuários, a página solicitada por
eles será alterada com base no papel deles. A página padrão é um formulário contendo
quatro botões, como mostrado na Figura 31.5.
A Listagem 31.4 mostra o handler de evento Page_Load( ) para essa página.

FIGURA 31.5 Página padrão do exemplo Role.


784 Capítulo 31 Tornando aplicações ASP.NET seguras

LISTAGEM 31.4 Page_Load( ), que verifica o papel


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: begin
3: if not User.Identity.IsAuthenticated then
4: Response.Redirect('LoginPage.aspx')
5: else begin
6: btnBoss.Visible := User.IsInRole('Boss');
7: btnEmp.Visible := User.IsInRole('Employee');
8: btnAsst.Visible := User.IsInRole('Asst');
9: btnPub.Visible := User.IsInRole('Public');
10: end;
11: end;

u Localize o código no CD: \Code\Chapter 31\Ex06\.

Depois de determinar se o usuário foi autenticado (linha 3), a Listagem 31.4 então a-
valia a que papéis o usuário pertence invocando a função User.IsInRole( ) (linhas 6–9). A
função IsInRole( ) é implementada pela classe GenericPrincipal, User. A classe GenericPrin-
cipal representa o contexto de segurança do usuário para quem a aplicação de ASP.NET
está executando. WindowsPrincipal é outra classe que implementa IPrincipal. Você poderia
trabalhar com uma classe GenericPrincipal quando quisesse criar a autorização lógica que
é independente da segurança de domínio do Windows. A Listagem 31.3 é capaz de deter-
minar a que papéis o usuário pertence. Para adicionar os papéis do usuário à classe Gene-
ricPrincipal, você deve modificar a classe TGlobal no arquivo Globals.pas. Especificamente,
você quer modificar o handler de evento Applications.AuthenticateRequest. A Listagem
31.5 mostra um exemplo de como é esse código.

LISTAGEM 31.5 Evento AuthenticateRequest


1: procedure TGlobal.Application_AuthenticateRequest(sender: System.Object;
2: e: EventArgs);
3: var
4: ra: TRoleArray;
5: begin
6: if Request.IsAuthenticated then
7: begin
8: ra := GetUserRoles(User.Identity.Name);
9: if ra < > nil then
10: Context.User := GenericPrincipal.Create(User.Identity, ra);
11: end;
12: end;

u Localize o código no CD: \Code\Chapter 31\Ex06\.

Esse código simplesmente preenche um TRoleArray com os papéis aos quais o usuário
pertence. TRoleArray é simplesmente um array de String. Ele é definido como

TRoleArray = array of String;


Autorização 785

Quando o array é preenchido a partir da função GetUserRoles( ) (linha 8), uma nova
classe GenericPrincipal, que é criada, contém esses novos papéis associados com o usuário.
GetUserRoles( ) é uma função que tem gravado diretamente no código o array de usuári-
os. Ela é mostrada na Listagem 31.6.

LISTAGEM 31.6 Função GetUserRoles( )


1: function TGlobal.GetUserRoles(aUserName: &String): TRoleArray;
2: begin
3: if aUserName = 'mary' then
4: Result := TRoleArray.Create('Boss', 'Public')
5: else if aUserName = 'joe' then
6: Result := TRoleArray.Create('Employee', 'Public')
7: else if aUserName = 'mike' then
8: Result := TRoleArray.Create('Asst', 'Public', 'Boss')
9: else
10: Result := nil;
11: end;

u Localize o código no CD: \Code\Chapter 31\Ex06\.

GetUserRoles( ) codifica manualmente os papéis para usuários específicos. Na prática,


você recuperaria esses papéis a partir de um banco de dados ou de algum outro armaze-
namento externo.
Ao executar a aplicação, o Web Form, com base nos papéis do usuário, conterá dife-
rentes controles.

Personificação
Quando o ASP.NET executa aplicações, ele faz isso utilizando a identidade "ASPNET" no
Windows 2000 e Windows XP ou "NETWORK SERVICE" no Windows 2003 Server. Para aplica-
ções não-ASP.NET (arquivos .html ou scripts ISAPI/CGI) a conta "IUSR_MACHINENAME" é utili-
zada. O nível de segurança para essa conta particular é um nível que tem acesso limitado
a arquivos e pastas necessário para permitir que o ASP.NET execute adequadamente.
Espera-se que os desenvolvedores de aplicação utilizem alguma forma de autentica-
ção para proibir acesso a vários recursos.
A personificação permite que o ASP.NET execute a aplicação sob a identidade da pes-
soa para quem a aplicação está executando. Isso permite que o ASP.NET baseie o nível de
acesso na ACL do usuário em cujo nome a aplicação está executando. Isso conta com
permissões no nível de NTFS concedidas à conta de usuário personificado. Examine o có-
digo a seguir:

procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);


var
fAry: array of String;
i: integer;
begin
786 Capítulo 31 Tornando aplicações ASP.NET seguras

Response.Write('hello');
fAry := Directory.GetFiles(MapPath('\'), '*.*');
for i := Low(fAry) to High(fAry) do
Response.Write(fAry[i]+'<br>');
end;

u Localize o código no CD: \Code\Chapter 31\Ex07\.

Esse código funcionará bem com a personificação desativada. Isso ocorre por-
que a identidade do ASP.NET padrão terá acesso ao diretório-raiz do IIS (em geral
c:\inetpub\wwwwroot). Entretanto, quando você fizer a seguinte alteração no arquivo
web.config,

<configuration>
<system.web>
<identity impersonate="true"/>
<\system.web>
<\configuration>

a lista de arquivos só será exibida se o usuário tiver acesso ao diretório-raiz do IIS.


A personificação é boa se sua aplicação estiver funcionando em um cenário de intra-
net controlada. Ela não é preferida se o acesso de usuário for controlado por ACLs e coi-
sas do tipo, porque isso pode representar bastante trabalho de administração para uma
aplicação complexa.

Finalizando
Utilizando a autenticação de formulários você pode permitir que o usuário faça o sign off
da aplicação invocando o método FormsAuthentication.SignOut como mostrado aqui:

FormsAuthentication.SignOut;
Server.Transfer.("LoginPage.aspx");
NESTE CAPÍTULO
CAPÍTULO 32 — Distribuindo aplicações
ASP.NET

Distribuição — Ajustes de configuração

— Dicas de configuração
e configuração — Adicionando/obtendo

do ASP.NET definições personalizadas de


configuração

Uma aplicação ASP.NET ou Web Service tem pouca


utilidade para qualquer pessoa até você compilá-la e
implantá-la. Naturalmente, não quis subestimar a
gratificação imediata que os desenvolvedores obtêm de
simplesmente compilar e ver suas aplicações em
execução – mesmo em um ambiente de teste. Além
disso, as aplicações são escritas para o usuário final;
portanto, elas devem ser distribuídas quando estiverem
prontas para a produção. Este capítulo discute uma
abordagem de distribuição simples. Ele também discute
opções de configuração que você provavelmente
precisará modificar em um ambiente de produção.

Distribuindo aplicações ASP.NET


Em muitas situações, a distribuição de uma aplicação
ASP.NET é suficientemente simples para utilizar o
comando XCOPY para mudar da localização de
desenvolvimento para a localização de distribuição.
Entretanto, a maioria das aplicações que são
suficientemente complexas requer um programa de
instalação/configuração. Por causa das complexidades
variáveis das últimas aplicações, não introduzirei as
muitas maneiras de configurá-las. Direi que há
ferramentas que podem ser utilizadas para simplificar o
processo de criação de aplicações de configuração
robustas. Uma dessas é o Windows Installer.

Considerações simples sobre distribuição


Quando você cria uma aplicação ASP.NET no Delphi for
.NET, ele cria automaticamente um diretório virtual sob
o IIS (se você optar pelo IIS Server). A configuração
788 Capítulo 32 Distribuição e configuração do ASP.NET

criada aqui é mais provável no layout que você implantará. A estrutura de diretório é tal
que sua aplicação reside em um diretório virtual IIS. Esses diretórios virtuais referem-se a
um diretório físico em algum lugar no sistema. Para manter as coisas simples, descreverei
a configuração na minha máquina, um caso relativamente simples e comum.
A Figura 32.1 mostra o console de gerenciamento dos meus diretórios do IIS.
Você vê que há vários diretórios virtuais como MyFirstWebService e MySecondWebService.
Esses foram criados automaticamente no Capítulo 28, quando demonstrei a criação de
Web Services.
Esses diretórios virtuais referem-se a um diretório físico. Você vê isso ao clicar com o
botão direito do mouse em um diretório virtual e ao selecionar Properties. Isso abre a cai-
xa de diálogo Properties do diretório virtual (ver Figura 32.2).
Preciso indicar alguns itens aqui. Primeiro, note os botões de opção na parte superior
da caixa de diálogo. Por meio desses botões de opção, você especifica a localização física
como sendo uma de três origens; ela é local na sua máquina, está em um compartilha-
mento de rede ou é localizada a partir de outra URL. Mudar de opção muda o conteúdo
nas outras partes dessa caixa de diálogo (ver Figura 32.3).
De volta na Figura 32.2, você verá uma caixa de texto rotulada Local Path. Isso espe-
cifica o diretório físico em que reside sua aplicação ASP.NET. Também indicarei alguns
outros itens nessa caixa de diálogo. As várias caixas de seleção permitem especificar cer-
tos direitos de acesso nesse diretório Web. Você vai querer certificar-se de que apenas
Read está marcada. Fazer de outro modo expõe seu diretório perigosamente a potenciais
ataques externos. Além disso, é recomendável certificar-se de que a caixa drop-down
Execute Permissions exibe Scripts only. As outras duas opções são None e Scripts and
Executables. None impediria que qualquer script executasse no site. Em outras palavras,
somente as páginas HTML estáticas poderiam ser vistas. Scripts e Executables também

FIGURA 32.1 Console de gerenciamento do IIS.


Distribuindo aplicações ASP.NET 789

FIGURA 32.2 Caixa de diálogo Virtual Directory Properties.

FIGURA 32.3 Um diretório virtual baseado em uma URL de redirecionamento.

poderiam estar abertos para o mundo externo. Por exemplo, poderia ser desastroso se
você acidentalmente desse acesso de gravação e execução ao seu diretório. Alguém pode-
ria não só gravar um arquivo no diretório, mas poderia também executá-lo.
Quando um usuário solicita uma página de seu site com um URL como,

www.xapware.com/AF/WebForm1.aspx

o IIS converte o alias AFWeb na localização física na máquina.


790 Capítulo 32 Distribuição e configuração do ASP.NET

NOTA
Embora este capítulo descreva o layout de distribuição no contexto de IIS, já existem outros Web
Servers que têm suas próprias especificidades de configuração, como o Cassani Web Server. Você
precisará entender como esses operam. Por exemplo, o Capítulo 8 discute a distribuição de uma
aplicação ASP.NET sob Mono e Apache, que executa em um ambiente Linux. Isso é evidentemen-
te diferente do que é discutido aqui.

Essa localização física será o diretório em que a aplicação ASP.NET residirá. A locali-
zação a que o alias se refere é considerada a raiz da sua aplicação.

NOTA
Em termos simplistas, a noção de uma aplicação que reside em um único diretório é suficiente
para essa discussão. Na realidade, uma aplicação ASP.NET pode ser composta de numerosos sub-
componentes que em si mesmos já são aplicações completas com todos os recursos que residem
em sua própria área. Isso é certamente verdadeiro sobre as aplicações que são compostas de Web
Services.

Considere, por exemplo, a seguinte estrutura de diretório do alias AFWeb:

\AFWeb
\bin
\images

Essa estrutura particular é simples. Ela contém somente um diretório \bin e um dire-
tório \images.
O diretório \bin é a localização em que o módulo code-behind compilado reside.
Você pode ter outros diretórios como parte de sua estrutura de diretório.
Por exemplo, ao configurar uma aplicação ASP.NET para distribuição, você talvez te-
nha diretórios específicos dentro da raiz que contêm vários arquivos que você precisa na
aplicação. O subdiretório \images aqui contêm as imagens para essa aplicação. É inteira-
mente possível que duas aplicações diferentes referenciem as mesmas imagens dentro de
suas páginas. Nesse cenário, em vez de replicar o diretório \images, você colocaria um di-
retório comum sob o IIS Web Server e tornaria essas imagens acessíveis a outras aplicações.
O que é importante é que você referencie essas imagens apropriadamente dentro dos ar-
quivos .aspx, como mostrado aqui:

<asp:image id=Image1
style="Z-INDEX: 1; LEFT: 6px; POSITION: absolute; TOP: 6px"
runat="server" imageurl="./images/ActiveFocusSplash.jpg">
</asp:image>

Note o uso da notação ponto-barra de a HTML referenciar o diretório apropriado,


que nesse caso é relacional. Quando a página for executada, a imagem é exibida correta-
mente, como mostrado na Figura 32.4.
O diretório base de sua aplicação ASP.NET é o ponto inicial a partir do qual você
expande para suportar outras necessidades. Dependendo da extensão dessa aplicação,
Distribuindo aplicações ASP.NET 791

você pode distribuí-la com um XCOPY simples. Outras exigirão uma rotina de instalação.
Por exemplo, talvez você distribua uma aplicação que instala um servidor de bancos de
dados.

FIGURA 32.4 Imagem exibida a partir de um diretório relativo.

Instalação por XCOPY


A idéia geral da distribuição XCOPY não é apenas para aplicações ASP.NET. Ela refere-se à
maneira como você deve ser capaz de instalar todas as aplicações .NET. Antes do .NET,
qualquer pessoa que tivesse de escrever um programa de instalação conhecia a comple-
xidade e o transtorno envolvidos apenas na introdução. Na sua maior parte, a instala-
ção é um pesadelo de configuração. A Microsoft tratou disso na maneira como as apli-
cações .NET são projetadas. As aplicações .NET, sendo capazes de autodescrever-se, só
precisam residir em uma área a partir da qual possam ser executadas. Elas mantêm in-
formações sobre elas próprias dentro de seus metadados ou em arquivos de configura-
ção que residem no mesmo diretório em que elas residem. É como voltar para o arquivo
.ini, mas melhor.
Para ilustrar isso você seria capaz de distribuir uma aplicação ASP.NET para uma má-
quina remota, inserindo o seguinte comando XCOPY dentro do console:

[c:\]XCOPY c:\Inetpub\wwwroot\AFWeb
\\ProductionBox\c\inetpub\wwwroot\afweb /E /K /R /H /I /Y

As opções XCOPY utilizadas são descritas a seguir:


/E copia todos os subdiretórios, mesmo os vazios
792 Capítulo 32 Distribuição e configuração do ASP.NET

/K copia atributos
/R sobrescreve arquivos de leitura
/H copia arquivos de sistema e ocultos
/I assume que o destino é diretório
/Y sobrescreve os arquivos sem solicitar confirmação

Pronto! Nesse ponto posso digitar o seguinte em meu navegador:

http://ProductionBox/AFWeb/

E verei a página mostrada na Figura 32.4.


Os capítulos restantes discutem as várias maneiras de configurar a aplicação
ASP.NET.

Ajustes de configuração
Há duas áreas com as quais você precisa se envolver na configuração. Primeiro, você deve
entender as configurações de extensão de servidor para que possa saber se o servidor
pode suportar adequadamente sua aplicação. Segundo, você precisa entender como con-
figurar adequadamente sua própria aplicação. Isso é feito alterando configurações em
dois arquivos em formato XML – machine.config e web.config.
A estrutura de configuração no ASP.NET é hierárquica. Primeiro, todas as aplicações
herdam as configurações globais da configuração que são especificadas em um arquivo
machine.config. Daí, elas assumem as definições de configuração do arquivo web.config,
que pode residir em qualquer um dos diretórios que faça parte das aplicações ASP.NET.
Por exemplo, considere a seguinte estrutura de diretório:

\AFWeb
\bin
\files
\in
\out
\images

Nessa estrutura de diretório, todos os diretórios herdam as configurações especifica-


das em machine.config. Se o diretório AFWeb contém um arquivo web.config, esse arquivo so-
brescreve/aumenta algumas configurações de machine.config. Os diretórios abaixo de
AFWeb então herdarão as configurações de AFWeb.config. Você pode colocar outro arquivo
web.config em qualquer um dos subdiretórios de AFWeb e também sobrescrever/aumentar
as configurações do diretório pai. Essa configuração fornece flexibilidade máxima em
como você configura suas aplicações Web do ASP.NET.
O ASP.NET impede acesso externo a esses arquivos. Se alguém fosse tentar acessar
um arquivo web.config a partir de um navegador, o servidor retornaria um erro. O acesso
de HTTP a arquivos .config é estritamente proibido.
Ajustes de configuração 793

O arquivo machine.config
O arquivo machine.config está localizado no seguinte diretório:

%WINDIR%\Microsoft.NET\Framework\<version>\CONFIG

Esse arquivo é bem grande. Para examiná-lo, normalmente copio-o para outro dire-
tório e o renomeio de modo que ele tenha uma extensão XML. Isso permite abri-lo com o
Internet Explorer, o que torna a navegação por arquivos XML mais fácil. Normalmente,
ao trocar esse arquivo pela configuração ASP.NET, você modifica a seção system.web do ar-
quivo. Realmente você não pode editar o arquivo utilizando IE. Nesse caso, provavel-
mente seja interessante ter um bom editor XML, como o notepad.exe.
Está além do escopo deste capítulo discutir os elementos contidos no machine.config.
Abordaremos aqueles que se aplicam às configurações de ambiente no arquivo web.con-
fig. O que você precisa saber é que quando uma configuração no nível do sistema deve
ser feita, ela deve ser aplicada dentro do arquivo machine.config. O machine.config é docu-
mentado no site Web da MSDN ou na documentação SDK on-line.

O arquivo web.config
O arquivo web.config, como o arquivo machine.config, contém configurações específicas
que se aplicam a uma aplicação ASP.NET específica. Você também pode adicionar suas
próprias configurações a esse arquivo que seriam necessárias à aplicação, como strings de
conexão de banco de dados. De certo modo, você pode pensar no arquivo web.config
como pensaria em um arquivo .ini. O arquivo web.config não é em parte alguma tão ma-
nipulável quanto o arquivo machine.config. O esquema de arquivo web.config é mostrado
na Listagem 32.1 com as principais seções destacadas em negrito.

LISTAGEM 32.1 Esquema de arquivo web.config


<configuration>
<location>
<system.web>
<authentication>
<forms>
<credentials>
<passport>
<authorization>
<allow>
<deny>
<browserCaps>
<result>
<use>
<filter>
<case>
<clientTarget>
<add>
<remove>
794 Capítulo 32 Distribuição e configuração do ASP.NET

LISTAGEM 32.1 Continuação


<clear>
<compilation>
<compilers>
<compiler>
<assemblies>
<add>
<remove>
<clear>
<customErrors>
<error>
<globalization>
<httpHandlers>
<add>
<remove>
<clear>
<httpModules>
<add>
<remove>
<clear>
<httpRuntime>
<identity>
<machineKey>
<pages>
<processModel>
<securityPolicy>
<trustLevel>
<sessionState>
<trace>
<trust>
<webServices>
<protocols>
<add>
<remove>
<clear>
<serviceDescriptionFormatExtensionTypes>
<add>
<remove>
<clear>
<soapExtensionTypes>
<add>
<clear>
<soapExtensionReflectorTypes>
<add>
<clear>
<soapExtensionImporterTypes>
<add>
<clear>
<WsdlHelpGenerator>
Ajustes de configuração 795

LISTAGEM 32.1 Continuação


</webServices>
</system.web>
</location>
</configuration>

Tocarei em algumas dessas seções que provavelmente serão modificadas. Este capítulo
não pretende documentar cada seção deste arquivo. Entretanto, você deve ter um entendi-
mento geral de onde no arquivo web.config você deve modificar as várias configurações.
Para extensa documentação, o melhor lugar é ir para www.msdn.microsoft.com. Uma vez aí,
procure a string “ASP.NET Configuration”. Você também pode digitar o seguinte URL:

http://msdn.microsoft.com/library/en-us/cpguide/html/
➥cpconaspnetconfiguration.asp

Você também pode localizar essas informações na ajuda on-line do .NET SDK. (Pes-
quise “ASP.NET, configuration section schema” no índice).

A seção <location>
A seção <location> de um arquivo de configuração permite especificar configurações para
uma aplicação Web globalmente ou para um recurso específico. Permite também que
você impeça que a definição de configuração seja anulada para esse recurso. Há dois atri-
butos para a seção <location>. Esses são path e allowOverride.
— path – Especifica o recurso ao qual a(s) definição(ões) de configuração se apli-
ca(m). Se o caminho estiver vazio, as configurações se aplicam aos diretórios
atuais e aos diretórios filhos.
— allowOverride – Indica se as definições de configuração podem ser sobrescritas nos
arquivos web.config que residem em diretórios filhos. Esse valor é True (o padrão)
ou False.
O exemplo a seguir estabelece um tamanho do arquivo máximo que pode ser carre-
gado na página especificada:

<configuration>
<location path="GetFilePage.aspx">
<httpRuntime maxRequestLength="128"/>
</location>
</configuration>

O limite para maxRequestLength é 128 KB.


O próximo determina o mesmo limite de tamanho do arquivo, mas no site inteiro.
Isso também impede que esta configuração seja modificada pelos arquivos web.config em
diretórios filhos.

<configuration>
<location allowOverride="false" >
796 Capítulo 32 Distribuição e configuração do ASP.NET

<httpRuntime maxRequestLength="128"/>
</location>
</configuration>

A seção <authentication>
A seção <authentication> configura o suporte de autenticação ASP.NET. Isso só pode ser fei-
to no nível da máquina (machine.config) ou no nível de site. Tentativas de declarar isso no
nível de subdiretório resultam em um erro de analisador de sintaxe. O atributo mode desta
seção determina um dos modos de autenticação listados na Tabela 32.1.

TABELA 32.1 Modos da seção <authentication>


Modo Descrição
Windows Configura a autenticação Windows como o modo de autenticação padrão.
Forms Configura ASP.NET Forms como o modo de autenticação padrão.
Passport Configura Passport como o modo de autenticação padrão.
None Não configura nenhum modo de autenticação. Nesse modo, os usuários anônimos
são aceitos ou a aplicação trata sua própria autenticação.

A seção <authentication> utiliza dois tags – <forms> e <passport>. Ao utilizar o tag


<forms>, há os atributos opcionais que você deve incluir na declaração. Os atributos são
muito longos para serem mostrados aqui, porém, são bem documentados em www.msdn.
microsoft.com e na ajuda on-line da SDK. No mínimo, você deve fornecer os atributos name
e loginUrl. O atributo name refere-se a um cookie HTTP que envia as informações de login
do cliente. O atributo LoginUrl é o URL para o qual o pedido de autenticação é direciona-
do. Eis um exemplo:

<configuration>
<system.web>
<authentication mode="Forms">
<forms name="AppLogin" loginUrl="/login.aspx">
<credentials passwordFormat = "SHA1"
<user name="AppUser"
password="ADD A PASSWORD"/>
</credentials>
</forms>
</authentication>
</system.web>
</configuration>

Esse exemplo também utiliza a tag <credentials>, que permite especificar uma combi-
nação válida de nome de usuário e senha dentro do arquivo de configuração.

A seção <authorization>
A seção <authorization> permite autorizar ou negar acesso a recursos dentro da aplicação
Web baseada em usuários e papéis. Esta seção pode ser declarada no nível da máquina,
Ajustes de configuração 797

site ou diretório filho. Ela contém duas subseções, <allow> e <deny>, das quais ambas con-
têm três atributos: users, roles e verbs.
— users – Uma lista separada por vírgulas de usuários. O ponto de interrogação (?)
indica usuários anônimos. O asterisco (*) indica todos os usuários.
— roles – Uma lista separada por vírgulas de papéis que são semelhantes aos dos gru-
pos Windows.
— verbs – Uma lista separada por vírgulas de métodos HTTP como GET, HEAD, POST e
DEBUG.

A seção <httpRuntime>
A seção <httpRuntime> permite definir várias configurações de tempo de execução em
qualquer nível (máquina, site, aplicação e diretórios filhos). Os vários atributos que po-
dem ser especificados são listados na Tabela 32.2.

TABELA 32.2 Atributos da seção <httpRuntime>


Atributo Descrição
appRequestQueueLimit Especifica um número máximo de solicitações que a aplicação
ASP.NET pode tratar antes de emitir um erro 503 "Server Too
Busy". O padrão é 100.
ExecutionTimeout Especifica um limite máximo de tempo em segundos que uma
solicitação pode executar antes de ser desligada pelo ASP.NET. O
padrão é 90.
Enable Especifica se o AppDomain está ativado no nó atual e nó filho.
Esse valor pode ser True ou False. O padrão é True.
IdleTimeOut Especifica o tempo de inatividade de um AppDomain. O padrão é
20 minutos; quando excedido, o AppDomain é desligado.
EnableKernelModeCache Especifica se o cache de saída está ativado (IIS 6.0 ou versão
posterior). Esse valor pode ser True ou False.
MaxRequestLength Especifica um tamanho máximo de carregamento de arquivo. O
padrão é 4096 KB.
minFreeLocalRequestFreeThreads Especifica um número de threads livres disponíveis para o
ASP.NET processar solicitações locais. O padrão é 4.
minFreeThreads Especifica um número de threads livres disponíveis para o
ASP.NET processar solicitações. O padrão é 8.
useFullyQualifiedRedirectUrl Especifica se os redirecionamentos de cliente são URLs
completamente qualificadas. O padrão é False.
versionHeader Especifica as informações de cabeçalho de versão que o ASP.NET
inclui em todas as respostas.

Eis um exemplo dessa configuração:

<configuration>
<system.web>
<httpRuntime
798 Capítulo 32 Distribuição e configuração do ASP.NET

idleTimeOut="30"
useFullyQualifiedRedirectUrl="true"
executionTimeout="60"
versionHeader="1.1.4128"/>
</system.web>
</configuration>

A seção <pages>
A seção <pages> define configurações de página padrão que você localizaria na diretiva
@page nos arquivos .aspx. Esta seção suporta todos os níveis: máquina, site, aplicação e di-
retórios filhos. A Tabela 32.3 lista os vários atributos que podem ser especificados aqui.

TABELA 32.3 Atributos da seção <page>


Atributo Descrição
buffer Especifica se o armazenamento em buffer da resposta está ativado. Isso é
True por padrão.
enableSessionState Especifica se o estado de sessão está ativado. Isso é True por padrão.
enableViewState Especifica se o estado de visualização está ativado. Isso é True por padrão.
enableViewStateMac Especifica se o estado de visualização é avaliado quanto a adulterações
em eventos post-back.
smartNavigation Especifica se a navegação inteligente está ativada.
pageBaseType Especifica a classe code-behind da qual as páginas .aspx são herdadas.
Observe que isso pode permitir especificar uma classe básica padrão para
todas as páginas .aspx por meio da máquina inteira. Essa é uma
capacidade poderosa.
userControlBaseType Especifica a classe code-behind a partir da qual os controles de usuário
são herdados.
autoEventWireup Especifica se os eventos de página são automaticamente permitidos.
validateRequest Determina se o ASP.NET avalia todos os dados potencialmente
prejudiciais inseridos pelo navegador.

A seção <sessionState>
A seção <sessionState> especifica as configurações para um estado atual de aplicações. Isso
permite alterar o comportamento do objeto Session. <sessionState> requer um atributo
mode, que pode ser um dos seguintes valores listados:

— Off – O estado de sessão não é suportado.

— InProc – O estado de sessão é armazenado localmente.

— StateServer – O estado de sessão é armazenado em um servidor remoto.

— SQLServer – O estado de sessão é armazenado em um banco de dados SQL.

Outros atributos opcionais poderiam ser requeridos dependendo do valor do atribu-


to mode. O gerenciamento de estado é discutido no Capítulo 33.
Dicas de configuração 799

Dicas de configuração
As seções a seguir ilustram algumas definições de configuração recomendadas que po-
dem ser consideradas no desenvolvimento de aplicações ASP.NET.

Redirecionando tratamento de erros


Durante o desenvolvimento é útil que quando ocorrer um erro em uma página, você seja
redirecionado para uma página que inclui um rastreamento de pilha, o que talvez inclua
o código incorreto. A Figura 32.5 mostra um exemplo dessa página.
Mas isso não é o que você gostaria que estivesse exposto em uma aplicação produto.
Isso não significa que você não quer que seus usuários saibam que ocorreu um erro – eles
devem saber disso. Você não quer expor seu código para eles desnecessariamente. Uma
melhor opção seria redirecionar os usuários para outra página. Isso é feito modificando a
seção <customErrors> do arquivo web.config. A seguinte alteração na seção <customErrors> re-
direciona o usuário para outra página quando ocorrer um erro:

<customErrors
mode="On"
defaultRedirect="ErrWebForm.aspx">
</customErrors>

O atributo mode pode ser um dos três valores: On, Off ou RemoteOnly (o padrão). On per-
mite erros personalizados e os exibe para todo mundo. Off impede que os erros de cliente
sejam exibidos. RemoteOnly exibe erros personalizados para usuários que não estão no ser-
vidor. Quando o mode is On, você pode exibir telas mais amigáveis e que exibem menos có-
digo para seus usuários, como a mostrada na Figura 32.6.

FIGURA 32.5 Página de erro.


800 Capítulo 32 Distribuição e configuração do ASP.NET

FIGURA 32.6 Página de erro redirecionada.

u Localize o código no CD: \Code\Chapter 32\Ex01\.

Reinicialização do Worker Process


Seja qual for, o software não é perfeito e ocasionalmente uma aplicação Web fará com
que o processo trabalhador do ASP.NET (aspnet_wp.exe) trave ou pare de responder. Para
corrigir isso um administrador teria de desligar o IIS e possivelmente o servidor. Isso, na-
turalmente, pode levar uma quantidade enorme de tempo. Esse cenário é particularmen-
te irritante para os desenvolvedores. Durante as etapas de desenvolvimento de uma apli-
cação, é comum que estas se tornem irresponsivas.
A seção <processModel> no arquivo machine.config permite especificar algumas confi-
gurações que aliviarão muitos desses problemas. Você só pode fazer essas alterações no
arquivo machine.config, porque esta é uma configuração de máquina e não uma configu-
ração Web Application.

NOTA
No modo nativo, o IIS 6.0 não utiliza essa seção processModel. Além disso, esta seção é lida
pelo código não-gerenciado no aspnet_wp.exe; as alterações não são aplicadas até o IIS ser rei-
niciado.

A Tabela 32.4 lista as várias propriedades que podem ser configuradas dentro da se-
ção <processModel>.
Dicas de configuração 801

TABELA 32.4 Atributos da seção <processModel>


Atributo Descrição
clientConnectedCheck Especifica o período de tempo que uma solicitação permanece
enfileirada antes de o ASP.NET fazer uma verificação de conexão com o
cliente.
comAuthenticationLevel Especifica o nível de segurança da autenticação quanto ao DCOM.
comImpersonationLevel Especifica o nível de personificação para a segurança COM.
cpuMask Especifica os processadores de CPU que têm permissão de executar
processos do ASP.NET em uma máquina de múltiplos processadores.
enable Especifica se o modelo de processo está ativado.
idleTimeout Especifica o período de inatividade no formato de string hr:min:sec
depois que o ASP.NET concluir o processo trabalhador. Infinite é o
valor padrão.
logLevel Especifica os tipos de evento que devem ser registrados em log.
maxIoThreads Especifica o número máximo de threads de E/S que um processo pode
utilizar por CPU.
maxWorkerThreads Especifica o número máximo de threads trabalhadores que um
processo pode utilizar por CPU.
memoryLimit Especifica o tamanho máximo da memória como um por cento da
memória total do sistema que pode ser consumido pelo processo
trabalhador. O padrão é 60%. Quando excedido esse tamanho, o
ASP.NET carrega um novo processo e reatribui as solicitações a esse
processo.
password Trabalha em conjunto com o atributo userName.
requestLimit Especifica um número de solicitações permitidas antes de o ASP.NET
carregar um novo processo trabalhador.
requestQueueLimit Especifica o número de solicitações permitidas na Queue. Quando
excedido, o ASP.NET retornará o erro 503 "Server Too Busy".
responseDeadlockInterval O intervalo de tempo depois do qual um processo será reiniciado. Isso
é condicionado na existência de solicitações enfileiradas e nenhuma
resposta durante o intervalo. O padrão é três minutos.
shutdownTimeout Especifica o número de minutos dentro do qual um processo
trabalhador deve desativar-se. Quando esgotado, o ASP.NET desativará
o processo.
timeout Número especificado de minutos até o ASP.NET iniciar um novo
processo trabalhador. Valor padrão é infinito.
username Especifica o usuário (identidade Windows) que executa os processos
trabalhadores do ASP.NET. Esse valor é configurado como ASPNET por
padrão. Essa é uma conta especial criada pela configuração ASP.NET.
webGarden Utilizado em conjunto com o atributo cpuMask. Quando esse valor é
True, cpuMask especifica as CPUs que são elegíveis para executar
processos ASP.NET. Quando False, o OS faz essa determinação.

O atributo timeout é um que você deve considerar a alteração. Por padrão, o processo
trabalhador nunca reiniciará. Você pode alterar timeout de modo ele que reinicie depois
do intervalo especificado. Você deve fazer isso para assegurar que vazamentos de memó-
ria não consumam a memória disponível.
802 Capítulo 32 Distribuição e configuração do ASP.NET

idleTimeout especifica quanto tempo o ASP.NET espera antes de reiniciar o processo


trabalhador quando as solicitações estiverem enfileiradas. Além disso, shutdownTimeout de-
termina quanto tempo ele permite que um processo seja desligado antes de ser elimina-
do pelo ASP.NET. É bom alterar essa configuração se um processo parar de responder du-
rante seu desligamento. Essas três configurações permitem lidar com problemas como
impasses, vazamentos de memória, falhas em que a maquina trava (violações de acesso)
e assim por diante.
Outro tipo de problema com o qual você talvez tenha de lidar é o consumo excessivo
de memória pelo processo. Três configurações adicionais permitem reiniciar o processo
trabalhador e transferir quaisquer solicitações pendentes para o novo processo. request-
Limit, por padrão, é configurada de modo que ela seja ilimitada. Você pode configurar
isso como um valor que, ao ser alcançado, reiniciará o processo. requestQueueLimit e me-
moryLimit são outras configurações que você pode modificar para fornecer uma configura-
ção mais flexível do que as configurações padrão.

Armazenando saída em cache para obter desempenho


Ao servir páginas da Web que nunca mudam, a melhor opção é utilizar um arquivo .html
padrão. Entretanto, algumas páginas ainda são geradas dinamicamente, mas talvez rete-
nham seu conteúdo durante um período de tempo. Em vez de fazer o ASP.NET regenerar o
.html toda vez que a página for solicitada, o desempenho seria aprimorado se você armaze-
nasse a página em cache no servidor. Quando uma nova solicitação for feita, o cache é ser-
vido ao requisitante até um período de tempo limite especificado – momento em que,
uma nova página é gerada na próxima solicitação. Você pode realizar armazenamento em
cache adicionando a diretiva @OutputCache ao arquivo .aspx como mostrado aqui:

<%@ OutputCache Duration="20" Location="Any" VaryByParam="none" %>

Um exemplo fornecido no CD demonstra isso. Ele contém uma página com um con-
trole Label e um Button. Quando o botão é pressionado, o seguinte código é executado:

procedure TWebForm1.Button1_Click(sender: System.Object;


e: System.EventArgs);
begin
Label1.Text := System.String.Format('Time on the Server is: {0}',
[System.DateTime.Now.ToLongTimeString]);
end;

u Localize o código no CD: \Code\Chapter 32\Ex02\.

Basicamente, ele apenas gera a saída do tempo de sistema. Quando a diretiva @Out-
putCache estiver ausente, você pode pressionar o botão continuamente e verá um novo
tempo sendo gerado a cada segundo. Quando a diretiva @OuputCache estiver presente, o
tempo não alterará até que se esgotem os 20 segundos. Basicamente, a página da solicita-
ção original foi armazenada em cache na primeira solicitação e é apresentada em solicita-
ções subseqüentes. Algumas outras configurações para a diretiva @OutputCache são mostra-
das na Tabela 32.5.
Dicas de configuração 803

TABELA 32.5 Configurações @OutputCache


Configuração Descrição
Duration Especifica a duração em segundos que a página é armazenada em cache.
Location Especifica a capacidade de cache da página. Pode ser um dos seguintes
valores enumerados:
Any – O navegador cliente, o servidor proxy ou o servidor em que a
solicitação foi processada
Client – Armazenamento em cache da saída localizada no navegador cliente
Downstream – Armazenado em qualquer dispositivo HTTP 1.1 compatível com
cache mas não o servidor
None – Cache desativado
Server – Cache localizado no servidor
ServerAndClient – Cache localizado no servidor ou no cliente solicitante
(mas não em servidores proxy)
Shared Especifica se a saída de um controle de usuário pode ser compartilhada em
múltiplas páginas.
VaryByCustom Especifica se armazena diversas versões de cache para diferentes
navegadores, permitindo várias saídas para diferentes navegadores.
VaryByHeader Especifica se armazena diferentes versões de cache para diferentes cabeçalhos
de solicitação de HTTP.
VaryByParam Especifica se armazena um cache diferente por parâmetros em uma lista de
string de formulário ou consulta.
VaryByControl Armazena em cache os controles de usuário no servidor de modo que eles
não sejam novamente renderizados em solicitações de página.

Monitorando o processo ASP.NET


A seção anterior discute como você pode alterar várias configurações que determinam
como o processo trabalhador opera. Especificamente, você estabelece limiares que fazem
com que o processo seja desligado por várias razões. Seria útil se você pudesse monitorar
essa atividade. Felizmente, você pode utilizar a classe ProcessInfo, que está no namespace
System.Web. A classe ProcessInfo tem membros que são úteis para fornecer certas informa-
ções de status sobre o processo do ASP.NET. A Tabela 32.6 lista essas propriedades.

TABELA 32.6 Propriedades ProcessInfo


Propriedade Descrição
Age O período de tempo de execução do processo
PeakMemoryUsed O máximo de memória que o processo tem utilizado
ProcessID O ID do processo
RequestCount O número de solicitações executado pelo processo
ShutdownReason A razão pela qual o processo foi desligado
StartTime A hora em que o processo iniciou
Status Status atual do processo
804 Capítulo 32 Distribuição e configuração do ASP.NET

Outra classe, ProcessModelInfo, pode ser utilizada para obter informações sobre pro-
cesso trabalhador, inclusive o histórico do processo trabalhador. Para fazer isso, você in-
vocaria o método ProcessModelInfo.GetHistory( ). Esse método aceita um parâmetro intei-
ro que especifica o número de elementos que o array resultante retorna. GetHistory( ) re-
torna um array de instâncias da classe ProcessInfo. O próximo código ilustra como você
pode chamar o método GetHistory( ) que estará vinculado ao DataGrid:

DataGrid1.DataSource := ProcessModelInfo.GetHistory(20);
DataGrid1.DataBind;

u Localize o código no CD: \Code\Chapter 32\Ex03\.

A Figura 32.7 mostra a saída desse código.

Rastreando a aplicação
O rastreamento pode ser referido como depurador de pobre. De fato, é uma das mais va-
liosas de capacidades que os desenvolvedores têm ao desenvolver suas aplicações. É uma
maneira que permite examinar a execução do código e também copiar estados personali-
zados sobre o código durante a execução. Há duas formas de rastreamento – rastreamen-
to no nível da página e no nível da aplicação.

Rastreamento no nível da página


O rastreamento no nível da página permite rastrear a execução de páginas individuais.
Você ativa o rastreamento no nível de página adicionando o atributo trace à diretiva
@page no arquivo .aspx, como mostrado aqui:

<%@ Page language="c#" Codebehind="WebForm1.pas" AutoEventWireup="true"


Inherits="WebForm1.TWebForm1" Trace="true" %>

FIGURA 32.7 Tela Process Monitoring.


Dicas de configuração 805

Quando você carregar utilizando uma página simples, ela será semelhante à mostra-
da na Figura 32.8.

u Localize o código no CD: \Code\Chapter 32\Ex04\.

Com o rastreamento ativado, você verá as informações de rastreamento acrescenta-


das na parte inferior da página. As informações de rastreamento são separadas nas possí-
veis seções, que são listadas na Tabela 32.7.

TABELA 32.7 Seções de rastreamento


Seção de rastreamento Descrição
Request Details Exibe informações sobre a solicitação do cliente.
Trace Information Exibe os detalhes de execução da página.
Control Tree Exibe a hierarquia de controle na página.
Session State Exibe os dados armazenados no estado Session.
Application State Exibe os dados armazenados no estado Application.
Cookies Collection Exibe os dados do par nome/valor do cookie.
Headers Collection Exibe os cabeçalhos do navegador cliente.
Form Collection Exibe os pares nome/valor de entradas de formulário.
QueryString Collection Exibe os pares nome/valor contidos na string de consulta.
Server Variables Exibe as variáveis de servidor como aqueles em cabeçalhos de HTTP,
ambiente e assim por diante.

FIGURA 32.8 Uma página com informações de rastreamento.


806 Capítulo 32 Distribuição e configuração do ASP.NET

Além dessas informações padrão, você pode gravar seus próprios dados na seção
Trace Information executando o método Trace.Write( ), como mostrado aqui:

Trace.Write('Debug Session', TextBox1.Text);

Isso resultaria na saída mostrada na Figura 32.9. Note a entrada adicionada à lista
Trace Information.
Algo excelente sobre o método Trace.Write( ) é que ele é ignorado por sua página
quando o rastreamento estiver desligado. Portanto, quando levar sua aplicação para pro-
dução, você não precisa limpar todas as instruções Trace.Write( ) de seu código.
Outra coisa que você pode fazer com o rastreamento é copiar informações de erro
como um resultado de uma exceção. Por exemplo, o seguinte código:

try
raise Exception.Create('An Error occurred.');
except
on E: Exception do
begin
Trace.Write('My Errors', E.Message, E);
end;
end;

resultaria na saída mostrada na Figura 32.10.

FIGURA 32.9 Uma tela Trace com informações personalizadas.


Dicas de configuração 807

FIGURA 32.10 Tela Trace com informações de erro.

Aqui, note a categoria My Errors.

Rastreamento no nível da aplicação


O rastreamento no nível da página é excelente quando você quer rastrear uma única pá-
gina. Entretanto, seria muito trabalhoso se você precisasse rastrear a aplicação inteira, es-
pecialmente se a aplicação consistisse em centenas de páginas como muitas têm. Nessa
situação você utilizaria rastreamento no nível da aplicação. Para ativar o rastreamento
no nível da aplicação, você deve adicionar a entrada <trace> a seu arquivo machine.config
ou web.config. Um exemplo é fornecido aqui:

<trace
enabled="true"
requestLimit="10"
pageOutput="false"
traceMode="SortByTime"
localOnly="true"
/>

Os atributos dentro da diretiva <trace> são explicados na Tabela 32.8.


808 Capítulo 32 Distribuição e configuração do ASP.NET

TABELA 32.8 Atributos <trace>


Atributo Descrição
enabled Especifica se o rastreamento está ativado. O valor padrão é False.
requestLimit Especifica o número de solicitações a listar na página trace.axd, que é discutida
mais adiante.
pageOutput Especifica se as informações detalhadas de rastreamento são incluídas na parte
inferior de cada página. Isso assume padrão de False.
traceMode Especifica a ordem em que as mensagens são exibidas. Esse valor pode ser
SortByTime (o padrão) ou SortByCategory.
localOnly Especifica se mensagens de rastreamento são exibidas apenas no computador
local ou também em computadores remotos.

Com essa configuração, você pode visualizar uma página especial chamada
trace.axd, que contém informações de rastreamento para solicitações feitas para sua apli-
cação. A Figura 32.11 mostra como é essa página.
Clicar no link View Details mostra as informações detalhadas sobre o rastreamento
de cada solicitação.

Adicionando/obtendo definições
personalizadas de configuração
Freqüentemente, você precisará armazenar informações no arquivo de configuração que
não faz parte das seções padrão. Há dois lugares em que você pode armazenar informa-
ções no arquivo web.config. Um está na seção <appSettings>. O outro está na própria se-
ção personalizada.

FIGURA 32.11 Rastreamento por aplicação.


Adicionando/obtendo definições personalizadas de configuração 809

Adicionando e lendo <appSettings>


Você pode adicionar entradas personalizadas ao arquivo web.config. De fato, há uma
seção especificamente para configurações de aplicação personalizadas. Esta seção é
<appSettings> e ocorre fora da seção <system.web>. Uma entrada típica nesta seção é uma
string de conexão com um banco de dados. A seguir vemos como isso pode aparecer na
seção <appSettings>:

<configuration>
<appSettings>
<add
key="ConnectionString"
value="server=XWING;database=Northwind;Trusted_Connection=Yes" />
</appSettings>
<system.web>
...
</system.web>
</configuration>

As entradas na seção <appSettings> são baseadas em um par chave/valor. Aqui, você


vê a chave como sendo "ConnectionString" e o valor sendo a string real de conexão.
Para ler essa entrada em uma aplicação, você utiliza a classe ConfigurationSettings,
que é definida no namespace System.Configuration. Você pode acessar entradas na seção
<appSettings> referindo-se à propriedade de array AppSettings como mostrado aqui:

SqlConnection1.ConnectionString :=
ConfigurationSettings.AppSettings['ConnectionString'];

u Localize o código no CD: \Code\Chapter 32\Ex05\.

Adicionando e lendo seções personalizadas de configuração


Quando precisar de vários itens no arquivo web.config, você pode criar sua própria seção.
O exemplo a seguir mostra a aparência que isso pode ter:

<configuration>
<configSections>
<section name="NewSection"
type="System.Configuration.NameValueSectionHandler, System,
➥Version=1.0.5000.0, Culture=neutral,
➥PublicKeyToken=b77a5c561934e089"/>
</configSections>
<NewSection>
<add key="ConnectionString"
value="server=XWING;database=Northwind;Trusted_Connection=Yes"/>
</NewSection>

</configuration>
810 Capítulo 32 Distribuição e configuração do ASP.NET

Como mostrado nesse exemplo, você primeiro declara suas seções na seção <config-
Section> do arquivo web.config. Ao declarar uma seção, você deve especificar o nome de
seção e um handler de configuração. Nesse exemplo o handler utilizado é o System.Confi-
guration.NameValueSectionHandler. É possível criar um handler personalizado. Para esse
exemplo esse handler é suficiente e o mesmo utilizado para ler os itens da seção <appSet-
tings>. Para ler o valor, o código seria semelhante ao mostrado aqui:

var
nvc: NameValueCollection;
begin
if not IsPostBack then
begin
nvc := (ConfigurationSettings.GetConfig('NewSection') as
NameValueCollection);
SqlConnection1.ConnectionString := nvc['ConnectionString'];

end

u Localize o código no CD: \Code\Chapter 32\Ex06\.


NESTE CAPÍTULO
CAPÍTULO 33 — Aplicações ASP.NET que
usam cache

Cache — Gerenciamento de estado


em aplicações ASP.NET
e gerenciamento de
estados em aplicações
ASP.NET

Este capítulo discute dois tópicos relacionados, porém


distintos, sobre a programação ASP.NET – cache e
gerenciamento de estado. O cache é utilizado para
melhorar o desempenho fazendo com que os dados
comumente acessados persistam no servidor Web.
Quando usuários solicitam esses dados, eles são obtidos
do servidor Web no lugar da origem de dados. O
gerenciamento de estado lida com a questão das
aplicações Web serem sem estado (stateless) – sem
nenhum conhecimento sobre solicitações anteriores.
Gerenciamento de estado fornece uma maneira de a
aplicação Web manter as informações sobre a interação
de um usuário com a aplicação Web.

Aplicações ASP.NET que usam


cache
Esta seção discute como funciona o armazenamento em
cache nas aplicações ASP.NET. Há principalmente três
formas de armazenamento em cache. Cache de página,
cache de fragmentos de página e cache de dados.

Armazenando páginas em cache


O cache de página é o processo de fazer com que uma
página inteira persista no servidor, servidor proxy ou
navegador cliente, de modo que, na próxima vez que
for recuperada, ela não precise ser gerada pelo ASP.NET.
812 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

Utilização da diretiva @ OutputCache


O cache de página é ativado incluindo a diretiva em negrito (linha 2) ao arquivo .aspx
mostrado na Listagem 33.1.

LISTAGEM 33.1 Arquivo .aspx contendo a diretiva do cache de página


1: <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas"
➥AutoEventWireup="false" Inherits="WebForm1.TWebForm1" %>
2: <%@ OutputCache Duration="20" Location="Any" VaryByParam="none" %>
3: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:
5: <html>
6: <head>
7: <title></title>
8: <meta name="GENERATOR" content="Borland Package Library 7.1">
9: </head>
10:
11: <body ms_positioning="GridLayout">
12: <form runat="server">
13: <asp:label id=Label1
14: style="Z-INDEX: 1; LEFT: 62px; POSITION: absolute;
15: TOP: 14px" runat="server">Label</asp:label>
16: <asp:button id=Button1
17: style="Z-INDEX: 2; LEFT: 70px; POSITION: absolute;
18: TOP: 46px" runat="server" text="Button">
19: </asp:button>
20: </form>
21: </body>
22: </html>

u Localize o código no CD: \Code\Chapter 33\Ex01\.

Nesse exemplo, a diretiva @ OutputCache inclui três atributos. O primeiro, Duration, es-
pecifica quanto tempo (em segundos) o cache irá reter a página antes de regenerar sua
HTML. O segundo atributo, Location, determina onde o cache é armazenado. O terceiro,
permite criar uma versão diferente da página resultante com base nos valores fornecidos
em uma lista separada por vírgulas após o atributo VaryByParam. Esses e outros atributos
são completamente explicados na Tabela 33.1. Alguns desses atributos são relativos ao
cache de página, de controle, ou ambos.

TABELA 33.1 Atributos @ OutputCache


Atributo Descrição
Duration Especifica o tempo em segundos que a página permanece no cache. Especificando
um valor, uma diretiva de expiração é estabelecida para a página ou controle sendo
armazenado em cache. Esse é um atributo exigido.
Location Location permite especificar onde a página é armazenada em cache. Ele pode
conter um dos valores a seguir.
Aplicações ASP.NET que usam cache 813

TABELA 33.1 Continuação


Atributo Descrição
Any – O item pode ser armazenado em cache em um destes locais. Essa é a
configuração padrão.
Client – O item é armazenado em cache no navegador do cliente.
Downstream – O item é armazenado em cache em um servidor downstream.
None – Não há nenhum cache de página realizado.
Server – O item é armazenado em cache no servidor.
Shared Os atributos share lidam com controles de usuário e determinam se o cache do
controle pode ser compartilhado com outras páginas.
VaryByCustom Strings separadas por ponto-e-vírgula que permitem variar páginas com base no
tipo de navegador ou strings personalizadas.
VaryByHeader Lista separada por ponto-e-vírgula dos cabeçalhos que podem ser utilizados para
disponibilizar diferentes páginas com base nas informações no cabeçalho.
VaryByParam Strings separadas por ponto-e-vírgula que representam os parâmetros utilizados
para determinar uma de saída de página variável. Essas strings correspondem aos
atributos enviados com um método GET ou parâmetros enviados com o método
POST. Esse atributo é obrigatório e pode conter uma string vazia.
VaryByControl Lista separada por ponto-e-vírgula de nomes de propriedades controladas pelo
usuário. Isso só é válido para cache de controle (cache de fragmento).

O exemplo na Listagem 33.1 ilustra como funciona o cache de página. É uma página
que contém um controle Button e um Label. O code-behind para o evento Click do Button rea-
liza o seguinte:

procedure TWebForm1.Button1_Click(sender: System.Object;


e: System.EventArgs);
begin
Label1.Text := System.String.Format('Time on the Server is: {0}',
[System.DateTime.Now.ToLongTimeString]);
end;

Ao executar a aplicação, clicar no botão mostra que a página está sendo armazenada
em cache. A data/hora gravada na página não muda até o cache expirar, como determi-
nado pelo atributo Duration da diretiva @ OutputCache.

Variação por parâmetros


Ilustrar uma das variações de atributos, especificamente o atributo VaryByParam. Esse atri-
buto pode conter um de três possíveis valores, incluindo none, um asterisco (*) e uma
string válida que representa um atributo do método GET ou um nome de parâmetro POST.
VaryByParam resulta em uma página diferente armazenada em cache para cada solicitação
distinta (como determinado pelos parâmetros que são passados). Ao utilizar o *, como
mostrado a seguir, todos os parâmetros são levados em consideração.

<%@ OutputCache Duration="20" Location="Any" VaryByParam="*" %>


814 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

Você também pode indicar um parâmetro específico pelo nome, fazendo com que
apenas os parâmetros especificados sejam levados em consideração distinguindo uma
solicitação separada que precisa ser armazenada em cache. O processo é ilustrado aqui:

<%@ OutputCache Duration="20" Location="Any" VaryByParam="FirstName" %>

Para ilustrar isso a Listagem 33.2 é o arquivo .aspx para um exemplo semelhante ao
mostrado na Listagem 33.1. Observe que o atributo VaryByParam agora contém um asterisco.

LISTAGEM 33.2 Exemplo da variação por parâmetro


1: <%@ Page language="c#" Debug="true" Codebehind="WebForm1.pas"
➥AutoEventWireup="false" Inherits="WebForm1.TWebForm1" %>
2: <%@ OutputCache Duration="120" Location="Any" VaryByParam="*" %>
3: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:
5: <html>
6: <head>
7: <title></title>
8: <meta name="GENERATOR" content="Borland Package Library 7.1">
9: </head>
10:
11: <body ms_positioning="GridLayout">
12: <form runat="server">
13: <asp:label id=Label1
14: style="Z-INDEX: 1; LEFT: 38px; POSITION: absolute;
15: TOP: 14px" runat="server">Label</asp:label>
16: </form>
17: </body>
18: </html>

u Localize o código no CD: \Code\Chapter 33\Ex02\.

Ao inserir uma URL como

http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob

a saída gravada será

"Bob, the time on the Server is: 8:46:04 a.m."

Alterando a URL para

http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Sam

o resultado é a saída

"Sam, the time on the Server is: 8:46:17 a.m."


Aplicações ASP.NET que usam cache 815

Supondo que VaryByParam="FirstName" e utilizando Bob como o parâmetro, veremos que


a saída original retorna com a data/hora aparentemente se movendo para trás, às 8:46:04
da manhã. O que está acontecendo aqui é que há duas versões dessa página sendo armaze-
nadas em cache – uma, quando o parâmetro FirstName é igual a Bob e a outra, quando Firs-
tName é Sam. Como um outro ponto interessante, considere as duas URLs a seguir:

http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Jones
http://localhost/PgCshByParam/WebForm1.aspx?FirstName=Bob&LastName=Archer

Ambas resultariam no retorno da mesma página armazenada em cache. Entretanto,


isso teria resultados diferentes se alterássemos a diretiva @ OutputCache para o seguinte:

<%@ OutputCache Duration="120" Location="Any" VaryByParam="FirstName" %>

Com essa diretiva, somente as solicitações em que o parâmetro FirstName é diferente


resultarão em páginas diferentes armazenadas em cache. As duas URLs anteriores con-
tendo o mesmo FirstName, mas diferentes parâmetros LastName, utilizarão a mesma página
armazenada em cache.

Variação por cabeçalhos


Em algumas situações você desejará que suas aplicações ASP.NET tirem proveito das ca-
pacidades do navegador. Entretanto, você não desejará disponibilizar páginas para nave-
gadores que não utilizam as capacidades suportadas pelo navegador alvo. Portanto, ao
utilizar o cache, não faz sentido armazenar uma página em cache que não será suportada
pelo navegador do cliente. A diretiva @ OutputCache a seguir mostra como você pode criar
uma página diferente com base nas informações no cabeçalho HTTP – especificamente o
cabeçalho User-Agent com o atributo VaryByHeader.

<%@ OutputCache Duration="120" Location="Any" VaryByParam="*"


➥VaryByHeader="User-Agent" %>

O navegador do cliente pode ser identificado pelo cabeçalho HTTP User-Agent. Você
pode especificar outros cabeçalhos HTTP no VaryByHeader ou múltiplos cabeçalhos separa-
dos por ponto-e-vírgulas.

Variação por strings personalizadas


Você pode ser bem específico quanto aos requisitos que determinam as variações das pá-
ginas armazenadas em cache utilizando o atributo VaryByCustom da diretiva @ OutputCache.
Há duas maneiras de utilizar esse atributo. A primeira, e a mais simples, é especificar
um valor de "Browser", como mostrado aqui:

<%@ OutputCache Duration="120" Location="Any" VaryByParam="*"


➥VaryByCustom="Browser" %>

Isso resulta em um comportamento semelhante ao exemplo de VaryByHeader explica-


do anteriormente. Ele difere pelo fato de que VaryByCustom="Browser" só utiliza o tipo e a
versão mais importante do navegador em vez das informações adicionais que poderiam
ser incluídas no cabeçalho User-Agent.
816 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

Uma outra maneira de utilizar o atributo VaryByCustom é especificar uma string defi-
nida pelo usuário. Ao fazer isso, você precisa sobrescrever o método GetVaryByCustom-
String( ) da classe HttpApplication em Globals.pas. Isso se pareceria com o código a seguir:

function TGlobal.GetVaryByCustomString(Context: HTTPContext;


custom: &String): System.String;
begin
if custom = 'Country' then
Result := GetCountry(Context)
else
Result := GetVaryByCustomString(Context, custom);
end;

Esse código ilustra como você pode armazenar páginas em cache com base no país
do usuário que origina a solicitação. Isso supõe que o método GetCountry( )retorne uma
string que seria utilizada como a string por meio da qual podemos variar as páginas ar-
mazenadas em cache.

Armazenando fragmentos de páginas em cache


O cache de fragmentos de página é semelhante ao cache de página, mas, em vez de arma-
zenar em cache a página inteira, você armazena elementos específicos da página. Isso
pode ser realizado armazenando em cache os controles de usuário, abordados no Capítu-
lo 34. Os controles de usuário podem ser utilizados com a diretiva @ OutputCache da mesma
maneira como ocorre com as páginas. Alguns atributos não são suportados porque eles
não fazem sentido uma vez que os controles de usuário existem no contexto da página.
Esses são Location e VaryByHeader. O arquivo .ascx a seguir define um controle de usuário
que utiliza esse tipo de cache:
<%@ Control Language="c#" AutoEventWireup="false" Codebehind=
➥"WebUserControl1.pas" Inherits="WebUserControl1.TWebUserControl1"%>
<%@ OutputCache Duration="20" VaryByParam="*" %>
<asp:label id=Label1
style="Z-INDEX: 101; LEFT: 38px; POSITION: absolute; TOP: 38px"
runat="server">Label</asp:label>

u Localize o código no CD: \Code\Chapter 33\Ex03\.

Esse controle simplesmente exibe a data/hora do sistema. Para ilustrar o cache de


fragmentos de página, ele é incluído em uma página que também exibe a data/hora do
sistema, mas não é armazenada em cache. A Figura 33.1 mostra a saída depois de atuali-
zar o navegador várias vezes. Você pode ver que esse controle de usuário retém sua
data/hora original e fará isso até o cache expirar.

Armazenando dados em cache


Cache de dados é uma maneira de armazenar quaisquer tipos de dados em cache. Isso é
particularmente útil para aumentar o desempenho sem a necessidade de solicitar dados
Aplicações ASP.NET que usam cache 817

FIGURA 33.1 Cache de um controle de usuário.

de uma origem de dados. Em vez disso, isso pode ser feito uma vez para preencher um Da-
taSet que você então armazena em cache. Solicitações subseqüentes a esse DataSet irão en-
tão obtê-lo do cache. Para exemplificar esse processo, precisaremos examinar a classe Ca-
che.

A classe Cache
A classe Cache é definida no namespace System.Web.Caching e fornece a capacidade de arma-
zenar as informações na memória no nível da aplicação, que podem ser obtidas em dife-
rentes solicitações. Esse processo funciona de maneira semelhante à classe Application,
que discutiremos mais adiante neste capítulo.
Há duas propriedades e quatro métodos importantes com relação à classe Cache, des-
critos na Tabela 33.2.

TABELA 33.2 Propriedades e métodos da classe Cache


Propriedade/método Descrição
Count Essa propriedade retorna o número de itens atualmente armazenados na
classe Cache.
Item Essa propriedade é um array indexador que retorna o item por meio de
uma chave especificada.
Add( ) Esse método permite adicionar um item à classe Cache. Você pode
especificar como dependências de parâmetros, uma diretiva de
expiração, uma diretiva de prioridade e um método de retorno
notificando remoção. Add( ) falhará se já existir um item em Cache para
uma dada chave.
Get( ) Esse método retorna um item da Cache por meio da chave especificada.
818 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

TABELA 33.2 Continuação


Propriedade/método Descrição
Insert( ) Esse método insere um item na classe Cache, substituindo quaisquer itens
existentes pela mesma chave. Você pode incluir os mesmos parâmetros
do método Add( ).
Remove( ) Esse método remove um item de Cache com a chave especificada.

Como indicado para os métodos Cache.Add( ) e Cache.Insert( ), há vários parâmetros


relativos ao item sendo colocados na classe Cache. Eles são discutidos na Tabela 33.3 na
ordem em que eles aparecem como parâmetros.

TABELA 33.3 Propriedades e métodos da classe Cache


Propriedade/método Descrição
Key A string chave utilizada para referenciar o item armazenado em cache.
Value O item (um parâmetro System.Object) adicionado à classe Cache.
Dependencies Um único ou múltiplos arquivos, diretórios ou chaves para um outro item
armazenado em cache do qual esse novo item depende. Quando o arquivo
ou o item armazenado em cache muda, esse item armazenado em cache é
removido de Cache.
AbsoluteExpiration A data/hora em que o item foi removido da classe Cache.
SlidingExperation O intervalo de data/hora em que o item foi removido de Cache se não tiver
sido acessado durante essa data/hora. Se o item for acessado, a expiração é
configurada como a data/hora do acesso mais a data/hora especificada por
esse valor.
Priority Um valor de tipo enumerado CacheItemPriority utilizado pela Cache ao
excluir objetos.
OnRemoveCallback Um delegate (handler de evento) invocado sempre que um item
armazenado em cache é removido.

O código a seguir ilustra o uso da classe Cache :

if Cache['DateToday'] < > nil then


Response.Write('From Cache: '+
DateTime(Cache['DateToday']).Today.ToLongDateString)
else begin
Response.Write('From System: '+
System.DateTime.Today.ToLongDateString);
Cache.Add('DateToday', System.DateTime.Today, nil, GetMidnight,
Cache.NoSlidingExpiration, CacheItemPriority.Default, nil)
end;

Esse código exibe a data/hora do dia atual obtida do sistema ou da Cache se a data/hora
do dia atual existir em Cache. A chamada a Cache.Add( ) ilustra a utilização do parâmetro
AbsoluteExpiration, configurado como meia-noite pela função auxiliar GetMidnight( ). Se
você estiver se perguntando, a função GetMidnight( ) simplesmente retorna um valor Date-
Time para a data/hora do dia seguinte adicionando 1 à data/hora do dia atual:
Aplicações ASP.NET que usam cache 819

function GetMidnight: System.DateTime;


begin
Result := System.DateTime.Today.AddDays(1);
end;

Exemplo de cache de dados


Armazenar em cache tipos de dados simples pode ser útil. O valor real ganho em termos
de desempenho acontece quando você armazena em Cache os dados que, do contrário,
seriam obtidos de um outro recurso como um banco de dados. A Listagem 33.3 mostra o
fragmento de um exemplo que demonstra essa técnica.

LISTAGEM 33.3 Cache de dados


1: const
2: c_cnstr = 'server=XWING;database=Northwind;Trusted_Connection=Yes';
3: c_sel = 'select * from products';
4:
5: procedure TWebForm1.Page_Load(sender: System.Object;
6: e: System.EventArgs);
7: begin
8: if not IsPostBack then
9: GetData;
10: end;
11:
12: procedure TWebForm1.GetData;
13: var
14: sqlcn: SqlConnection;
15: sqlDa: SqlDataAdapter;
16: Ds: DataSet;
17: dtView: DataView;
18: begin
19:
20: dtView := Cache['dvProducts'] as DataView;
21: if dtView = nil then
22: begin
23: sqlcn := SqlConnection.Create(c_cnstr);
24: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
25: Ds := DataSet.Create;
26: sqlDA.Fill(Ds);
27: try
28: dtView := DataView.Create(Ds.Tables['Table']);
29: Cache['dvProducts'] := dtView;
30: Label1.Text := 'From Database';
31: finally
32: sqlcn.Close;
33: end;
34: end
35: else
820 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

LISTAGEM 33.3 Continuação


36: Label1.Text := 'From Cache';
37:
38: DataGrid1.DataSource := dtView;
39: DataBind;
40: end;
41:
42: procedure TWebForm1.DataGrid1_PageIndexChanged(source: System.Object;
43: e: System.Web.UI.WebControls.DataGridPageChangedEventArgs);
44: begin
45: GetData;
46: DataGrid1.CurrentPageIndex := e.NewPageIndex;
47: DataGrid1.DataBind;
48: end;

u Localize o código no CD: \Code\Chapter 33\Ex04\.

A procedure GetData( ) (linhas 12–40) é a que queremos examinar bem de perto. A linha
20 tenta obter um DataView a partir de Cache. Se não existir, ele será nil. Sendo esse o caso, os
dados são obtidos do banco de dados Northwind. Além de extrair esses dados do banco de
dados, eles são adicionados à classe Cache (linha 29). Nas solicitações subseqüentes a essa pá-
gina, a linha 20 não deve retornar nil, mas, em vez disso, o DataView armazenado em cache.
Essa técnica é adequada para dados que não mudam, como informações sobre uma pes-
quisa. Ela também funcionará para dados que mudam – nesse caso, você precisará desenvol-
ver um código a fim de sincronizar os dados armazenados em Cache e no banco de dados.
Isso poderia ser tão simples quanto atualizar todo o DataView quando ocorre uma alteração.
Ela também pode se tornar mais complexa, como ao atualizar tanto o Cache como o banco de
dados apenas com os dados modificados. Uma outra solução útil é armazenar em cache as
informações por um tempo específico (por exemplo, meia-noite). Isso é perfeito para dados
raramente atualizados. Vários fatores determinarão as abordagens que você deve seguir,
como escalabilidade e requisitos de sistema para citar alguns. Por exemplo, você não vai
querer desperdiçar memória de sistema armazenando em cache grandes volumes de DataSet,
o que acabaria com quaisquer aumentos de desempenho que você possa ganhar.

Dependências do arquivo de cache


É possível tornar um item armazenado em cache dependente de únicos ou múltiplos ar-
quivos associados, diretórios ou outros itens armazenados em cache. Se a entidade asso-
ciada for modificada, o item dependente será removido de Cache. A Listagem 33.4 mostra
como estabelecer essa dependência utilizando o método Cache.Insert( ).

LISTAGEM 33.4 Estabelecendo uma dependência em um arquivo armazenado em cache


1: procedure TWebForm1.Button1_Click(sender: System.Object;
2: e: System.EventArgs);
3: var
4: str: String;
Aplicações ASP.NET que usam cache 821

LISTAGEM 33.4 Continuação


5: begin
6: str := Cache['MyData'] as System.String;
7: if str = nil then
8: begin
9: Label1.Text := 'Not in Cache';
10: Str := 'Now in Cache';
11: Cache.Insert('MyData', Str,
12: CacheDependency.Create(MapPath('cache.txt')));
13: end
14: else
15: Label1.Text := Str;
16: end;

u Localize o código no CD: \Code\Chapter 33\Ex05\.

As linhas 11–12 associam o arquivo cache.txt como a dependência do item 'MyData'


armazenado em cache. Se cache.txt for modificado, 'MyItem' será removido de Cache. Isso
permite estabelecer um mecanismo externo por meio do qual você pode invocar uma
atualização das informações armazenadas em cache. A seção a seguir ilustra um exemplo
prático dessa técnica.

DICA
Utilize o método MapPath( ) para converter caminhos virtuais em caminhos físicos, como utiliza-
do na Listagem 33.4.

Neste exemplo, quando o arquivo 'cache.txt' é modificado ou excluído, o item asso-


ciado a 'MyData' também é removido de Cache.
Você também pode estabelecer uma dependência de chave. Uma dependência de
chave é aquela em que um item armazenado em cache torna-se dependente de um outro
item armazenado em cache. Isso é feito com a instrução INSERT desta maneira:

keyAry[0] := 'Item1';
keyAry[1] := 'Item2';
Cache.Insert('MyData', Str, CacheDependency.Create(nil, keyAry));

Neste exemplo, o item com a chave 'MyData' torna-se dependente dos itens com cha-
ve como 'Item1' e 'Item2'.
A técnica de criar um array de itens também pode ser utilizada ao estabelecer uma
dependência de múltiplos arquivos ou diretórios criando um array de string de nomes de
arquivos ou de diretórios.

Estendendo dependências de arquivos para utilização no SQL Server


Esta seção demonstra um exemplo realista da utilização de dependências de cache.
A Listagem 33.5 mostra um método GetData( ) semelhante àquele visto na Listagem 33.3.
822 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

LISTAGEM 33.5 GetData( ) com uma dependência em cache


1: procedure TWebForm1.GetData;
2: var
3: sqlcn: SqlConnection;
4: sqlDa: SqlDataAdapter;
5: Ds: DataSet;
6: dtView: DataView;
7: begin
8: dtView := Cache['dvEmp'] as DataView;
9: if dtView = nil then
10: begin
11: sqlcn := SqlConnection.Create(c_cnstr);
12: sqlDA := SqlDataAdapter.Create(c_sel, sqlcn);
13: Ds := DataSet.Create;
14: sqlDA.Fill(Ds);
15: try
16: dtView := DataView.Create(Ds.Tables['Table']);
17: Cache.Insert('dvEmp', dtView,
18: CacheDependency.Create(MapPath('cache.txt')));
19: Label1.Text := 'From Database';
20: finally
21: sqlcn.Close;
22: end;
23: end
24: else
25: Label1.Text := 'From Cache';
26: DataGrid1.DataSource := dtView;
27: DataBind;
28: end;

u Localize o código no CD: \Code\Chapter 33\Ex06\.

As linhas 17 e 18 são onde a dependência é estabelecida. Quando o arquivo 'ca-


che.txt' é modificado ou excluído, o DataView é removido de Cache. É recomendável fazer
isso quando os dados no banco de dados são modificados e dessincronizam as informa-
ções armazenadas em cache. A pergunta que isso levanta é como modificar cache.txt. Se o
banco de dados associado for modificado por um programa externo, ele poderá modifi-
car o arquivo. Se o banco de dados for modificado por uma aplicação ASP.NET, ela tam-
bém poderá modificar o arquivo; entretanto, devemos nos perguntar por que simples-
mente não atualizar os dados sem lidar com o arquivo.
A idéia aqui é invocar uma atualização dos dados armazenados em cache sempre que
os dados armazenados na tabela que o DataView representa mudarem. Realmente não im-
porta onde os dados foram modificados. A maneira de fazer isso é criar uma trigger na ta-
bela SQL que modificou o arquivo. Um exemplo é mostrado aqui:

CREATE TRIGGER EMP_UPD_CACHE


ON Employees
FOR UPDATE, DELETE, INSERT
Aplicações ASP.NET que usam cache 823

AS
DECLARE @ShCmd VARCHAR(100)
SELECT @ShCmd = 'echo '+ Cast(GetDate( ) as VARCHAR(25))+' >
➥"C:\Data\cache.txt"'
EXEC master..xp_cmdshell @ShCmd, no_output

Essa trigger gravará as informações no arquivo cache.txt quando um registro é atuali-


zado, excluído ou adicionado à tabela Employees, que efetivamente invocará a atualiza-
ção que desejamos.

NOTA
O exemplo anterior supõe que o SQL Server está em execução na mesma máquina do servidor
Web ou pelo menos os dois servidores vêem o mesmo compartilhamento NTFS.

Métodos de retorno com cache


Esta seção ilustra como podemos associar um método de retorno com um item adiciona-
do à classe Cache. Esse método de retorno é invocado sempre que os itens com os quais
está associado são removidos de Cache. O método de retorno recebe os três parâmetros lis-
tados aqui:

— key – Índice de string para o item armazenado em cache.

— value – Valor do item removido de Cache.

— reason – A razão pela qual o item foi removido de Cache. Esse valor é um dos valores
do tipo enumerado CacheItemRemovedReason.

Valores válidos para o parâmetro reason são:


— DependencyChanged – Uma dependência no item foi modificada.

— Expired – O item atingiu seu período de expiração.

— Removed – O item foi removido da classe Cache pelo método Remove( ) or Insert( ).

— Underused – O item foi removido pelo sistema para liberar memória.

A Listagem 33.6 demonstra como utilizar o método de retorno com itens armazena-
dos em cache.

LISTAGEM 33.6 Exemplo de cache utilizando método de retorno


1: procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
2: var
3: keyAry: array[0..0] of String;
4: begin
5: if not IsPostBack then
6: begin
7: Cache.Insert('Item1', 'Item 1', nil,
8: System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
824 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

LISTAGEM 33.6 Continuação


9: CacheItemPriority.Default, CacheItemRemoved);
10: Cache.Insert('Item2', 'Item 2', nil,
11: System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
12: CacheItemPriority.Default, CacheItemRemoved);
13: keyAry[0] := 'Item2';
14: Cache.Insert('Item3', 'Item 3', CacheDependency.Create(nil, keyAry),
15: System.DateTime.Now.AddSeconds(10), Cache.NoSlidingExpiration,
16: CacheItemPriority.Default, CacheItemRemoved);
17: end;
18: end;
19:
20: procedure TWebForm1.CacheItemRemoved(Key: System.String; Value: TObject;
21: Reason: CacheItemRemovedReason);
22: begin
23: FItemArray := Application['Log'] as ArrayList;
24: if FItemArray = nil then
25: begin
26: FItemArray := ArrayList.Create;
27: Application['Log'] := FItemArray;
28: end;
29: FItemArray.Add(Key+': '+Enum(Reason).ToString( ));
30: end;
31:
32: procedure TWebForm1.btnRemove_Click(sender: System.Object;
33: e: System.EventArgs);
34: begin
35: Cache.Remove('Item2');
36: btnGetLog_Click(nil, nil);
37: end;
38:
39: procedure TWebForm1.btnGetLog_Click(sender: System.Object;
40: e: System.EventArgs);
41: begin
42: if Application['Log'] < > nil then
43: begin
44: DataGrid1.DataSource := Application['Log'];
45: DataGrid1.DataBind;
46: DataBind;
47: end;
48: Label1.Text := 'Items cached: '+Cache.Count.ToString
49: end;

u Localize o código no CD: \Code\Chapter 33\Ex07\.

Nesse exemplo, as linhas 7–16 inserem três itens à classe Cache. O item inserido na li-
nha 14 (Item3) também é associado com Item2 por meio de uma dependência. Isso signifi-
ca que quando Item2 é alterado ou removido, Item3 será removido de Cache.
Gerenciamento de estado em aplicações ASP.NET 825

As linhas 20–30 mostram o método de retorno utilizado quando itens são removi-
dos de Cache. Esse método adiciona o nome de item e a razão a um ArrayList armazenado
no objeto HttpApplicationState. Essa lista é utilizada pelo handler de evento btnGet-
Log_Click( ), que vincula o ArrayList a um DataGrid. O handler de evento btnRemove_Click( )
remove Item2 de Cache e chama o método btnGetLog_Click( ) para exibir os resultados.
Como Item3 é dependente de Item2, remover Item2 também remove Item3. Isso pode ser ve-
rificado na Figura 33.2.

FIGURA 33.2 Resultados do CacheItemRemoveCallback.

Gerenciamento de estado em aplicações ASP.NET


O gerenciamento de estado está relacionado ao cache – enquanto o cache mantém o es-
tado global para múltiplos clientes, o estado de sessão mantém o estado para um único
cliente. As aplicações Web são sem estado; portanto, inerentemente elas não monitoram
as informações de usuários entre solicitações. Cada solicitação é vista como uma solicita-
ção completamente distinta não relacionada a solicitações anteriores.
O ASP.NET fornece vários níveis em que o estado pode ser gerenciado. Estes são
cookies, ViewState, Session e Application.
As seções a seguir abordam cada um desses diferentes mecanismos de gerenciamen-
to de estado.

Gerenciando estados com cookies


Cookies são basicamente texto que o servidor Web pode colocar no navegador do cli-
ente. Eles são transferidos via cabeçalhos HTTP. À medida que o usuário visita várias
páginas dentro de um site ou aplicação Web, o servidor pode examinar o conteúdo des-
ses cookies. Um cookie é associado ao domínio do servidor que iniciou sua criação. Por-
tanto, um cookie nunca pode ser transferido para outros domínios. Um cookie é tem-
porário quando dura somente na sessão atual. Ele também pode persistir por múltiplas
826 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

sessões. Esse é um dos mecanismos que o servidor pode utilizar para manter as informa-
ções do estado.

Criando um cookie
Criar um cookie é simples. O próprio cookie é encapsulado pela classe HTTPCookie definida
no namespace System.Web.HttpCookie. O código a seguir mostra como criar um cookie cujo
valor contém o texto inserido a partir de um controle TextBox:

var
MyCookie: HttpCookie;
begin
MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
Response.Cookies.Add(MyCookie);
Response.Redirect('WebForm2.aspx')
end;

u Localize o código no CD: \Code\Chapter 33\Ex08\.

Esse código cria um cookie com o nome 'MyName' e o adiciona à coleção de cookies no
objeto HttpResponse. Isso é como o servidor informa o navegador a manter o cookie espe-
cificado. Para ilustrar como o cookie torna-se disponível em uma solicitação separada, o
usuário é redirecionado para uma outra página que obterá o cookie.

Obtendo valores de cookies


Quando o navegador faz uma solicitação a um servidor Web, ele envia junto sua coleção
de cookies para esse domínio. Eles podem ser obtidos por meio da coleção HttpRe-
quest.Cookies, como mostrado aqui:

procedure TWebForm2.Page_Load(sender: System.Object; e: System.EventArgs);


begin
if Request.Cookies['MyName'] < > nil then
Label1.Text := System.String.Format('Hello {0}, Welcome to the site',
Request.Cookies['MyName'].Value)
else
Label1.Text := 'I don''t know you';
end;

u Localize o código no CD: \Code\Chapter 33\Ex08\.

Esse código demonstra como o cookie foi transferido pelo navegador como parte da
solicitação. O servidor pode então obter o valor do cookie e, nesse exemplo, utilizá-lo
como parte de uma string exibida para o usuário.

Criando cookies persistentes


Um cookie persistente é aquele que o navegador coloca na unidade de disco do usuário em
um diretório que o navegador conhece. O servidor pode iniciar isso adicionando uma data
de expiração à propriedade HTTPCookie.Expires. O código a seguir ilustra esse procedimento:
Gerenciamento de estado em aplicações ASP.NET 827

var
MyCookie: HttpCookie;
begin
MyCookie := HttpCookie.Create('MyName', TextBox1.Text);
if cbxRemember.Checked then
MyCookie.Expires := System.DateTime.Today.AddDays(30);
Response.Cookies.Add(MyCookie);
Response.Redirect('WebForm2.aspx')
end;

Nesse exemplo um CheckBox no Web Form determina se o servidor instruirá o navega-


dor a criar um cookie persistente. Se instruir, o cookie será configurado para expirar em
30 dias a partir da data atual.
Agora, suponha que um usuário fosse revisitar um site com esse código depois de
adicionar um cookie persistente. O código na Listagem 33.7 demonstra como utilizar o
valor de cookie para apresentar uma mensagem de boas-vindas e para pré-preencher
TextBox de modo que o usuário não precise reinserir seu nome.

LISTAGEM 33.7 Utilizando um cookie para pré-preencher controles


procedure TWebForm1.Page_Load(sender: System.Object; e: System.EventArgs);
begin
if not IsPostBack then
if not Request.Browser.Cookies then
lblNoCookie.Text := 'Your browser does not support cookies.'
else begin
if Request.Cookies['MyName'] < > nil then
begin
lblWelcome.Text := System.String.Format('Welcome back {0}',
Request.Cookies['MyName'].Value);
TextBox1.Text := Request.Cookies['MyName'].Value;
end;
end;
end;

u Localize o código no CD: \Code\Chapter 33\Ex08\.

Para excluir um cookie da máquina do usuário, você pode fazer uma de duas coisas:
— Você pode adicionar um outro cookie com o mesmo nome da sessão em que está
baseado; sem nenhuma atribuição à propriedade Expires.
— Você pode adicionar um cookie com System.DateTime.Now atribuído à propriedade
Expires, o que resulta na remoção do cookie da máquina do usuário.

Desvantagens dos cookies


Embora convenientes e facilmente implementados, cookies apresentam desvantagens.
Eles só podem conter um pequeno volume de dados, 4 KB especificamente. Além disso,
828 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

eles só podem armazenar dados de string. Portanto, você teria de converter os dados
como datas, inteiros, flutuantes e assim por diante em strings antes de armazená-los nos
cookies. Os cookies também são dependentes de navegador e alguns navegadores não os
suportam. Por fim, eles requerem que o usuário permita que sua aplicação armazene ar-
quivos na máquina dele. Cookies têm uma reputação ruim porque consomem espaço
nas máquinas dos usuários sem que eles saibam. Portanto, muitos usuários optam por
desativar o suporte a cookie nos navegadores.
A utilização de cookies é excelente em algumas circunstâncias. Entretanto, quando
se trata de gerenciar estado em cenários mais complexos, o ASP.NET tem um outro meio
de fazer isso, discutido a seguir.

Trabalhando com ViewState


ViewState é o componente de monitoramento de informações de páginas ASP.NET como
propriedades de controle de servidor. Ele é tratado automaticamente; em geral, você não
faz nada com ele e, se não precisar dele, poderá desativá-lo. Ele merece ser discutido aqui
porque há coisas que você pode fazer para melhorar o desempenho das suas aplicações
Web realizando ajustes em ViewState. Adicionalmente, você pode utilizar esse state bag
para armazenar informações personalizadas para essas viagens de ida e volta.
Eis como ViewState funciona. Ao preencher um formulário Web, os dados inseridos
no formulário são enviados ao servidor como parte dos comandos POST/GET. O servidor,
por sua vez, empacota essas informações em um campo oculto, ViewState, e as envia de
volta ao cliente junto com o restante da resposta. O cliente não faz nada com essas infor-
mações. ViewState é fornecido para o cliente de modo que ele seja enviado de volta ao ser-
vidor a fim de ser utilizado nas solicitações subseqüentes. Isso é chamado viagem de ida e
volta (round-tripping). O caso simples é o servidor que utiliza as informações para confi-
gurar as propriedades de controles em um postback da página.

NOTA
O nome real do campo oculto ViewState é __ViewState. Nesta seção, iremos referenciá-lo sim-
plesmente como ViewState.

Em muitos exemplos que examinei para demonstrar ViewState, é solicitado que você
crie uma aplicação ASP.NET com alguns campos de formulário e um botão Submit. É in-
formado que você preencha os campos e clique no botão Submit. A página será postada
de volta e os campos serão preenchidos com as informações que você inseriu original-
mente. Se clicar no botão Back no navegador, os campos manterão seus valores. Isso
ocorre por meio do ViewState.
É verdade que as propriedades de controles, como a propriedade Text do controle
TextBox, são adicionadas ao ViewState. Entretanto, as propriedades de controle enviadas ao
servidor como parte do comando POST são utilizadas para gerar a HTML na resposta em
vez desses valores contidos no ViewState. Você pode ver isso desativando ViewState para a
página inteira de uma aplicação semelhante ao processo recém-descrito. Você verá que
os controles retêm seus valores. Em outras palavras, a página continua com estado, mes-
mo sem o ViewState.
Gerenciamento de estado em aplicações ASP.NET 829

Então por que se incomodar com ViewState? Lembre-se de que os valores que o servi-
dor utiliza, passados como parte do comando POST, inicializam os controles dos dados ao
gerar a HTML. As únicas propriedades que são inicializadas dessa maneira são aquelas in-
cluídas no comando POST. Outras propriedades contam com ViewState. Por exemplo, con-
sidere a página mostrada na Figura 33.3.

u Localize o código no CD: \Code\Chapter 33\Ex09\.

Imagine que isso seja um formulário de pesquisa do cliente. O botão Lookup Custo-
mer é desativado por padrão. O handler de evento Page_Load( ) contém o código a seguir:

if not IsPostBack then


btnLookup.Enabled := LoggedIn;

LoggedIn( ) simplesmente retorna True. Ele simula algum formulário de teste para au-
tenticação de usuário.
Com ViewState ativado na página, a página funciona como esperado. Quando o
usuário visita a página pela primeira vez, btnLookup.Enabled é configurado como True. Ao
clicar no botão Lookup Customer, ele retém seu estado ativado. Entretanto, ao desati-
var ViewState na página, clicar no botão Lookup Customer mostra que ele será desativa-
do no postback. Isso ocorre porque o método LoggedIn( ) não é invocado, visto que isso
é um postback. O servidor gera a HTML para o botão com base nos valores das suas pro-
priedades em tempo de design. O servidor não tem nenhuma informação nas proprie-
dades do comando POST, nem há nenhuma informação sobre o estado em ViewState a
partir do qual ele possa determinar um diferente valor de propriedade para a proprieda-
de Enabled do botão.
Você pode ver onde ViewState serve a um bom propósito. Entretanto, você realmente
não precisa de ViewState. É recomendável desativar ViewState em todas as páginas que não

FIGURA 33.3 Exemplo de um formulário de pesquisa do cliente.


830 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

precisam dele. Isso evitará perda de desempenho, resultando em bytes extras marcados
nos seus documentos HTML.

Desativando ViewState na página


Você pode desativar ViewState para uma página inteira adicionando-o à diretiva @ Page.
Isso é mostrado aqui:

<%@ Page language="c#" EnableViewState="False" Codebehind="WebForm1.pas"


➥AutoEventWireup="True" Inherits="WebForm1.TWebForm1" %>

Desativando ViewState para um controle


Você pode desativar ViewState para um controle específico simplesmente configurando
sua propriedade EnableViewState como False no Object Inspector. Você também pode edi-
tar o arquivo .aspx diretamente, como mostrado aqui:

<asp:button id=btnLookup
style="Z-INDEX: 9; LEFT: 222px; POSITION: absolute;
TOP: 206px" runat="server" enableviewstate="False"
enabled="False" text="Lookup Customer">
</asp:button>

Adicionando valores ao state bag


Se lembrar da seção sobre cookies, alguns navegadores não suportam cookies ou o recur-
so foi desativado pelo usuário. Você pode armazenar as mesmas informações no ViewSta-
te adicionando-as ao state bag (ou a propriedade ViewState de cada controle). Adicionar
um valor ao state bag é simples, como mostrado na linha a seguir:

ViewState.Add('MyData', 'MyDataText');

Isso adiciona o item 'MyDataText' derivado a partir da string 'MyData'. Para referenciar
um item no state bag, simplesmente indexe-o pela sua chave de string:

Response.Write(ViewState['MyData']);

u Localize o código no CD: \Code\Chapter 33\Ex10\.

NOTA
Diferentemente dos cookies, que só podem armazenar strings, ViewState suporta o armazena-
mento de objetos arbitrários.

Gerenciamento de estado de seção


O gerenciamento de estado de sessão ocorre durante uma visita do usuário a um site. Em
geral, esse gerenciamento começa depois que usuário visita o site e termina quando o
usuário sai do site.
Quando um usuário entra em um site pela primeira vez, o ASP.NET cria uma sessão
única para esse usuário. Essa sessão é uma instância da classe HttpSessionState. A sessão con-
Gerenciamento de estado em aplicações ASP.NET 831

some certa quantidade de memória para esse usuário. Além disso, é dado ao usuário um ID
único, o qual é passado para o navegador do usuário e retornado ao servidor em cada soli-
citação durante a execução da sessão. Por padrão, tudo isso ocorre via um cookie.
A sessão permanece na memória até o usuário sair do site ou até a sessão expirar,
que, por padrão, ocorre depois de 20 minutos de inatividade.
Você pode armazenar as informações na instância da classe HttpSessionState que
pode ser obtida nas solicitações subseqüentes.
As informações sobre a sessão são mantidas por um provedor de estado de sessão.
Esse provedor é executado em um de três modos: InProc, StateService ou SQLServer.
— InProc – Os dados de sessão são mantidos dentro do mesmo domínio da aplicação
ASP.NET. Ele está dentro do contexto de aspnet_wp.exe. Essa é a configuração pa-
drão.
— StateServer – Os dados de sessão são mantidos dentro do contexto de um asp-
net_state.exe do Windows NT Service. Esse serviço pode ser executado na mesma
máquina ou em uma diferente.
— SQLServer – Os dados de sessão são mantidos em um banco de dados SQL Server
predefinido.
— Off – O estado de sessão está desativado.

Armazenando e obtendo informações com o objeto Session


O código a seguir demonstra como adicionar dados ao objeto Session:

Session.Add('UserName', TextBox1.Text);

Para obter essas mesmas informações, você emitiria uma instrução como

Response.Write('Welcome '+Session['UserName'].ToString);

Você pode adicionar qualquer objeto à classe Session. Por exemplo, o código a seguir
adiciona um DataSet à classe Session:

Session.Add('MyData', DataSet1);

Para remover um item do objeto Session, simplesmente chame seu método Remove( )
desta maneira:

Session.Remove('MyData');

Alterando o tempo limite padrão da sessão


Você pode alterar o tempo limite padrão da sessão modificando o arquivo web.config. O
tempo limite padrão é 20 minutos. A modificação a seguir em web.config configura o tem-
po limite como 60 minutos:

<configuration>
<system.web>
832 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

<sessionState timeout="60"/>
</system.web>
</configuration>

Criando sessões sem cookies


Anteriormente afirmamos como o SessionID é passado entre o servidor e o navegador via
um cookie. Ao discutir cookies neste capítulo, indicamos algumas desvantagens dos
cookies, como quando o usuário desativa o suporte de cookie no navegador. Isso torna-
ria o cookie de Session inutilizável para um site ASP.NET. Portanto, podemos alterar a ma-
neira como o ASP.NET transfere o SessionID. Isso necessariamente implica modificar o ar-
quivo web.config, como mostrado aqui:

<configuration>
<system.web>
<sessionState cookieless="true" />
</system.web>
</configuration>

Quando isso é feito, o SessionID é passado como parte da URL. Isso é chamado cookie
munging. Uma URL com o SessionID se pareceria com isto:

http://www.xapware.com/SessionEx/(kxn1f555r4xgbe45jlr4wcyf)/WebForm1.aspx

O SessionID é a parte em negrito.


A utilização dessa técnica apresenta algumas desvantagens. Primeira, você não pode
colocar links absolutos para páginas dentro do seu site. Todos os links devem ser relati-
vos à página atual. Se você aceitar isso, é uma excelente maneira de evitar as limitações
de cookies no lado do cliente. Segunda, reduz a utilidade do cache no lado do cliente e do
proxy de páginas HTML completas. Por exemplo, as URLs mudam em cada sessão, assim
as páginas armazenadas em cache ontem não serão utilizadas hoje.

Armazenando dados de sessão em um servidor de estado de sessão


As aplicações ASP.NET, por padrão, utilizam Session State Server no modo in-process. Isso
associa as informações de sessão diretamente à aplicação ASP.NET pelo fato de ambas es-
tarem em execução no mesmo processo. Se a aplicação ASP.NET fosse desligada, todas as
informações de sessão seriam perdidas. Essa é a desvantagem do modo InProc. A vanta-
gem é o desempenho. Nesse sentido, com as informações de sessão existindo dentro do
mesmo processo e máquina, a obtenção de dados é mais rápida. A configuração de
web.config a seguir mostra a configuração do InProc padrão de Session:

<configuration>
<system.web>
<sessionState mode="InProc"/>
</system.web>
</configuration>
Gerenciamento de estado em aplicações ASP.NET 833

Para configurar um Out-of-Proc Session State Server, o arquivo web.config conteria


algo semelhante a

<configuration>
<system.web>
<sessionState mode="StateServer"
stateConnectionString="tcpip=192.168.0.20:42424"/>
</system.web>
</configuration>

tcpip refere-se ao endereço IP da máquina que hospeda o servidor de sessão de esta-


do. Neste exemplo, a porta utilizada é 42424. Você pode alterar esse número e certificar-se
de que essa porta não seja utilizada por nenhum outro processo na máquina.
Para iniciar o servidor de sessão de estado na máquina que irá hospedá-lo, você sim-
plesmente emite net start aspnet_state na linha de comando, como mostrado aqui:

C:\>net start aspnet_state


The ASP.NET State Service service is starting.
The ASP.NET State Service service was started successfully.
C:\>

Armazenando as informações de sessão fora do processo, você tem o benefício de re-


duzir as probabilidades de os dados da sessão serem perdidos. Se a aplicação ASP.NET ou
se o servidor Web estivessem desligados, é mais provável que as informações de sessão se-
riam retidas pelo servidor de sessão de estado em uma outra máquina. Mais uma vez,
aqui, o desempenho é reduzido e não apenas por causa da transferência de dados pela
rede, mas também por causa das operações de serialização e desserialização que devem
acontecer.

Armazenando dados de sessão no SQL Server


É possível armazenar as informações de sessão no SQL Server utilizando um conjunto de
tabelas predefinidas. Essa abordagem apresenta a melhor robustez, mas também o me-
nor desempenho. Entretanto, para aplicações que precisam de failover robusto, essa é a
melhor opção porque é possível tirar proveito da clusterização de banco de dados para li-
dar com falhas do banco de dados.
Para configurar o armazenamento das informações de estado no SQL Server, você
deve
1. Criar o banco de dados predefinido no SQL Server.

2. Configurar o arquivo web.config a fim de que ele aponte para o SQL Server.

Criando o banco de dados de estado no SQL Server


Esse primeiro passo envolve a execução do Enterprise Manager e também de um script
pronto para criar o banco de dados e as tabelas necessárias. Há dois conjuntos de
scripts:
834 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

— InstallSqlState.sql – Cria os bancos de dados ASPState e TempDB. As informações


do estado são mantidas no banco de dados TempDB, que apenas armazena essas
informações temporariamente. Se o SQL Server estiver desligado, os dados serão
perdidos.
— UninstallSqlState.sql – Desinstala o banco de dados criado com InstallSqlState.sql.

— InstallPersistSqlState.sql – Instala o banco de dados ASPState. Essa versão do ban-


co de dados armazena as informações do estado no mesmo banco de dados e, por-
tanto, os dados do estado tornam-se persistentes.
— UninstallPersistSqlState.sql – Desinstala o banco de dados criado com InstallPer-
sistSqlState.sql.

Você encontrará esses scripts no diretório a seguir:

%SystemRoot%\Microsoft.NET\Framework\[Framework Version]\

Dependendo de qual script de instalação optou por executar, você possivelmente


encontrará os bancos de dados ASPState e TempDB no SQL Server por meio do Enterprise
Manager.

Modificando web.config para gerenciamento do estado SQLServer


Depois do seu banco de dados ser configurado para gerenciamento de estado, você preci-
sa modificar o arquivo web.config a fim de que a aplicação ASP.NET aponte para o banco
de dados. O web.config deve ser semelhante ao mostrado aqui:

<configuration>
<system.web>
<sessionState mode="SQLServer"
sqlConnectionString="data source=localhost;user id=sa;pwd=somepwd" />
</system.web>
</configuration>

Observe a configuração do atributo mode para SQLServer. Além disso, você verá as in-
formações sobre a conexão, se uma conexão puder ser feita ao banco de dados.

Eventos de sessão
Há dois eventos relacionados a Sessions que você pode tratar. Eles são os eventos Session_
Start e Session_End. O evento Session_Start ocorre quando um usuário visita o site pela pri-
meira vez. O evento Session_End ocorre quando o usuário sai do site ou quando a sessão
expira. Os dois eventos são declarados na classe TGlobal. Essa classe está localizada no ar-
quivo Global.pas incluído no seu projeto.
Uma das maneiras de utilizar esses eventos é manter uma contagem de usuários no
seu site. Quando um usuário faz uma visita, você incrementa a contagem. Quando um
usuário sai, você decrementa a contagem. A Listagem 33.8 mostra como você poderia fa-
zer isso.
Gerenciamento de estado em aplicações ASP.NET 835

LISTAGEM 33.8 Armazenando uma contagem de usuário em Session_Start


procedure TGlobal.Session_Start(sender: System.Object; e: EventArgs);
begin
Application.Lock;
try
if Application['NumUsers'] = nil then
Application['NumUsers'] := System.Object(Integer(1))
else
Application['NumUsers'] :=
System.Object(Integer(Application['NumUsers'])+1);
finally
Application.UnLock;
end;
end;

u Localize o código no CD: \Code\Chapter 33\Ex12\.

Inversamente, você decrementaria a contagem de usuário no evento Session_End


como mostrado na Listagem 33.9.

LISTAGEM 33.9 Armazenando uma contagem de usuário em Session_End


1: procedure TGlobal.Session_End(sender: System.Object; e: EventArgs);
2: begin
3: Application.Lock;
4: try
5: if Application['NumUsers'] < > nil then
6: Application['NumUsers'] :=
7: System.Object(Integer(Application['NumUsers'])-1)
8: else
9: Application['NumUsers'] := System.Object(Integer(0));
10: finally
11: Application.UnLock;
12: end;
13: end;

u Localize o código no CD: \Code\Chapter 33\Ex12\.

Talvez você tenha notado que esses dois handlers de evento utilizam o objeto Appli-
cation, que discutiremos na seção a seguir.

Gerenciamento de estado da aplicação


O estado de aplicação é diferente do estado de sessão pelo fato de que os dados armazena-
dos no nível da aplicação estão disponíveis para todos os usuários dessa aplicação. No es-
tado de sessão os dados da sessão são armazenados somente para o usuário dessa sessão.
A Figura 33.4 mostra essa diferença.
836 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

O estado de aplicação é mantido pela classe HttpApplicationState. Essa classe é um di-


cionário criado na primeira solicitação à aplicação. Isso é diferente da classe HttpSession-
State, que é criada em cada visita do usuário ao site.
A classe HttpApplicationState funciona quase como a classe HttpSessionState.
As informações que você armazenaria na classe HttpApplicationState precisam estar
disponíveis para todos os usuários das aplicações. Por exemplo, strings de conexão ao
banco de dados e o número de usuários que assinaram são exemplos de informações no
nível da aplicação.

Gerenciamento de
estado de sessão

Sessão 1
Usuário 1 var 1
var 2

Sessão 2
Usuário 2 var 1
var 2

Sessão 3
Usuário 3 var 1
var 2

Gerenciamento de
estado da aplicação

Usuário 1

Aplicação
var 1
Usuário 2
var 2
var 3

Usuário 3

FIGURA 33.4 A diferença entre estado de aplicação e de sessão.


Gerenciamento de estado em aplicações ASP.NET 837

Armazenando informações com o objeto Application


Você pode adicionar, acessar e remover dados de maneira semelhante a como você faz
com a classe HttpSessionState.
Para adicionar um item, simplesmente faça isto:

Application['NumUsers'] := System.Object(Integer(1));

Para remover um item, chame a função HttpApplicationState.Remove( ) desta maneira:

Application.Remove('MyItem');

Acessar um item é igualmente simples:

MyItem := Application['MyItem'];

Você pode limpar o conteúdo do objeto Application chamando seu método


RemoveAll( ):

Application.RemoveAll;

Sincronização de acesso aos dados de estado no objeto Application


As operações da classe HttpApplicationState são thread-safes, mas se pretende realizar um
grupo de operações, é recomendável bloquear acesso de gravação ao estado de aplicação
até ter concluído seu conjunto de operações. As Listagens 33.8 e 33.9 ilustram a utiliza-
ção dos métodos Application.Lock( ) e Application.UnLock( ) para bloquear e desbloquear
acesso de gravação aos dados mantidos pelo objeto Application.
Observe que é necessário corresponder cada chamada a Lock( ) com uma chamada a
UnLock( ) que é protegida por uma cláusula try-finally.
Fazendo isso, você pode evitar acesso concorrente, que pode causar impasses, condi-
ções de concorrência e outros problemas.

Utilização de Cache versus Application


Poderia parecer que as classes Cache e HttpApplicationState são bem semelhantes. As duas
têm a capacidade de armazenar dados em um contexto no nível da aplicação e a sintaxe
para lidar com elas é basicamente idêntica. As diferenças, porém, são enormes.
As classes Cache e HttpApplicationState fornecem um mecanismo para armazenar da-
dos no nível da aplicação e, por causa disso, podem ser utilizadas para gerenciar o estado.
Aqui é onde acabam as semelhanças.
A classe Cache leva o gerenciamento desses dados ainda mais longe que o da
HttpApplicationClass.
Primeiro, acesso a dados na classe Cache é completamente thread-safe. Isso é diferen-
te da classe HttpApplicationState que requer que você envolva o acesso aos dados com os
métodos de sincronização Lock( ) e UnLock( ).
Segundo, a classe Cache, baseada em uma estrutura de priorização, pode liberar dados
em Cache quando não forem utilizados, a fim de liberar memória se os recursos estiverem
baixos.
838 Capítulo 33 Cache e gerenciamento de estados em aplicações ASP.NET

Além disso, você consegue maior controle sobre os itens adicionados à classe Cache
configurando diretivas absolutas e variáveis de expiração.
Por último, você pode associar itens à classe Cache com outros itens armazenados em
cache ou com um arquivo, o que resultará na remoção dos itens no cache da Cache.
A classe HttpApplicationState é apropriada como um armazenamento geral de estado
para as informações que precisam estar disponíveis no nível da aplicação e precisam exis-
tir durante a vida da aplicação.
A classe Cache é mais adequada para gerenciamento de estado complexo em que é
exigido maior controle sobre os dados armazenados em cache.
NESTE CAPÍTULO
CAPÍTULO 34 — Controles de usuário

— Controles Web
Desenvolvendo
controles ASP.NET
Server personalizados
por Nick Hodges

Um dos excelentes recursos do ASP.NET é a capacidade


de criar componentes poderosos para uso nos seus sites
Web. Há muito tempo os desenvolvedores Delphi
conhecem o poder e a conveniência da utilização de
componentes nas aplicações Windows. Agora, com o
ASP.NET, você pode incorporar essas habilidades de
construção de componentes ao mundo do
desenvolvimento de aplicações Web. A Framework Class
Library (FCL) fornece um rico framework de recursos
para desenvolver componentes personalizados que farão
quase qualquer coisa que os limites de HTML e JavaScript
permitem fazer. Desenvolvedores Web não precisam
mais invejar os desenvolvedores Windows, que têm um
conjunto poderoso e rico de controles para fim de
construir aplicações com uma aparência profissional.
Há dois principais tipos de controles que você pode
construir para suas aplicações Web. O primeiro tipo são os
controles de usuário. Controles de usuário são simples de
construir e utilizar nas suas páginas Web. Eles funcionam
de maneira muito semelhante às páginas normais e
permitem construir seções de um site Web utilizando o
designer de página WYSIWYG (What You See Is What You
Get). Se você está familiarizado com frames na VCL, então
já entende o que é um controle de usuário.
O outro tipo de componente ASP.NET são os
controles Web (Web Controls). Os controles Web são mais
parecidos com os componentes Delphi tradicionais. Eles
são instalados na paleta de componentes, arrastados e
soltos nos seus formulários como componentes regulares.
Este capítulo focaliza duas áreas: o
desenvolvimento e a utilização de controles Web e
controles de usuário.
840 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Controles de usuário
Esta seção discute os controles de usuário e mostra como construir e adicionar um con-
trole de usuário a uma página Web – ambos via a IDE e no código.
Um controle de usuário é um controle do lado do servidor que você projeta no desig-
ner de página e salva como um arquivo de texto com a extensão *.ascx. No Delphi for
.NET, um controle de usuário consiste no arquivo *.ascx e em um arquivo *.pas associado
que será compilado na DLL resultante com base no ASP.NET. A página *.ascx é implanta-
da e controlada junto com a DLL no servidor, dessa forma você como um desenvolvedor
pode gerenciar o controle e fazer alterações conforme necessário e desejado.
Os controles de usuário são inseridos em outras páginas via o próprio designer ou
manualmente por meio de adições ao código. O controle de usuário simplesmente tor-
na-se parte da página à qual é adicionado, como ocorre com qualquer outro controle. Se
pensar nos controles de usuário simplesmente como um fragmento de HTML que é “in-
jetado” em uma área específica de uma página Web, você tem uma boa idéia de como
eles funcionam.
Normalmente, uma página Web em um site Web consistirá em coleções de contro-
les de usuário agrupadas que criam uma página completa. Itens, como painéis esquerdo
e direito, cabeçalhos e rodapés e o corpo principal das páginas podem ser configurados
como controles de usuário e então agrupados para criar uma única página. Como os con-
troles de usuário são “fragmentos” discretos de código HTML, você pode gerenciar me-
lhor um site como uma coleção de controles de usuário em vez de um conjunto de pági-
nas monolíticas. Os controles de usuário dividem um site em seções discretas que podem
ser mais bem controladas e gerenciadas.
Você deve também observar que os controles de usuário são mais poderosos do que
simplesmente uma inclusão no lado do servidor. Os controles de usuário podem conter
código anexado que os torna mais robustos do que simplesmente inserir HTML em uma
página. Os controles de usuário podem conter qualquer quantidade de HTML, outros
controles de usuário e controles Web. Todos os controles podem conter código anexado,
aumentando a funcionalidade de HTML simples para componentes completos com to-
dos os recursos. Os controles de usuário representam uma instância real de uma classe
com código e dados anexados.

Um controle muito simples de usuário


Criar um controle de usuário no Delphi for .NET não poderia ser mais simples. Primeiro,
crie uma nova aplicação ASP.NET selecionando File, New, ASP.NET Web Application.
Nomeie a aplicação “SimpleUserControl”. Depois de salvar a aplicação e estar pronto para
prosseguir, selecione File, New, Other e então o item Delphi ASP Files na TreeView à es-
querda. Em seguida, na ListView à direita, selecione ASP.NET User Control e clique em
OK.
A IDE agora criará um controle de usuário padrão. O arquivo WebUserControl1.ascx é
criado e acompanhado por WebUserControl1.pas, o arquivo Pascal que representa e define a
classe do seu controle de usuário. Agora, selecione File, Save As e salve o arquivo do con-
trole de usuário com o nome ‘ucSimpleControl’. Isso irá alterar o nome do arquivo *.ascx e
do arquivo *.pas.
Controles de usuário 841

Agora, o que você tem deve ser algo semelhante ao mostrado na Figura 34.1.
Você agora tem um controle de usuário vazio pronto para ser desenvolvido. Daqui
você pode construir o controle utilizando todas as funcionalidades do designer, da mes-
ma maneira como faria para criar uma página completa. Você pode adicionar HTML e
controles Web e editar a página *.ascx diretamente. A IDE gerenciará o controle para
você, atualizando o código dos dois arquivos associados.
Para efeitos de simplicidade, vamos apenas adicionar algum texto simples ao contro-
le. Clique no designer e digite, diretamente no designer, isto:

This is a simple user control.

Agora, volte à página WebForm1 e adicione o texto a seguir:

This page demonstrates how to use and display a simple user control

FIGURA 34.1 A IDE exibindo um controle de usuário vazio.

Clique em Return depois de digitar isso e então selecione Insert, Insert User Control
na barra de menu. Na caixa de diálogo, clique em Browse e selecione o controle ucSimple-
File.ascx. Sua caixa de diálogo deve se parecer àquela mostrada na Figura 34.2.

FIGURA 34.2 A caixa de diálogo Insert User Control com o controle de usuário selecionado.
842 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Depois de clicar em OK, a IDE irá inserir o controle de usuário na página, represen-
tado-o via uma pequena caixa cinza com a palavra UserControl, seguida pelo valor da
propriedade ID (ou nome). Nesse ponto, você pode selecionar o controle no designer e
configurar algumas das suas propriedades no Object Inspector. Por enquanto, altere
sua propriedade ID para SimpleUserControl. Seu formulário agora ficará parecido com o
mostrado na Figura 34.3.

FIGURA 34.3 Configurando propriedades no controle de usuário.

Depois de executar a aplicação você verá o controle de usuário na página resultante


no Internet Explorer, como mostrado na Figura 34.4.

Examinando o controle simples


Agora, podemos examinar precisamente o que está acontecendo por trás do nosso con-
trole de usuário. Primeiro, examine o arquivo ucSimpleControl.pas. O código aí declara
uma classe para o controle de usuário como mostrado na Listagem 34-1.
Controles de usuário 843

FIGURA 34.4 O controle de usuário incorporado à página e mostrado no Internet


Explorer.

LISTAGEM 34.1 A declaração simples de controle


1: type
2: TWebUserControl1 = class(System.Web.UI.UserControl)
3: strict private
4: procedure InitializeComponent;
5: strict private
6: procedure Page_Load(sender: System.Object; e: System.EventArgs);
7: strict protected
8: procedure OnInit(e: System.EventArgs); override;
9: private
10: { Private Declarations }
11: public
12: { Public Declarations }
13: end;

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

Observe que a classe descende da classe System.Web.UI.UserControl (linha 2). Essa classe
é a base de todos os controles de usuário. Ela também declara um handler de evento
Page_Load( ) (linha 6), que você pode implementar a fim de realizar qualquer inicializa-
ção do controle de usuário. (A seção a seguir discute como fazer isso.) Você pode, natural-
mente, adicionar métodos, campos e propriedades a essa classe como faria com qualquer
outra classe Delphi regular.
A outra metade do controle é o arquivo *.ascx. O arquivo deve se parecer com isto:

<%@ Control Language="c#" AutoEventWireup="false"


➥Codebehind="ucSimpleControl.pas"
➥Inherits="ucSimpleControl.TWebUserControl1"%>

This is a simple user control.


844 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

A primeira linha é um tag ASP.NET específico que define o controle. O restante do


código nessa unit é simplesmente HTML normal. Você pode editar esse código como de-
sejado e suas alterações serão refletidas na IDE e na própria aplicação. O código também
pode conter tags normais de controle ASP.NET, como veremos ao construir um controle
um pouco mais complexo na seção a seguir.
A Tabela 34.1 descreve os atributos da tag <@ Control %>.

TABELA 34.1 Atributos <@ Control @>


Atributo Descrição

Language Essa tag define a linguagem de criação de scripts que a página utilizará. O
Delphi for .NET não inclui um compilador de linguagem de criação de scripts
Delphi, assim o padrão é configurado como C#.
AutoEventWireup Esse atributo determina se os handlers de evento no seu código são
automaticamente “conectados” aos delegates associados. Para aplicações
desenvolvidas no Delphi for .NET, a IDE gera o código no arquivo *.pas para
automaticamente associar o código que trata o evento, portanto esse
parâmetro sempre deve ser configurado como False.
CodeBehind Esse atributo é utilizado em tempo de projeto para assegurar que a IDE do
Delphi for .NET conecte adequadamente o arquivo *.aspx ao código do
arquivo *.pas correto.
Inherits Esse atributo nomeia a classe que define o código para o controle de usuário.

Observe que essa tabela descreve apenas os atributos existentes no código. Consulte
a documentação do .NET SDK para uma descrição completa dos atributos que podem ser
utilizados na tag <@ Control @>.
Também importante no uso de um controle de usuário são os tags e outros códigos
adicionados à página que utiliza o controle. Nesse momento, o arquivo WebForm1.aspx
deve ser semelhante ao mostrado na Listagem 34.2.

LISTAGEM 34.2 Arquivo BasicUserControlPage.aspx


1: <%@ Page language="c#" Debug="true" Codebehind="BasicUserControlPage.pas"
[ic.ccc]AutoEventWireup="false" Inherits="BasicUserControlPage.TWebForm2" %>
2: <%@ Register TagPrefix="uc1" TagName="BasicUserControl"
[ic.ccc]Src="BasicUserControl.ascx" %>
3: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
4:
5: <html>
6: <head>
7: <title></title>
8: <meta name="GENERATOR" content="Borland Package Library 7.1">
9: </head>
10:
11: <body ms_positioning="GridLayout">
12: <form runat="server">
Controles de usuário 845

LISTAGEM 34.2 Continuação


13: <p>
14: This page demonstrates how to use and display a simple user control.
15: </p>
16: <uc1:basicusercontrol id="UserControl1" runat="server">
17: </uc1:basicusercontrol>
18: </form><br>
19: </body>
20: </html>

u Localize o código no CD: \Code\Chapter 34\ExamplePage\.

A parte chave disso é a tag <% Register %> no cabeçalho do arquivo e a tag ASP.NET no
corpo da tag HTML que representa o próprio controle. A Tabela 34.2 discute o significa-
do dos atributos para a tag <%@ Register %>.

TABELA 34.2 Atributos <%@ Register %>


Nome do atributo Descrição
TagPrefix Esse parâmetro define um prefixo que será utilizado para rotular uma tag
ASP.NET na página *.aspx.
TagName Esse parâmetro fornece o nome do controle que será referenciado pela tag
Prefix.
Src Esse parâmetro nomeia o arquivo em que o código ASP.NET do controle de
usuário reside.

O controle real é adicionado à página *.aspx, como a seguir:

<uc1:ucsimplecontrol id=SimpleUserControl runat="server">


</uc1:ucsimplecontrol>

Observe que a tag que o define inicia com o valor do parâmetro TagPrefix na tag
<%@ Register %> – nesse caso, uc1. Depois disso, o tipo de controle é nomeado – novamen-
te, nesse caso, ucsimplecontrol. O restante da tag é normal, como ocorre com qualquer ou-
tra tag que representa um controle ASP.NET em uma página .aspx.
Portanto, criar, adicionar e utilizar um controle de usuário é bem fácil com o ASP.NET
e o Delphi for .NET. Agora, vamos tentar um exemplo um pouco mais complexo.

Um controle de usuário para login de usuário


Hoje em dia, a maioria dos sites Web requer que os usuários se identifiquem via logon.
Depois de efetuarem o logon, os usuários podem ser identificados e fornecer conteúdo
personalizado, configurações e assim por diante. Isso necessita de um sistema para efetuar
o logon do usuário. Há probabilidades de que você precisará de um pequeno controle
que contém controles TextBoxs para entrada de nome de usuário e senha. Um controle de
usuário é perfeito para isso.
846 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Para construir um controle na interface de login, crie uma nova ASP Application.
Como mostrado anteriormente, adicione um novo Web User Control ao projeto. Salve o
projeto com o nome LoginControlTest. Salve o arquivo do controle de usuário com o nome
'ucLogin.ascx'.
A partir desse ponto, siga os passos a seguir:
1. Na guia Web Controls, em Component Palette, coloque um Panel no controle de
usuário. Configure sua propriedade ID como LoginLabel. Dê um clique duplo no pai-
nel e utilize a tecla de backspace para remover a palavra Panel do controle.

2. Remova a seleção Absolute Position na barra de ferramentas do designer. Isso fará


com que o Panel seja posicionado em relação à esquerda superior de qualquer que
seja a tag posicionada internamente. Observe que os controles dentro do Panel de-
vem permanecer posicionados de maneira absoluta.

3. Coloque dois controles Label e dois TextBoxs na guia Web Controls no Panel. (Não
utilize aqueles na guia HTML.) Observe que esses controles, e quaisquer outros po-
sicionados no Panel, serão “possuídos” pelo Panel – isto é, eles irão se mover junto
com o Panel em vez de independentemente. Configure seus valores da propriedade
ID como UserNameTextBox e PasswordTextBox, respectivamente.

4. Nomeie os controles Label UsernameLabel e PasswordLabel, respectivamente.

5. Configure a propriedade Text do controle Label como Username: e Password:, respecti-


vamente.

6. Configure a propriedade TextMode do controle PasswordTextbox como Password.

7. Organize-os como mostrado na Figura 34.5.

8. Posicione uma caixa de seleção abaixo dos controles TextBox, nomeie esses contro-
les RememberMeCheckbox e configure sua propriedade Text como Remember Me On This Com-
puter.

9. Posicione um botão abaixo da caixa de seleção, altere seu ID para LoginButton e con-
figure sua propriedade Text como Login.
10. Organize os itens da maneira como preferir dentro do LoginPanel. No final, LoginPa-
nel deve ser semelhante à Figura 34.5.

Um controle de usuário pode e tem código por trás dele, da mesma maneira como
uma página da Web normal. Nesse caso, como visto anteriormente, o Delphi cria e ge-
rencia para você uma classe que descende de System.Web.UI.UserControl. Essa classe contém
qualquer código que você quer adicionar ao controle. Por exemplo, você pode adicionar
o método a seguir para tratar do usuário que efetua o logon:

procedure TWebUserControl1.DoLogin(aUsername, aPassword: &string;


aRememberUser: Boolean);
begin
// Executa a lógica de login que sua aplicação exige.
end;
Controles de usuário 847

FIGURA 34.5 Controle de usuário de login no designer.

Você pode então dar um clique duplo no botão Login e adicionar o seguinte ao
handler de evento resultante:

procedure TWebUserControl1.LoginButton_Click(sender: System.Object;


e: System.EventArgs);
begin
DoLogin(UsernameTextBox.Text, PasswordTextBox.Text,
RememberMeCheckbox.Checked);
end;

NOTA
Um outro aspecto a ser observado é a HTML real utilizada pelo controle. Na verdade, a HTML é um
único “fragmento” de código HTML que define um formulário HTML – que, naturalmente, é exa-
tamente o que o controle é. Isso significa que ela deve se ajustar transparentemente dentro de
qualquer outra tag contêiner na página Web em que é inserida.

Agora, seu controle está pronto para ser utilizado e reutilizado em qualquer página
Web na sua aplicação. Você pode adicioná-lo à sua aplicação, onde quer que você prefira,
utilizando o item de menu Insert, Insert User Control. Uma aplicação que mostra como
uma página da Web simples poderia parecer é incluída ao código deste capítulo.
848 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Controles Web
Você viu como controles de usuário podem ser fácil e rapidamente construídos, utiliza-
dos para reutilizar HTML e código Delphi dentro das suas aplicações Web. Entretanto, há
momentos em que os controles de usuário não são suficientes. Normalmente, os contro-
les de usuário são úteis quando você precisa de uma coleção ou composição de controles
HTML e Web. Naturalmente, às vezes você desejará criar controles Web personalizados.
Esta seção abrange construção, instalação e utilização de controles Web.
Assim como controles de usuário eram análogos a desenvolver TFrame personalizados
descendentes ou controles de usuário no WinForms, controles Web são análogos a de-
senvolver descendentes de Tcomponent na VCL. Os controles Web podem ser instalados na
Component Palette e colocados nas páginas Web ou em controles de usuário. Na seção a
seguir desenvolveremos um controle Web muito simples, discutiremos como ele funcio-
na e então gradualmente adicionaremos recursos e funcionalidades a fim de ilustrar as
várias capacidades do modelo de componentes ASP.NET.

Construindo um controle Web muito simples


A Listagem 34.3 mostra o código para um controle Web extremamente simples. Ele só
exibe o que é configurado pela propriedade DisplayText. Entretanto, ilustra os princípios
básicos da construção de um controle Web. Examinaremos esse controle e o utilizaremos
como uma base para outras construções, adicionando funcionalidades enquanto discuti-
mos os recursos do desenvolvimento de controles Web. Observe que todos os códigos
desses componentes podem ser encontrados no CD que acompanha este livro.

LISTAGEM 34.3 Um controle Web simples


1: unit uSimpleTextControl;
2:
3: interface
4:
5: uses
6: System.Web, System.Web.UI, System.ComponentModel;
7:
8: type
9: [DefaultProperty('DisplayText')]
10: TSimpleTextControl = class(System.Web.UI.Control)
11: strict private
12: FDisplayText: string;
13: strict protected
14: procedure Render(Writer: HtmlTextWriter); override;
15: public
16: constructor Create;
17: published
18: function GetDisplayText: string;
19: procedure SetDisplayText(const Value: &string);
20: published
21: [
Controles Web 849

LISTAGEM 34.3 Continuação


22: DefaultValue('<Your Text Here>'),
23: Description('The text that will be displayed in the control.'),
24: Category('Appearance'),
25: Bindable(True)
26: ]
27: property DisplayText: string read GetDisplayText write SetDisplayText;
28: end;
29:
30: implementation
31:
32: constructor TSimpleTextControl.Create;
33: begin
34: inherited Create;
35: // TODO: Adicione qualquer código construtor aqui
36: end;
37:
38: function TSimpleTextControl.GetDisplayText: string;
39: begin
40: Result := FDisplayText;
41: end;
42:
43: procedure TSimpleTextControl.Render(Writer: HtmlTextWriter);
44: begin
45: Writer.Write(DisplayText);
46: end;
47:
48: procedure TSimpleTextControl.SetDisplayText(const Value: &string);
49: begin
50: if FDisplayText < > Value then
51: begin
52: FDisplayText := Value;
53: end;
54: end;
55:
56: end.

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

Se criar um novo package, coloque esse controle nele e então o instale na IDE;
você pode utilizar esse controle em uma página ASP.NET ou em um controle de usuá-
rio. Ele não faz absolutamente muita coisa – simplesmente exibe o texto inline com a
HTML na página. Na verdade, você não pode posicionar o controle, alterar seu forma-
to ou fazer qualquer outra coisa. Ele, porém, ilustra os princípios básicos, portanto va-
mos examiná-lo.
850 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Primeiro, observe que o controle descende da classe System.Web.UI.Control (linha 10).


Essa é a classe básica para todos os controles Web e seria análoga a System.ComponentModel.
Component na FCL ou TComponent na VCL. A classe System.Web.UI.Control fornece a funcionali-
dade básica para colocar um controle sobre uma área do ASP.NET. TSimpleTextControl adi-
ciona uma única propriedade publicada, DisplayText, que armazena e então exibe uma
string (linha 27).
Segundo, observe que o componente implementa um construtor “vazio” que não
faz nada além de chamar inherited (linhas 32–36). Isso é requerido pelo ASP.NET. Frame-
work. Na VCL, o Delphi conta com construtores virtuais para criar e gerenciar compo-
nentes em tempo de projeto. Na FCL, cada classe deve ter um construtor sem parâme-
tros. Esse construtor não pode ser herdado; ele precisa ser declarado explicitamente para
cada classe individual.
O “trabalho” real do controle, se for possível chamar isso de trabalho, é feito no mé-
todo Render( ) sobrescrito (linhas 43–46). O método Render( ) é chamado no momento
em que é realmente necessário renderizar HTML para o controle. O método tem um úni-
co parâmetro do tipo HTMLTextWriter. HTMLTextWriter é uma classe FCL projetada para ren-
derização de HTML em uma área de projeto de HTML. Abordaremos HTMLTextWriter em
mais detalhes mais adiante neste capítulo. Observe que o método Render( ) sobrescrito
simplesmente chama HTMLTextWriter.Write( ), enviando junto a propriedade DisplayText
como um parâmetro.
Portanto, quando esse controle é colocado em uma página, ele simplesmente adi-
ciona uma linha de texto na HTML onde quer que o controle acabe sendo posicionado
no fluxo da página. Observe que o controle não tem nenhuma propriedade, permitin-
do que o texto seja formatado ou localizado em qualquer lugar na página. Além disso,
como o controle não pode ser posicionado, ele nem mesmo pode ser movido em uma
área de design da página no designer de página – em vez disso, ele permanece no fluxo
de HTML.
A única propriedade que afeta a exibição do controle é a propriedade DisplayText, de-
clarada desta maneira:

published
[
DefaultValue(''),
Description('The text that will be displayed in the control.'),
Category('Appearance'),
Bindable(True)
]
property DisplayText: string read GetDisplayText write SetDisplayText;

DisplayText é uma propriedade simples de string, mas é declarada com alguns atribu-
tos. Esses atributos são discutidos na Tabela 34.3.
Controles Web 851

TABELA 34.3 Atributos das propriedades de controles Web


Atributo Descrição
DefaultValue Esse é o valor padrão da propriedade – nesse caso, uma string vazia.
Description Essa é a descrição da propriedade que será exibida no Object Inspector se a
propriedade estiver selecionada aí.
Category Quando o Object Inspector é classificado por categoria, a propriedade será
posicionada na categoria ‘Appearance’.
Bindable O atributo Bindable torna uma propriedade vinculável a um campo dataset.

Valores persistentes
O controle na seção anterior não fez nada além de exibir texto não-formatado. Contudo,
talvez você não tenha notado que ele nem mesmo manteria os valores que você define
para a configuração DisplayText em runtime. Se configurar a propriedade DisplayText e en-
tão fizer um postback na página, o valor original em tempo de projeto será retornado.
Isso, naturalmente, não é um comportamento muito valioso nem desejável. É quase cer-
to que você irá querer que seus controles mantenham os valores entre postbacks. Um
controle com essa capacidade é mostrado na Listagem 34.4.

LISTAGEM 34.4 Um controle persistente


1: unit uPersistentTextControl;
2:
3: interface
4:
5: uses
6: System.Web
7: , System.Web.UI
8: , System.ComponentModel
9: ;
10:
11: type
12: [DefaultProperty('DisplayText')]
13: TPersistentTextControl = class(System.Web.UI.Control)
14: strict protected
15: procedure Render(Writer: HtmlTextWriter); override;
16: public
17: constructor Create;
18: published
19: function GetDisplayText: string;
20: procedure SetDisplayText(const Value: &string);
21: published
22: [
23: DefaultValue(''),
24: Description('The text that will be displayed in the control.'),
25: Category('Appearance'),
26: Bindable(True)
852 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.4 Continuação


27: ]
28: property DisplayText: string read GetDisplayText write SetDisplayText;
29: end;
30:
31: implementation
32:
33: constructor TPersistentTextControl.Create;
34: begin
35: inherited Create;
36: ViewState['DisplayText'] := '';
37: end;
38:
39: function TPersistentTextControl.GetDisplayText: string;
40: begin
41: Result := String(ViewState['DisplayText']);
42: end;
43:
44: procedure TPersistentTextControl.Render(Writer: HtmlTextWriter);
45: begin
46: Writer.Write(DisplayText);
47: end;
48:
49: procedure TPersistentTextControl.SetDisplayText(const Value: &string);
50: begin
51: ViewState['DisplayText'] := Value;
52: end;
53:
54: end.

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

TPersistentTextControl utiliza o recurso ViewState do ASP.NET para assegurar que


quaisquer alterações na propriedade DisplayText permaneçam persistentes entre solicita-
ções de páginas. ViewState é uma classe de dicionário que pode armazenar objetos com
base em um índice de string. Especificamente, ele armazena objetos e informações rela-
cionadas ao estado de visualização para um dado controle de servidor. As informações de
ViewState são enviadas no stream de HTML com sua página, portanto ele não deve ser uti-
lizado para armazenar grandes volumes de informações. Ele deve ser utilizado, porém,
para armazenar informações sobre o estado de exibição do seu controle – nesse caso, o
valor da propriedade DisplayText.
Agora, se você posiciona esse controle em uma página e altera seu valor como parte
de um postback, o valor que você configura será mantido e exibido entre solicitações de
páginas. Obviamente, isso é um recurso importante, uma vez que certamente você dese-
jará manter o estado dos seus controles à medida que os usuários clicam em vários locais
do seu site Web.
Controles Web 853

Adicionando alguma renderização personalizada


TPersistentTextControl permite manter alterações para a propriedade DisplayText, mas ele
ainda não permite fazer nenhuma formatação desse texto. O controle a seguir, TBoldText-
Control, adiciona uma propriedade UseBoldText para permitir exibir o texto em negrito se a
propriedade estiver configurada como True. Isso é feito adicionando código ao método
Render( ), como mostrado na Listagem 34.5.

LISTAGEM 34.5 Adicionando um método Render( )


1: procedure TBoldTextControl.Render(Writer: HtmlTextWriter);
2: begin
3: if UseBoldText then
4: Writer.RenderBeginTag(HTMLTextWriterTag.B);
5: Writer.Write(DisplayText);
6: if FUseBoldText then
7: Writer.RenderEndTag;
8: end;

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

Como previamente mencionado, a classe HTMLTextWriter permite renderizar HTML


quando o controle solicita. No caso anterior, adicionamos um tag de negrito à proprieda-
de DisplayText se a propriedade UseBoldText estiver configurada como True.
Mas quem irá renderizar HTML da maneira difícil? HTML é bem variada e você não
quer escrever seus próprios códigos para tratar as diferentes maneiras que a HTML pode
ser renderizada. Felizmente, você não precisa fazer isso. O ASP.NET Framework fornece
toda a funcionalidade necessária via a classe System.Web.UI.WebControls.WebControl. Essa
classe, diferentemente da sua Control ancestral que utilizamos, inclui toda a funcionali-
dade para aplicar formatação e localização de HTML ao controle. Portanto, se, em vez
disso, herdarmos dessa classe, poderemos obter um controle semelhante ao TsimpleText-
Control anterior, mas que pode ser formatado com todas as capacidades de HTML. Isso é
mostrado na Listagem 34.6.

LISTAGEM 34.6 Melhor renderização de HTML


1: unit uSimpleWebControl;
2:
3: interface
4:
5: uses
6: System.Web
7: , System.Web.UI
8: , System.Web.UI.WebControls
9: , System.ComponentModel
10: ;
11:
12: type
854 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.6 Continuação


13: [DefaultProperty('DisplayText')]
14: TSimpleWebControl = class(System.Web.UI.WebControls.WebControl)
15: strict protected
16: procedure RenderContents(Writer: HtmlTextWriter); override;
17: public
18: constructor Create;
19: published
20: function GetDisplayText: string;
21: procedure SetDisplayText(const Value: &string);
22: published
23: [
24: DefaultValue(''),
25: Description('The text that will be displayed in the control'),
26: Category('Appearance'),
27: Bindable(True)
28: ]
29: property DisplayText: string read GetDisplayText write SetDisplayText;
30: end;
31:
32: implementation
33:
34: constructor TSimpleWebControl.Create;
35: begin
36: inherited Create;
37: ViewState['DisplayText'] := '';
38: end;
39:
40: function TSimpleWebControl.GetDisplayText: string;
41: begin
42: Result := string(ViewState['DisplayText']);
43: end;
44:
45: procedure TSimpleWebControl.RenderContents(Writer: HtmlTextWriter);
46: begin
47: Writer.Write(DisplayText);
48: end;
49:
50: procedure TSimpleWebControl.SetDisplayText(const Value: &string);
51: begin
52: ViewState['DisplayText'] := Value;
53: end;
54:
55: end.

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.


Controles Web 855

O código desse controle é basicamente o mesmo do TpersistentTextControl, exceto


que ele descende de System.Web.UI.WebControls.WebControl (Linha 14). Em vez de sobrescre-
ver o método Render, ele sobrescreve o método RenderContents.
O método RenderContents é chamado quando o texto real a ser exibido é necessário
para o controle. Um WebControl faz toda a renderização de HTML com base nas configura-
ções das suas propriedades, mas ele precisa conhecer qual é o conteúdo específico a ser
exibido. Portanto, nesse caso, simplesmente enviamos junto o valor da propriedade Dis-
playText, assim como fizemos para TSimpleTextControl. A diferença agora é que podemos
mover o controle na área de projeto e formatar o texto com um host de tags HTML via as
propriedades do controle, como mostrado na Figura 34.6.

Determinando o tipo de bloco HTML


Nesse ponto você desenvolveu um controle Web básico que pode exibir e decorar o texto
com uma grande quantidade de HTML e folhas de estilo CSS. Por padrão, esses controles
são renderizados como tags <span> pelo ASP.NET. Mas um tag <span> não é recomendável.
Talvez você queira um tag <div> ou um <p>.
Controles Web permitem controlar a tag que é posicionado na HTML pelo seu con-
trole via a propriedade TagKey. Podemos ampliar nosso controle nesse ponto e chamar
TBlockTextWebControl. O TBlockTextWebControl permite colocar seu texto em uma tag de divi-
são, span ou parágrafo. Ele define o tipo TBlockType, como a seguir:

FIGURA 34.6 Um controle Web básico com as propriedades de formatação


configuradas.
856 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

type
TBlockType = (ttDivision, ttSpan, ttParagraph);

E então adiciona esta propriedade:

property BlockType: TBlockType read GetBlockType write SetBlockType;

O valor de BlockType determinará quais das três tags comuns de texto serão utilizadas
no controle. A propriedade que determina isso é a TagKey. Essa propriedade precisa ser so-
brescrita para retornar o valor apropriado. O Delphi não permite sobrescrever proprieda-
des, mas você pode sobrescrever o método get_TagKey( ) do controle, retornando o valor
da propriedade a partir do tipo enumerado HTMLTextWriterTag. Isso é mostrado na Listagem
34.7.

LISTAGEM 34.7 Sobrescrevendo o método get_TegKey( )


1: strict protected
2: function get_TagKey: HTMLTextWriterTag; override;
3: ...
4:
5: function TBlockTextWebControl.get_TagKey: HTMLTextWriterTag;
6: begin
7: case FBlockType of
8: ttDivision: Result := HTMLTextWriterTag.Div;
9: ttSpan : Result := HTMLTextWriterTag.Span;
10: ttParagraph: Result := HTMLTextWriterTag.P;
11: else
12: Result := HTMLTextWriterTag.Div;
13: end;
14: end;

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

Tratando dados post-back


Às vezes, um controle que você constrói talvez precise reagir a um postback. O postback
ocorre quando a página é chamada como resultado de um formulário HTML, como quando
o usuário clica em um botão Submit. Quando um postback ocorre, as informações de View-
State são examinadas e a página é reexibida utilizando quaisquer novas informações inseri-
das nos controles da página pelo usuário. Por exemplo, se um usuário inserir informações
em uma caixa de edição e clicar no botão Submit, as informações na caixa de edição “são
postadas de volta” (posted back) na página e sua aplicação pode realizar uma ação nela.
Para que um controle participe do processo de postback, ele precisa implementar a
interface IPostBackEventHandler. Essa interface é declarada desta maneira:

IPostBackEventHandler = interface
procedure RaisePostBackEvent(eventArgument: string);
end;
Controles Web 857

Você deve implementar o método RaisePostBackEvent a fim de executar qualquer que


seja o código que você quer que seja chamado quando um evento postback ocorre. Você
deve utilizar a interface IPostBackEventHandler se seu controle não precisar obter a entrada
de dados gerada pelo usuário, mas mesmo assim precisa participar do evento postback.
Normalmente, nesse método, você chamaria o código que dispara um evento personali-
zado para o controle. Isso permite implementar e chamar eventos do lado do servidor
com base em ações do lado do cliente.
Com freqüência, porém, seu controle receberá a entrada de usuário; portanto, ele pre-
cisará ser capaz de processar essa entrada. A fim de que um controle Web trate dados de
postback, ele deve implementar a interface IPostBackDataHandler, declarada como a seguir:

IPostBackDataHandler = interface
function LoadPostData(postDataKey: string;
postCollection: NameValueCollection) : boolean;
procedure RaisePostDataChangedEvent;
end;

IPostBackDataHandler tem dois métodos que permitem tratar dados de postback para
seu controle. Quando ocorre um postback, cada controle no formulário é verificado para
ver se ele trata essa interface: se tratar, o método LoadPostData( ) é chamado. Sua imple-
mentação de LoadPostData( ) deve processar os novos dados como desejado, retornando
True se o estado do controle mudou e False se não mudou. Se a função retornar True, o fra-
mework, por sua vez, chamará o segundo método, RaisePostDataChangedEvent( ), que basi-
camente funciona da mesma maneira como o método RaisePostBackEvent( ) na interface
IPostBackEventHandler. Nesse método, você pode disparar um evento do lado do servidor a
fim de permitir que usuários do seu controle façam o que eles precisam fazer quando os
dados no controle mudarem.
Os parâmetros LoadPostData( ) contêm as informações postadas para o controle. O
primeiro parâmetro, postDataKey, é um valor de string que representa o valor apropriado
indexado pela string na coleção passada no segundo parâmetro, postCollection. Portanto,
a fim de obter as informações postadas, você faria algo semelhante a

Var
NewValue: string;
begin
NewValue := postCollection[postDataKey];
...
end;

Observe também que há momentos em que você quer implementar essas duas inter-
faces em um único controle.

TPostBackInputWebControl
O exemplo de TPostbackInputWebControl mostrado na Listagem 34.8 ilustra como criar um
controle que trata dados de postback.
858 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.8 Exemplo de TPostbackInputWebControl


1: unit uPostBackTextWebControl;
2:
3: interface
4:
5: uses
6: System.Web
7: , System.Web.UI
8: , System.Web.UI.WebControls
9: , System.ComponentModel
10: , System.Collections.Specialized
11: ;
12:
13: type
14: TTextChangedEvent = procedure(e: EventArgs) of object;
15:
16: type
17: [DefaultProperty('DisplayText')]
18: TPostBackInputWebControl = class(WebControl, IPostBackDataHandler)
19: private
20: FOnTextChanged: TTextChangedEvent;
21: function GetDisplayText: string;
22: procedure SetDisplayText(const Value: &string);
23: strict protected
24: procedure AddAttributesToRender(Writer: HTMLTextWriter); override;
25: function get_TagKey: HTMLTextWriterTag; override;
26: procedure DoTextChanged;
27: procedure Render(Writer: HTMLTextWriter); override;
28:
29: // IPostBackDataHandler
30: function LoadPostData(PostDataKey: string;
31: PostCollection: NameValueCollection) : boolean;
32: procedure RaisePostDataChangedEvent;
33: public
34: constructor Create;
35: published
36: [
37: DefaultValue(''),
38: Description('The text that will be displayed in the input control'),
39: Category('Appearance'),
40: Bindable(True)
41: ]
42: property DisplayText: string read GetDisplayText write SetDisplayText;
43: [
44: Category('Action'),
45: Description('Fired whenever the user changes the text in the' +
46: ' input control')
47: ]
48: property OnTextChanged: TTextChangedEvent
Controles Web 859

LISTAGEM 34.8 Continuação


49: add FOnTextChanged remove FOnTextChanged;
50: end;
51:
52: implementation
53:
54: constructor TPostBackInputWebControl.Create;
55: begin
56: inherited Create;
57: ViewState['DisplayText'] := '';
58: end;
59:
60: function TPostBackInputWebControl.GetDisplayText: string;
61: var
62: Obj: TObject;
63: begin
64: Obj := ViewState['DisplayText'];
65: if Obj < > nil then
66: Result := ViewState['DisplayText'].ToString
67: else
68: Result := '';
69: end;
70:
71: function TPostBackInputWebControl.LoadPostData(postDataKey: &string;
72: PostCollection: NameValueCollection): boolean;
73: var
74: NewValue: string;
75: begin
76: NewValue := PostCollection[PostDataKey];
77: Result := not DisplayText.Equals(NewValue);
78: if Result then
79: DisplayText := NewValue;
80: end;
81:
82: procedure TPostBackInputWebControl.RaisePostDataChangedEvent;
83: begin
84: DoTextChanged;
85: end;
86:
87: procedure TPostBackInputWebControl.SetDisplayText(const Value: &string);
88: begin
89: ViewState['DisplayText'] := Value;
90: end;
91:
92: function TPostBackInputWebControl.get_TagKey: HTMLTextWriterTag;
93: begin
94: Result := HTMLTextWriterTag.Input;
95: end;
96:
860 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.8 Continuação


97: procedure TPostBackInputWebControl.DoTextChanged;
98: begin
99: if Assigned(FOnTextChanged) then
100: FOnTextChanged(EventArgs.Empty);
101: end;
102:
103: procedure TPostBackInputWebControl.AddAttributesToRender(Writer:
104: HTMLTextWriter);
105: begin
106: inherited;
107: Writer.AddAttribute(HTMLTextWriterAttribute.Name, Self.UniqueID);
108: Writer.AddAttribute(HTMLTextWriterAttribute.Type, 'Text');
109: Writer.AddAttribute(HTMLTextWriterAttribute.Value, DisplayText);
110: end;
111:
112: procedure TPostBackInputWebControl.Render(Writer: HTMLTextWriter);
113: begin
114: if Page < > nil then
115: Page.VerifyRenderingInServerForm(Self);
116: inherited;
117: end;
118:
119: end.

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

TPostBackInputWebControl fornece a funcionalidade básica de um controle de entrada


HTML e também um evento do lado do servidor, OnTextChanged (linhas 48–49), que é dis-
parado sempre que o usuário altera o texto no controle de entrada e postas essas altera-
ções para o servidor. Esse código introduz dois novos recursos ao modelo de componen-
tes de controles Web, assim vamos examiná-lo mais de perto.
Primeiro, observe que TPostBackInputWebControl descende de WebControl (Linha 18), for-
necendo assim todas as propriedades de folha de estilo que permitem formatar o contro-
le como desejado. Além disso, o controle declara que ele implementará a interface IPost-
BackDataHandler (linha 18), permitindo que ele participe no postback de um formulário e
processe as alterações feitas na página pelo usuário.
Em seguida, a unit declara um tipo de evento, TTextChangedEvent, desta maneira:

type
TTextChangedEvent = procedure(e: EventArgs) of object;

Esse evento será disparado quando os dados de postback indicam que uma alteração
ocorreu na propriedade DisplayText do controle. Ele é disparado no método DoTextChan-
ged( ) (linha 99) que, por sua vez, é chamado no RaisePostDataChangedEvent( ) (linha 82),
que é chamado quando a função LoadPostData( ) retorna True, indicando que os dados no
controle mudaram.
Controles Web 861

A função LoadPostData( ) simplesmente obtém os valores novos e antigos do contro-


le, compara-os e retorna True se forem diferentes. Além disso, se eles forem diferentes, a
nova versão é salva na propriedade DisplayText.
Esse também é o primeiro controle que examinamos que realmente exigia uma ren-
derização um pouco mais sofisticada. O controle renderizará uma única tag de entrada
que é determinada sobrescrevendo a função get_TagKey( ):

function TPostBackInputWebControl.get_TagKey: HTMLTextWriterTag;


begin
Result := HTMLTextWriterTag.Input;
end;

Além disso, como a tag HTML <input> é uma tag única que transporta todos os seus
dados em atributos, é necessário sobrescrever o método AddAttributesToRender( ) a fim de
adicionar os atributos apropriados a tag HTML resultante. O método AddAttributesToRen-
der( ) a seguir é chamado durante o processo de renderização para coletar todos os atri-
butos, ou pares de name="value", a serem incluídos na tag:

procedure TPostBackInputWebControl.AddAttributesToRender(Writer:
HTMLTextWriter);
begin
inherited;
Writer.AddAttribute(HTMLTextWriterAttribute.Name, Self.UniqueID);
Writer.AddAttribute(HTMLTextWriterAttribute.Type, 'Text');
Writer.AddAttribute(HTMLTextWriterAttribute.Value, DisplayText);
end;

Nesse caso, acrescentamos o nome UniqueID ao controle como o atributo 'name', o


tipo de controle de entrada final e o texto real a ser exibido no controle. O valor UniqueID é
requerido pelo ASP.NET a fim de identificar de maneira única o controle durante o pro-
cesso de chamar todos os métodos postback para todos os controles em uma página. A
função AddAttributesToRender coleta esses atributos e os renderiza apropriadamente na tag
<input> à medida que o controle é renderizado na página.
Além disso, o TPostBackInputWebControl nunca deve ser posicionado em uma página a
menos que esteja dentro de uma tag <form runat=server>. O ASP.NET Framework fornece
um meio de assegurar isso. O controle sobrescreve o método Render desta maneira:

procedure TPostBackInputWebControl.Render(Writer: HTMLTextWriter);


begin
if Page < > nil then
Page.VerifyRenderingInServerForm(Self);
inherited;
end;

A classe Page inclui um método chamado VerifyRenderingInServerForm( ) que verifica


se o controle está atualmente renderizado dentro de uma tag <form runat=server>. Se não
estiver, levanta uma HttpException. Se nenhuma exceção for levantada, o controle é ren-
derizado normalmente chamando o método herdado.
862 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

Controles compostos
Anteriormente, neste capítulo, examinamos os controles de usuário, uma maneira ba-
seada em texto, de agrupar controles em uma única entidade. Às vezes, porém, talvez
você queira alcançar a mesma funcionalidade por meio de um controle Web baseado
em um binário. O ASP.NET Framework permite construir controles compostos que po-
dem conter outros controles de uma maneira semelhante aos controles de usuário. Os
controles compostos utilizam o modelo de controle pai/filho para gerenciar os even-
tos, renderização e funcionalidade de postback dos controles contidos para criar uma
única entidade.
A fim de criar um controle composto, você pode fazer com que ele descenda de
System.Web.UI.Control ou System.Web.UI.WebControls.WebControl, dependendo dos recursos que
você precisa no seu controle. De qualquer maneira, seu controle deve fazer duas coisas:
Primeiro, ele deve sobrescrever CreateChildControls( ) para criar os controles que esta-
rão contidos dentro dele. O método CreateChildControls( )é chamado pelo framework no
momento em que, no processo de renderização, esses controles são necessários. Isso aju-
da a assegurar que eles forneçam dados de postback apropriadamente. O framework, via
o método EnsureChildControls( ), monitora se os controles filhos foram criados, assegu-
rando que eles só sejam criados uma vez e no momento adequado. Se, de alguma manei-
ra, seu controle precisar acessar os controles filhos, você deverá chamar EnsureChildCon-
trols( ) para assegurar que os controles filhos existem.
Segundo, um controle composto deve implementar a interface System.Web.UI.INa-
mingContainer que gerenciará a atribuição de nomes dos subcontroles dentro do seu
controle. Curiosamente, INamingContainer é uma interface vazia. Entretanto, quando
incluída como parte de uma declaração de classe do controle composto, a presença
dessa interface irá assegurar que os controles contidos dentro do controle composto
tenham nomes únicos na página. Pense sobre o que poderia acontecer se um usuário
colocasse duas instâncias do seu controle composto em uma página. Se o controle
não criasse nomes únicos para os controles contidos, haveria um conflito de nomes
na página.

Implementando um controle composto – TNewUserInfoControl


Freqüentemente, um site Web desejará registrar usuários, permitindo que eles forneçam
um endereço de correio eletrônico e uma senha. Sendo essa necessidade bastante co-
mum, aparentemente vale a pena construir um controle composto que forneça a interfa-
ce com o usuário para essa entrada em um formulário reutilizável. A Listagem 34.9 mos-
tra a declaração e implementação de TNewUserInfoControl; um controle composto que for-
necerá controles TextBox de entrada para um correio eletrônico de usuário, uma senha e
uma verificação de senha, bem como um botão Submit. Uma listagem parcial da imple-
mentação desse controle é mostrada na Listagem 34.9.

NOTA
A Listagem 34.9 mostra apenas o código chave. O código completo está disponível no CD.
Controles Web 863

LISTAGEM 34.9 Implementação de controle composto


1: unit uNewUserInfoControl;
2:
3: interface
4:
5: uses
6: System.Web
7: , System.Web.UI
8: , System.Web.UI.WebControls
9: , System.ComponentModel
10: ;
11:
12: type
13: TSubmitInfoEvent = procedure(Sender: TObject; e: EventArgs) of object;
14:
15: type
16: TNewUserInfoControl = class(WebControl, INamingContainer)
17: private
18: FAlignLabelsToRight: Boolean;
19: FOnSubmitInfo: TSubmitInfoEvent;
20: SubmitButton: Button;
21: EMailLabel: System.Web.UI.WebControls.Label;
22: EmailTextBox: TextBox;
23: PasswordLabel: System.Web.UI.WebControls.Label;
24: PasswordTextBox: TextBox;
25: VerifyLabel: System.Web.UI.WebControls.Label;
26: VerifyTextBox: TextBox;
27: EmailValidator: RequiredFieldValidator;
28: PasswordValidator: RequiredFieldValidator;
29: VerifyValidator: RequiredFieldValidator;
30: strict protected
31: procedure Click(Sender: TObject; E: EventArgs);
32: procedure Render(Writer: HTMLTextWriter); override;
33: procedure CreateChildControls; override;
34: procedure DoSubmitInfo(Sender: TObject; e: EventArgs); virtual;
35: procedure set_AlignLabelsToRight(const Value: Boolean);
36: function get_AlignLabelsToRight: Boolean;
37: public
38: constructor Create;
39: function get_Password: string;
40: function get_VerifyPassword: string;
41: function get_Controls: ControlCollection; override;
42: [
43: DesignerSerializationVisibility
44: (DesignerSerializationVisibility.Hidden)
45: ]
46: property Password: string read get_Password;
47: [
48: DesignerSerializationVisibility
864 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.9 Continuação


49: (DesignerSerializationVisibility.Hidden)
50: ]
51: property VerifyPassword: string read get_VerifyPassword;
52: function get_ButtonText: string;
53: procedure set_ButtonText(const Value: &string);
54: function get_UserEmail: string;
55: procedure set_UserEmail(const Value: &string);
56: function get_MissingEmailErrorMessage: string;
57: procedure set_MissingEmailErrorMessage(const Value: &string);
58: function get_EmailLabelText: string;
59: procedure set_EmailLabelText(const Value: &string);
60: function get_MissingPasswordErrorMessage: string;
61: procedure set_MissingPasswordErrorMessage(const Value: &string);
62: function get_PasswordLabelText: string;
63: procedure set_PasswordLabelText(const Value: &string);
64: function get_VerifyLabelText: string;
65: procedure set_MissingVerifyErrorMessage(const Value: &string);
66: function get_MissingVerifyErrorMessage: string;
67: procedure set_VerifyLabelText(const Value: &string);
68: published
69: [
70: Bindable(True), Category('Appearance'), DefaultValue(''),
71: Description('The text to be displayed on the Submit Button')
72: ]
73: property ButtonText: string read get_ButtonText write set_ButtonText;
74: [
75: Bindable(True), Category('Appearance'), DefaultValue(''),
76: Description('The user's email address.')
77: ]
78: property UserEmail: string read get_UserEmail write set_UserEmail;
79: [
80: Bindable(True), Category('Appearance'), DefaultValue(''),
81: Description('The error message for the email field validator')
82: ]
83: property MissingEmailErrorMessage: string read
➥get_MissingEmailErrorMessage
84: write set_MissingEmailErrorMessage;
85: [
86: Bindable(True), Category('Appearance'), DefaultValue(''),
87: Description('The text for the label that goes with the' +
88: ' email textbox')
89: ]
90: property EmailLabelText: string read get_EmailLabelText
91: write set_EmailLabelText;
92: [
93: Bindable(True), Category('Appearance'), DefaultValue(''),
94: Description('The error message for the Password validator')
95: ]
Controles Web 865

LISTAGEM 34.9 Continuação


96: property MissingPasswordErrorMessage: string
97: read get_MissingPasswordErrorMessage write
➥set_MissingPasswordErrorMessage;
98: [
99: Bindable(True), Category('Appearance'), DefaultValue(''),
100: Description('The text for the label that goes with the' +
101: ' password textbox')
102: ]
103: property PasswordLabelText: string read Get_PasswordLabelText
104: write set_PasswordLabelText;
105: [
106: Bindable(True), Category('Appearance'), DefaultValue(''),
107: Description('The text for the label that goes with the' +
108: 'verify password textbox')
109: ]
110: property VerifyLabelText: string read get_VerifyLabelText
111: write set_VerifyLabelText;
112: [
113: Bindable(True), Category('Appearance'), DefaultValue(''),
114: Description('The error message for the verify password validator')
115: ]
116: property MissingVerifyErrorMessage: string read
➥get_MissingVerifyErrorMessage
117: write set_MissingVerifyErrorMessage;
118: property AlignLabelsToRight: Boolean read get_AlignLabelsToRight
119: write set_AlignLabelsToRight;
120: property OnSubmitInfo: TSubmitInfoEvent add FOnSubmitInfo
121: remove FOnSubmitInfo;
122: end;
123:
124: ...
125:
126: procedure TNewUserInfoControl.CreateChildControls;
127: begin
128: Controls.Clear;
129:
130: // Controles de correio eletrônico
131: EMailLabel := System.Web.UI.WebControls.Label.Create;
132:
133: EmailTextBox := TextBox.Create;
134: EmailTextBox.ID := 'EmailTextBox';
135:
136: EmailValidator := RequiredFieldValidator.Create;
137: EmailValidator.ID := 'EmailValidator';
138: EmailValidator.ControlToValidate := EmailTextBox.ID;
139: EmailValidator.Text := MissingEmailErrorMessage;
140: EmailValidator.Display := ValidatorDisplay.Static;
141:
866 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.9 Continuação


142: // Controles de senha
143: PasswordLabel := System.Web.UI.WebControls.Label.Create;
144:
145: PasswordTextBox := TextBox.Create;
146: PasswordTextBox.TextMode := TextBoxMode.Password;
147: PasswordTextBox.ID := 'PasswordTextBox';
148:
149: PasswordValidator := RequiredFieldValidator.Create;
150: PasswordValidator.ID := 'PasswordValidator';
151: PasswordValidator.ControlToValidate := PasswordTextBox.ID;
152: PasswordValidator.Text := MissingPasswordErrorMessage;
153: PasswordValidator.Display := ValidatorDisplay.Static;
154:
155: // Controles VerifyPassword
156: VerifyLabel := System.Web.UI.WebControls.Label.Create;
157:
158: VerifyTextBox := TextBox.Create;
159: VerifyTextBox.TextMode := TextBoxMode.Password;
160: VerifyTextBox.ID := 'VerifyTextBox';
161:
162: VerifyValidator := RequiredFieldValidator.Create;
163: VerifyValidator.ID := 'VerifyValidator';
164: VerifyValidator.ControlToValidate := VerifyTextBox.ID;
165: VerifyValidator.Text := MissingVerifyErrorMessage;
166: VerifyValidator.Display := ValidatorDisplay.Static;
167:
168: // Botão Submit
169: SubmitButton := Button.Create;
170: SubmitButton.ID := 'SubmitButton';
171: Include(SubmitButton.Click, Self.Click);
172:
173: Controls.Add(EmailLabel);
174: Controls.Add(EmailTextBox);
175: Controls.Add(EmailValidator);
176:
177: Controls.Add(PasswordLabel);
178: Controls.Add(PasswordTextbox);
179: Controls.Add(PasswordValidator);
180:
181: Controls.Add(VerifyLabel);
182: Controls.Add(VerifyTextBox);
183: Controls.Add(VerifyValidator);
184:
185: Controls.Add(SubmitButton);
186: end;
187:
188: procedure TNewUserInfoControl.Render(Writer: HTMLTextWriter);
189: begin
Controles Web 867

LISTAGEM 34.9 Continuação


190: AddAttributesToRender(Writer);
191:
192: Writer.AddAttribute(HTMLTextWriterAttribute.Cellpadding, '1', False);
193: Writer.RenderBeginTag(HTMLTextWriterTag.Table);
194:
195: // Controles de correio eletrônico
196: Writer.RenderBeginTag(HtmlTextWriterTag.Tr);
197: if FAlignLabelsToRight then
198: Writer.AddAttribute(HTMLTextWriterAttribute.Align, 'right');
199: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
200: EmailLabel.RenderControl(Writer);
201: Writer.RenderEndTag; // Fecha a tag TD
202: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
203: EmailTextBox.RenderControl(Writer);
204: Writer.RenderEndTag; // Fecha a tag TD
205: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
206: EmailValidator.RenderControl(Writer);
207: Writer.RenderEndTag; // Fecha a tag TD
208: Writer.RenderEndTag; // Fecha a tag TR
209:
210: // Controles de senha
211: Writer.RenderBeginTag(HtmlTextWriterTag.Tr);
212: if FAlignLabelsToRight then
213: Writer.AddAttribute(HTMLTextWriterAttribute.Align, 'right');
214: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
215: PasswordLabel.RenderControl(Writer);
216: Writer.RenderEndTag; // Fecha a tag TD
217: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
218: PasswordTextBox.RenderControl(Writer);
219: Writer.RenderEndTag; // Fecha a tag TD
220: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
221: PasswordValidator.RenderControl(Writer);
222: Writer.RenderEndTag; // Fecha a tag TD
223: Writer.RenderEndTag; // Fecha a tag TR
224:
225: // Controles de verificação
226: Writer.RenderBeginTag(HtmlTextWriterTag.Tr);
227: if FAlignLabelsToRight then
228: Writer.AddAttribute(HTMLTextWriterAttribute.Align, 'right');
229: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
230: VerifyLabel.RenderControl(Writer);
231: Writer.RenderEndTag; // Fecha a tag TD
232: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
233: VerifyTextBox.RenderControl(Writer);
234: Writer.RenderEndTag; // Fecha a tag TD
235: Writer.RenderBeginTag(HtmlTextWriterTag.Td);
236: VerifyValidator.RenderControl(Writer);
237: Writer.RenderEndTag; // Fecha a tag TD
868 Capítulo 34 Desenvolvendo controles ASP.NET Server personalizados

LISTAGEM 34.9 Continuação


238: Writer.RenderEndTag; // Fecha a tag TR
239:
240: // Botão Submit
241: Writer.RenderBeginTag(HTMLTextWriterTag.Tr);
242: Writer.AddAttribute(HTMLTextWriterAttribute.Colspan, '2');
243: Writer.AddAttribute(HTMLTextWriterAttribute.Align, 'right');
244: Writer.RenderBeginTag(HTMLTextWriterTag.Td);
245: SubmitButton.RenderControl(Writer);
246: Writer.RenderEndTag; // Fecha a tag TD
247: Writer.RenderBeginTag(HTMLTextWriterTag.Td);
248: Writer.Write('&nbsp');
249: Writer.RenderEndTag; // Fecha a tag TD
250: Writer.RenderEndTag; // Fecha a tag TR
251: Writer.RenderEndTag; // Fecha a tag TABLE
252:
253: end;
254:
255: ...
256:
257: procedure TNewUserInfoControl.DoSubmitInfo(Sender: TObject; e: EventArgs);
258: begin
259: if Assigned(FOnSubmitInfo) then
260: FOnSubmitInfo(Sender, e);
261: end;
262:
263: procedure TNewUserInfoControl.Click(Sender: TObject; E: EventArgs);
264: begin
265: DoSubmitInfo(Sender, E);
266: end;
267:

u Localize o código no CD: \Code\Chapter 34\ControlExamples\.

Se consultar o código completo no CD, você observará que, em cada getter e setter
para as propriedades que se referem a um controle filho, ocorre uma chamada a EnsureC-
hildControls( ) para assegurar que os controles sejam criados apropriadamente antes de
acessar quaisquer propriedades neles. Essas chamadas evitam que seu componente ten-
te acessar um controle filho ainda não criado, o que resultaria em uma NullReferenceEx-
ception.
O controle implementa a interface InamingContainer (linha 16). Mais uma vez, essa in-
terface está vazia, mas o fato de que o controle suporta a interface irá assegurar que todos
os controles filhos tenham nomes únicos. Você pode ilustrar isso colocando dois contro-
les em uma única página e conferindo a HTML resultante. Atribuímos nomes aos con-
troles filhos com base no ID do controle pai junto com o próprio ID.
O controle também utiliza o método Render( ) (linhas 188–253) para fazer toda a
renderização HTML. Ele cria uma tabela simples para armazenar os controles, fazendo
Controles Web 869

com que eles sejam alinhados. Ele inclui uma propriedade Boolean, AlignLabelsToRight,
para permitir alinhamento dos rótulos à esquerda ou à direita como desejado.
Todos os controles individuais são criados em runtime no método CreateChildCon-
trols( ) sobrescrito (linhas 126–186). Primeiro, o método remove todos os controles
existentes do array. Cada controle é criado e suas propriedades adequadas são atribuídas.
Uma propriedade a ser observada é a propriedade ControlToValidate no validators, que ob-
viamente deve apontar para um controle válido existente. Depois que todos os controles
são criados, eles são adicionados ao array Controls. Além disso, nesse método, é atribuído
o handler de evento Click de SubmitButton.

NOTA
Também observe que os valores de senha nunca são absolutamente armazenados no servidor –
eles apenas se tornam de leitura a partir dos próprios controles. Isso assegura que eles não sejam
enviados de volta ao controle do cliente como parte de uma resposta de postback. Se um usuário
não conseguir atender aos critérios de validação, os campos de senha permanecem em branco de-
pois do postback.

O componente fornece um evento OnSubmitInfo (linha 120–121) para tratar dos cli-
ques no botão Submit. Observe que esse evento só é disparado quando todos os valida-
dores concordam com o fato de que o formulário foi preenchido adequadamente. Até
então, os validadores exibirão as mensagens de erro atribuídas a eles no Object Inspector
se o formulário não tiver sido preenchido adequadamente.

DICA
O MSDN tem uma documentação completa sobre o ASP.NET Framework. Ele também inclui vários
exemplos de codificação e descrições das técnicas de construção de componentes.
O MSDN é bem completo, porém, todos os construtores de componentes ASP.NET devem ter
uma cópia do ASP.NET Server Controls and Components de Nikhil Kothari e Vandana Datye. Este ex-
celente livro cobre completamente todos os aspectos sobre a construção de componentes
ASP.NET. Todos os exemplos são escritos em C#, mas um desenvolvedor Delphi não deverá ter ne-
nhum problema em acompanhar a leitura. Se você desenvolver qualquer tipo de controle
ASP.NET, você deverá comprar este livro.
PÁGINA EM BRANCO
Índice

Símbolos AboveNormal, prioriedade de ambientes conectados, 445


thread, 347 ambientes desconectados, 445
; (ponto-e-vírgula), 256
abrindo aplicações Mono ADO.NET, 199,
- (sinal de subtração), 30
conexões, 452, 453 200, 201, 202
- (subtração), operador, 57, 72, controles Web, 199
Object Repository, 34
506 handler de evento
AcceptChanges( ), método, 490
# (sinal de libra), especificador btnSubmit_Click( ), 199
de formato, 256 AcceptRejectRule, propriedade handler de evento
(ForeignKeyConstraint), 490 Page_Load( ), 200
% (módulo) operador, 506
AccessKey, propriedade (classe BDP (Borland Data Provider),
% (porcentagem, sinal), 256
WebControl), 626 578, 579, 580
%DelphiDotNetAssemblyCom-
acesso a Web Services, 4 caixa de diálogo Data Adapter
piler, diretiva de compilador,
acesso assíncrono ao stream, Configuration, 588, 589
133
276, 278, 279 classe BdpCommand, 581,
& (e comercial), 265
AcquireReaderLock( ), método, 582
(* *), notação de comentário, 49 classe BdpConnection, 580,
358
( ), (parêntese), 50 581
AcquireWriterLock( ), método,
* (asterisco), 782, 813 classe BdpDataAdapter, 583,
358
* (multiplicação), operador, 57, 584
ActivateFile( ), método, 323 classe BdpDataReader, 582,
506
Active Server Pages. Ver 583
*-debuginfo, package, 189
ASP.NET, 591 classe BdpParameter, 584, 585
, (vírgula), 256 classe
Adapter( ), método, 227
. (ponto), 42, 44, 256 BdpParameterCollection,
Add Reference, caixa de
/ (barra), 267 diálogo, 726 584, 585
/ (divisão de ponto flutuante), classe BdpTransaction, 586
Add To-Do, caixa de diálogo, 36
operador, 57 Command Text Editor, 587
Add Web Reference, caixa de Connections Editor, 587
/ (divisão), operador, 506 diálogo, 696, 697 Parameter Collection Editor,
/, separador de data, 261 Add( ), método, 219, 220, 463, 587
//, notação de comentário, 49 483, 487, 494, 495, 513, 517, visão geral de arquitetura,
? (ponto de interrogação), 782 701, 817 578, 579
AddAttributesToRender( ), classe DataReader. Ver
\\bins diretório, 790
método, 861 DataReaders, 467
\\images, diretório, 790 classe de comando. Ver
{ } (chaves), 49, 253 Added RowState, 496
comandos, 456
+ (adição), operador, 57, 72, 506 Added, valor classes. Ver classes, 446
(DataViewRowState), 508 conexões, 449
+ (sinal de adição), 30
AddEmployeeRow( ), método, (classe Connection), 449
< (shift-left), operador, 58
573, 576 abrindo, 452, 453
< Register %>, tag, 845
AddEmUp( ), função, 88 eventos, 453, 454, 455
@ (at), símbolo, 462 fechando, 452, 453
AddInts( ), função, 51
@, endereço de operador, 73 interface IDbConnection, 449,
AddNew( ), método, 502, 520
=, operador de comparação, 56 450
AddRange( ), método, 227, 486,
0, especificador de formato, 256 OdbcConnection.Connection-
487, 488
String, 451, 452
AddType, diretiva, 203 OleDbConnection.Connec-
A adição (+), operador, 57, 72, 506 tionString, 451
AAA, especificador de formato, ADO.NET, 443 pool, 455
256 Ver também bancos de da- SqlConnection.Connection-
dos, 445 String, 450, 451
Aborted, estado de thread, 348
Ver também WinForms, DataAdapter, classe. Ver
AbortRequested, estado de
controles. DataAdapters, 475
thread, 348
872 Índice

DataSets não-tipificados, 566 representação comum de da- AllowDBNull, propriedade


DataSets fortemente dos, 444 (DataColumns), 488
tipificados, 566 processamento de transação, AllowDefaultSort, propriedade
adicionando linhas, 576 558, 559 (classe DataView), 501
arquivos .pas, 307, 568, 570, classe SqlTransaction, 558 AllowDelete, propriedade (classe
574, 576 DataAdapters, 562 DataView), 501
criando, 567, 568 exemplo simples, 559, 562 AllowEdit, propriedade (classe
DataView), 501
dados hierárquicos, 577 leituras de transação, 563,
AllowNew, propriedade (classe
definições de DataRow, 360, 564
DataView), 501
574, 575, 576 método BeginTransaction( ), allowOverride, atributo (tag
definições de DataTable, 571 558 <location>), 795
editando linhas em, 576 método Commit( ), 558, 559 AllPaintingInWmPaint
excluindo linhas em, 577 método Rollback( ), 558, 559 ControlStyle, valor do tipo
listagem de código de nível de isolamento, 563, enumerado, 336
exemplo, 307, 568, 570 564 ALM (Application Lifecycle
localizando linhas em, 577 pontos de salvamento, 564, Development), 23
propriedades/métodos de 565 alocando memória, 75
DataTable, 573, 574 transações aninhadas, 565, AlternatingItemStyle (controle
vantagens/desvantagens, 566 DataGrid), 668
566, 567 vinculação de dados, 514 AlternatingItemStyle,
DataTable, classe. Ver analisando dados propriedade (controle
DataTables, 486 sintaticamente, 523, 525 DataList), 662
DataViews, 500, 501 classe BindingContext, 515 AlternatingItemTemplate,
template, 658
classe DataViewManager, classe BindingManagerBase,
ambientes conectados, 445
501, 502 515
ambientes de desenvolvimento
classificando, 511 classe CurrencyManager, 515
ambientes conectados, 445
comparados com formato de dados, 523, 525
ambientes desconectados, 445
DataSources, 501 interface IBindingList, 514
filtrando, 504, 506, 507, 508, interface IDataErrorInfo, 514 analisando sintaticamente
509, 510, 511 interface IEditableObject, vinculação de dados, 523, 525
joins, 512, 513 514 And (binário), operador, 58
métodos, 502 interface IList, 514 AND, operador, 506
pesquisando, 512 listas, 516, 517 and, operador lógico, 56
propriedades, 502 mestre/detalhe, 526, 527 animação, 174
vinculação de dados, 503, modificação de dados, 520, declaração de sprite, 175
504 521, 523 declaração do formulário princi-
origens de dados, atualizando, navegação, 517, 519, 520 pal, 176
528 tratamento de evento, 517, implementação de demo de
atualizando dados após 519, 520 animação, 177, 182
atualizações, 555, 557 vinculação complexa, 515
Animate_Click( ), handler de
classe SQLCommand, 531, vinculação de valor simples,
evento, 180
532, 534, 535, 536, 537, 517
539 vinculação de valor único, aninhando
classe SQLCommandBuilder, 517 comentários, 49
528, 529, 530 vinculação simples, 515 tipos, 108
classe SQLDataAdapter, 539, transações, 565, 566
Advanced .NET Remoting, 768
540, 541, 543, 544 Ansi, valor (tipo enumerado
Age, propriedade (classe
questões de concorrência, CharSet), 422
ProcessInfo), 803
551, 552, 553, 554, 555 AnsiChar, tipo, 61
AlarmClock, controle, 307
SQLCommandBuilder, 531 Apache
stored procedure, 544, 545, aliases, 78, 79
Mono, 203, 204
546, 547, 548, 549, 550, aliases de unit, 46, 47
site Web, 203
551 aliases fortemente tipificados,
aparando
princípios de projeto, 443 79
strings, 250
arquitetura de dados Align, barra de ferramentas, 27
ApartmentState, classe, 348, 349
desconectados, 443, 444 AllocateDataSlot( ), método,
beneficiando-se de 361 APIs
tecnologias existentes, 444 AllocateNamedDataSlot( ), Reflection API, 372
integração XML, 444 método, 361 assemblies, 372, 373, 374, 375
integrados em .NET Frame- <allow>, seção (arquivo invocação de membro, 380,
work, 444 web.config), 782, 797 381, 382, 383, 385, 386
Índice 873

metadados, 374 controle DropDownList, 650, TypeConverter, 298, 300


módulos, 375, 376, 377 651, 652 métodos, 311, 312, 313
MSIL, emitindo por reflec- controle ListBoxes, 653, 654 passos para escrever
tion, 386, 387, 388, 390 controle RadioButtonList, componentes, 289
tipos, 377, 378 655, 656, 657 propriedades, 293, 294, 295,
utilitário Reflector, 374 controle Repeater, 657, 658, 296, 297, 298, 300, 302,
vinculação tardia, 379, 380 303, 304
659, 660, 661
quando utilizar, 288, 289
aplicação de solicitação de expressões de vinculação,
testando, 313
download, 678 avaliando, 660
units de componente, 290,
listagem de código, 679 vinculação de dados 291, 293
método GetData( ), 683 complexa, 647 EmitDemo, 388, 390
método SaveUserInfo( ), 683, vinculação de dados simples, globalização, 248
684 643, 644, 647 Hello.exe (Mono), 190, 191
método SendEmail( ), 683 ASP.NET Web Forms, 595 InvMemb, 383
aplicações. Ver também banco adicionando controles, 595 InvProject, 380, 381
de dados; WinForm, controles campo VIEWSTATE, 600 localização, 248
aplicação console MonoMenu code-behind, 600, 601 TransEx (exemplo de
.NET, 191 comunicação baseada em processamento de transação),
aplicação de solicitação de evento, 599, 600 559, 562
download, 678 criando, 595 Web Forms
listagem de código, 679 estrutura de página, 596, definição, 5
método GetData( ), 683 597, 598, 599 Web Services, 7, 685
método SaveUserInfo( ), 683, eventos postback, 608, 609 acesso a, 4
684 handlers de evento, 595 benefícios da comunicação, 8,
método SendEmail( ), 683 herança de página, 601 9
aplicações ASP.NET manutenção de estado, 600 capacidade de reutilização, 10
distribuição, 787, 788, 789, separação de clientes, 9, 10
790, 791, 792 projeto/programação, 601 consumindo, 695, 696, 698,
aplicações colaboradoras, 4 BankExample, 724 700, 701, 702, 703, 704
aplicações console, 6 arquivo BankServer.dpr, 732, criando, 686, 687, 688, 689,
aplicações Mono ADO.NET, 733 691
199, 200, 201, 202 classe TAccount, 729 definição, 6, 7
controles Web, 199 configurando, 725, 726 exemplo, 687
handler de evento implementação de cliente, invocando, 691
btnSubmit_Click( ), 199 733, 734, 735, 736 invocando assincronamente,
handler de evento interface de IBankManager, 704, 705
Page_Load( ), 200 729 neutralidade de linguagem, 10
aplicações Mono ASP.NET, 195, referências, 726, 727 retornando dados a partir de,
196, 197 unit BankServer_Impl.pas, 692, 693
configuração de XSP, 197 728, 729 segurança, 705, 706, 708, 709
controles Web, 196 unit BankShared.pas, 729 servidores, 10
distribuição de ASP.NET no componentes .NET, 6 SOAP (Simple Object Access
Mono, 197 console da aplicação Protocol), 8, 685
handler de evento MonoMenu .NET, 192 UDDI (Universal Description,
Button1_Click, 196 controles WinForms Discovery, and Integration),
parâmetros runtime de XSP, personalizados, 287, 288 8, 686
197, 198 AlarmClock, 307 WSDL (Web Service
portabilidade, 198, 199 classes ancestrais, 289, 290 Description Language), 8,
aplicações orientadas a banco comportamento em tempo 686
de dados ASP.NET, 642 de projeto, 313 XML (Extensible Markup
aplicação de solicitação de eventos, 304, 305, 306, 307, Language), 8, 685
download, 678, 679, 684 309, 310, 311 Windows Forms, definição, 5,
controle CheckBoxList, 648 exemplo ExplorerViewer, 19, 21, 22
controle DataGrid, 72, 239, 314, 315, 323 Windows Services, definição, 6
exemplo PlayingCard, 328,
240, 241, 343, 369, 370, aplicações multithread
329, 330, 331, 335, 336,
384, 667, 668, 669, 670, armazenamento de thread local,
337
671, 672, 673, 675, 676, 360, 361
exemplo SimpleStatusBars,
677, 678 324, 325, 326, 328 classes/métodos thread-safe, 361
controle DataList, 662, 663, ícones de componente, 314 método synchronized( ), 361
664, 665, 666, 667 implementação de propriedade IsSynchronized,
874 Índice

362 classe ThreadPriority, 347, vinculação de dados complexa,


propriedade SyncRoot, 362 348 647
comunicações entre processos classe ThreadState, 348 vinculação de dados simples,
Win32, 361 classe ThreadStateException, 643, 644, 647
delegates 371 AppDomains, 341, 342, 718, 719
definição, 344, 352 classe Timer, 351
AppDomains padrão, 341
executando classe WaitHandle, 355, 356
threads Append( ), método, 251, 273
assincronamente, 352
AppDomains, 341, 342 Append, modo de arquivo, 272
ThreadStart, 345, 346
WaitCallback, 349 criando com métodos de AppendFormat( ), método, 251
eventos, 359 instância, 344 Application Domains, 718, 719
classe AutoResetEvent, 360 criando com métodos Application Lifecycle Develop-
classe ManualResetEvent, estáticos, 345 ment (ALM), 23
359 declarando, 342, 343
Application Name, caixa de
exceções, 368 definição, 340
diálogo, 686
estados de apartment, 348,
classe SynchronizationLock- Application, objeto, 837
349
Exception, 371
estados de thread, 338, 348 ApplyStyle( ), método, 628
classe
pools, 349, 351 appRequestQueueLimit, atributo
ThreadAbortException, prioridades, 347, 348
368 (tag <httpRuntime>), 797
threads leves, 340
classe ThreadInterrupted- <appSettings>, seção (ASP.NET
threads lógicos, 340, 341
Exception, 370 timers, 351 web.config), 809
classe ThreadStateException, threads produtores, 359 Archived FileAttribute, 268
371 aplicações orientadas a banco args, parâmetro (método
garbage collection, 371 de dados ASP.NET, 642 InvokeMember( )), 382
mecanismos bloqueadores, 355 aplicação de solicitação de ArithmeticException, classe, 117
classe Interlocked, 358, 359 download, 678 armazenamento
classe Monitor, 356, 357 listagem de código, 679 thread local, 360, 361
classe Mutex, 356 método GetData( ), 683 cache da saída (ASP.NET), 802,
classe ReaderWriterLock, método SaveUserInfo( ), 683, 803
357, 358 684 cache de página, 812, 814
classe WaitHandle, 355, 356 método SendEmail( ), 683
métodos de controle arquitetura, 713, 714
controle CheckBoxList arquitetura cliente/servidor, 714
BeginInvoke( ), 364 propriedades, 648 arquitetura de duas camadas,
CreateGraphics( ), 365, 368 controle DataGrid, 667, 668 714
EndInvoke( ), 364 adicionando itens, 677 arquitetura de três camadas, 715
Invoke( ), 363 classificando, 678 arquitetura distribuída, 713, 714
propriedade InvokeRequired, editando, 671, 672, 673, 675, arquitetura multicamada, 715,
363, 364 676, 677 716
namespace System.Threading, paginação, 72, 239, 240, 241, desenvolvimento, 717, 718
342 343, 369, 370, 384, 668, distribuição, 717, 718
classe ApartmentState, 348, 669, 670, 671 escalabilidade, 716, 717
349 tipos de coluna, 667, 668 segurança, 718
classe AutoResetEvent, 360 controle DataList, 662, 663, tolerância a falhas, 716, 717
classe Interlocked, 358, 359 664, 665, 666, 667 arquitetura pear-to-pear, 714,
classe ManualResetEvent, declaração, 556, 663, 664 715
359 eventos, 662, 663 BDP (Borland Data Provider),
classe Monitor, 356, 357 exemplo, 664 578, 579
classe Mutex, 356 propriedades, 662, 663 .NET Remoting, 718
classe ReaderWriterLock, renderização de imagem, 666 arquivo, autorização de, 781
357, 358 templates, 662
classe SynchronizationLock- arquivos. Ver também diretórios,
controle DropDownList, 650, 265
Exception, 371 651, 652
classe Thread, 342, 345, 347 .ascx, 843
controle ListBoxes, 653, 654 .bdsproj, 128
classe controle RadioButtonList, 655,
ThreadAbortException, .cfg, 128
656, 657 .dcpil, 128
368
controle Repeater, 657, 658, .dcuil, 128
classe
ThreadInterruptedExcep- 659, 660, 661 .dll, 128
tion, 370 expressões de vinculação, .dpk, 128
classe ThreadPool, 349, 351 avaliando, 660 .map, 697
Índice 875

.pas, 128, 307, 568, 570, 574, construtores, 221, 225, 227 DCOM (Distributed Compo-
576, 698 exemplo, 230 nent Object Model), 712
definições de DataRow, 360, métodos, 227, 228 definição, 710, 713
574, 575, 576 propriedades, 227 gerenciamento de tempo de
definições de DataTable, 571 arrays, 67 vida, 751, 752, 753, 754
listagem de código de dinâmicos, 68, 69 Java RMI, 711
exemplo, 307, 568, 570 bidimensionais, 67 leases, 723, 724
propriedades/métodos de declarando, 67 leases, não conseguindo
DataTable, 573, 574 iterando por, 67 renovar, 754, 755
.resx, 314 multidimensionais, 67 Microsoft .NET Distributed
.wsdl, 697 vinculando CheckBoxLists a, Applications
.xml (autenticação), 777, 778 649 Integrating XML Web
.xsd, 568 vinculando DropDownLists a, Services and .NET
arquivo DPR de cliente, 85, Remoting, 768
650, 651
176, 267, 302, 343, 366, , objetos remotable, 720, 721,
vinculando ListBoxes a, 653
381, 481, 540, 547, 569, 645, 722
vinculando RadioButtonLists a,
702, 746, 750, 763, 764 projeto de template, 737
648, 653, 655, 656
arquivo DPR de servidor, 748, proxies, criando, 759, 761,
as, operador, 80 762, 764
760, 761
carregando clientes, 635, 636 .ascx, arquivos, 843 rastreamento de mensagem,
classe File, 264 .aspx, arquivo 738, 739
classe FileInfo, 264 (BasicUserControlPage), 844 RPC (Remote Procedure Call),
classe FileSystemInfo, 264 ASP.NET, 591 711
propriedades, 268, 269 .NET Remoting SAOs (Server Activated Ob-
classe Path, 264 Advanced .NET Remoting, 768 jects), 743
copiando, 270 aplicação BankExample, 724 SOAP (Simple Object Access
criando, 270 arquivo BankServer.dpr, Protocol), 712, 713, 740,
default.htm, 25 732, 733 741, 742, 743, 766, 767,
excluindo, 270 classe TAccount, 729 768
informações de arquivo, configurando, 725, 726 soquetes, 710, 711
visualizando, 270, 271 implementação de cliente, sponsors, 723, 724
733, 734, 735, 736 TCP (Transmission Control
machine.config, 792, 793
interface IBankManager, Protocol), 764, 765
modos de arquivo, 272 729 visão geral arquitetônica, 718
monitorando, 279, 280, 281 referências, 726, 727 XML-RPC, 711, 712
movendo, 270 unit BankServer_Impl.pas, aplicações Mono ASP.NET, 195,
valores de acesso de arquivo, 728, 729
196, 197
273 unit BankShared.pas, 729
configuração de XSP, 197
valores do tipo enumerado AppDomains, 718, 719
controles Web, 196
FileAttributes, 267, 268 arquitetura cliente/servidor,
distribuição de ASP.NET no
web.config, 792, 793, 795 714
Mono, 197
esquema de arquivo, 793, arquitetura multicamada,
handler de evento
795 715, 716, 717, 718
Button1_Click, 196
seção <appSettings>, 809 arquitetura pear-to-pear, 714,
parâmetros runtime de XSP,
seção <authentication>, 773, 715
197, 198
796 ativação de objeto, 722, 723
portabilidade, 198, 199
seção <authorization>, 796, canais, 724
aplicações orientadas a banco de
797 CAOs (Client Activated Ob-
dados, 642
seção <credentials>, 776, 777 jects), 743, 744, 745, 746,
aplicação de solicitação de
seção <customErrors>, 799 748, 749, 750, 751
download, 678, 679, 684
seção <forms>, 773, 774 classe ChannelServices, 720
controle CheckBoxList, 648
seção <httpRuntime>, 797 classe
controle DataGrid, 72, 239,
seção <location>, 795 RemotingConfiguration,
240, 241, 343, 369, 370,
seção <pages>, 798 719, 720
384, 667, 668, 669, 670,
seção <processModel>, 800, codificação binária, 765,
671, 672, 673, 675, 676,
801, 802 766, 767, 768
677, 678
seção <sessionState>, 798 COM-Interop, 712
controle DataList, 662, 663,
seção <trace>, 807, 808 configuração, 755, 757, 758,
664, 665, 666, 667
seções de configuração 759
controle DropDownList, 650,
personalizadas, 809, 810 CORBA (Common Object
651, 652
array of, instrução, 69 Request Broker Architec-
controle ListBoxes, 653, 654
ture), 711
ArrayList, coleção, 227
Índice 876

controle RadioButtonList, classes, 601 819, 856, 857, 858, 859,


655, 656, 657 HTTPCookie, 607 860, 861
controle Repeater, 657, 658, HTTPRequest, 605 propriedades, 850, 851
659, 660, 661 HTTPResponse, 601, 604, renderização personalizada,
expressões de vinculação, 605 853, 855
avaliando, 660 Page, 608, 609 tipo de bloco HTML, 855, 856
vinculação de dados comunicação de TNewUserInfoControl, 150,
navegador/servidor Web, 235, 236, 237, 280, 299,
complexa, 647
594, 595 383, 385, 388, 389, 518,
vinculação de dados simples,
519, 524, 525, 545, 615,
643, 644, 647 controles de servidor, 610, 611
666, 680, 681, 682, 683,
armazenamento em cache de controles de HTML, 611
699, 700, 707, 854, 862,
página, 814 controles de lista,
863, 864, 865, 866, 867,
armazenamento em cache da pré-preenchendo, 616, 868, 869
saída, 802, 803 617, 618 valores persistentes, 81, 100,
arquivo machine.config, 792, controles de servidor Web, 129, 131, 161, 178, 179,
793 611 180, 222, 225, 259, 308,
arquivo web.config, 792, 793, controles de usuário, 611 353, 373, 395, 440, 522,
795 controles de validação, 501, 523, 526, 554, 560, 562,
esquema de arquivo, 793, 611, 618, 619, 620, 621, 606, 687, 688, 728, 756,
795 623, 624, 625 794, 824, 837, 851, 852
seção <appSettings>, 809 controles vinculados a da- CSS (Cascading Style Sheets),
seção <authentication>, 796 dos, 611 627, 628
seção <authorization>, 796, propriedades fortemente distribuição, 787
797 tipificadas WebControl, diretórios virtuais, 787, 788,
seção <customErrors>, 799 300, 571, 572, 573, 625, 789, 790, 791
seção <httpRuntime>, 797 627 distribuição XCOPY, 791, 792
seção <location>, 795 controles de usuário, 840 extensão de arquivo .aspx, 595,
seção <pages>, 798
arquivos *.ascx, 843 598
seção <processModel>, 800,
classe UserControl, 843 gerenciamento de estado, 825
801, 802
criando, 840, 841, 842 cookies, 825, 826, 827, 828
seção <sessionState>, 798
declarando, 842, 843 gerenciamento de estado de
seção <trace>, 807, 808
seções de configuração exemplo SimpleUserControl, aplicação, 835, 836, 837,
personalizadas, 809, 810 840, 841, 842, 843, 844, 838
autenticação, 770 845 gerenciamento de estado de
autenticação baseada em LoginControl, 845, 846, 848 sessão, 830, 831, 832, 833,
formulário, 772, 773, 775, exemplo SimpleUserControl, 834, 835
776, 778, 780 843, 845 SQL Server, 834
autenticação Passport, 780, tag@ Control %>, 844 ViewState, 828, 830
781 tag@ Register %>, 845 monitorando, 803, 804
autenticação Windows, 770, controles Web, 848 personificação, 785, 786
771, 772 ASP.NET Server Controls and processos trabalhadores,
configurando, 770 Components, 869 reiniciando, 800, 801, 802
definição, 769 controles compostos, 150, rastreando, 804
autorização 235, 236, 237, 280, 299, rastreamento no nível da
autorização baseada em 383, 385, 388, 389, 518, aplicação, 807, 808
papéis, 783, 784, 785 519, 524, 525, 545, 615, rastreamento no nível da
autorização de arquivo, 781 666, 680, 681, 682, 683, página, 804, 806, 807
autorização de URL, 782, 783 699, 700, 707, 854, 862, redirecionamento de erros, 799,
definição, 769 863, 864, 865, 866, 867, 800
cache, 811 868, 869 validação, 618
cache de dados, 816, 817, criando, 848, 849, 850, 851 classe CustomValidator, 625
818, 819, 820 dados postback, tratando, classe RangeValidator, 624
cache de fragmentos de 75, 100, 177, 229, 232, classe BaseValidator, 618, 619
233, 234, 252, 284, 285, classe CompareValidator, 620,
página, 816
304, 307, 308, 309, 316, 621
cache de página, 811, 812,
317, 318, 319, 320, 321, classe CustomValidator, 624
813, 815, 816
322, 327, 331, 332, 333, classe RangeValidator, 623
cache utilizando métodos de 334, 335, 345, 346, 378,
retorno, 823, 825 classe
453, 461, 471, 490, 493,
dependências de arquivo de RegularExpressionValidator,
571, 573, 575, 604, 606,
cache, 820, 821, 823 621, 623
660, 665, 729, 730, 731,
classe RequiredFieldValidator,
Índice 877

619, 620 Borland.Delphi.dll, 121 exemplo, 373


classe ValidationSummary, carregando dinamicamente, método
625 137, 138 GetCallingAssembly( ), 374
validação do lado do cliente, chamando a partir de C#, 135 método
618 construindo com bibliotecas GetCustomAttributes( ),
validação do lado do diretiva 374
servidor, 618 %DelphiDotNetAssembly método GetModules( ), 374
WebForms, 595, 611 Compiler, 133 método
adicionando controles, 595 projeto de biblioteca de GetReferencedAssemblies( ),
adicionando controles teste, 132 374
dinamicamente, 639, 640, projeto de teste de método GetTypes( ), 374
641 biblioteca, 130 propriedade FullName, 374
arquivos, carregando unit de teste, 132, 133 propriedade
clientes, 635, 636 construindo com packages GlobalAssemblyCache, 374
propriedade Location, 374
comunicação baseada em arquivo package de exemplo,
ReflectAssembly( ), 374
evento, 599, 600 125, 127
registrando, 409, 410
campo VIEWSTATE, 600 atributos assembly, 127
vantagens de, 124, 125
code-behind, 600, 601 cláusula contains, 127
visualizando conteúdo de, 121,
controles de lista, cláusula requires, 127
123
pré-preenchendo, 616, diretiva package, 127
617, 618 instalação de package, 135 AssemblyBuilder, classe, 387
criando, 595, 613 projeto package de teste, AssemblyKeyFile, atributo, 136
estrutura de página, 596, 128, 129, 130 AssemblyTitle, atributo, 127
597, 598, 599 tipos de arquivo, 128 AssemblyVersion, atributo, 127
eventos Load, 613, 614 definição, 93, 121 asterisco (*), 782, 813
eventos postback, 608, 609 dependências
at, símbolo (@), 462
formatando, 300, 571, 572, visualizando, 121
573, 625, 627, 628, 629 distribuição de componente, atalhos (teclado), 30
handlers de evento, 595 125 ativando objetos, 722
herança de página, 601 distribuindo, 123, 124 ativação por singleton, 722
imagens, 638, 639 executando sob Mono, 191 ativação single-call, 722
layout de página, 612 aplicação console no client-activated, 723
navegando entre, 629, 630, MonoMenu .NET, 191, atribuição, operadores, 55
631, 632, 633 192 atribuindo nomes fortes a assem-
manutenção de estado, 600 erros de compilador, 194 blies, 136
ordem de processamento de unit MonoFuncs, 193 atributos, 109, 110
evento, 616 GAC (Global Assembly Cache),
painéis, 633, 635 atributos assembly, 127
123
respostas de correio atributos. Ver propriedades, 798
Interop Assemblies, 400, 401
eletrônico, enviando, 636, Attachments, propriedade (classe
componentes de, 402, 403
637, 638 MailMessage), 637
criando, 402
salvando informações a Attributes property (classe
partir de, 614, 615, 616 opção Copy Local, 401
personalizando, 408, 409 FileSystemInfo), 268
separação de
projeto/programação, 601 RCWs (Runtime Callable Attributes, valor (tipo
simulação de múltiplos Wrappers), 402 enumerado NotifyFilters), 281
formulários, 633, 635 manifestos atualizando
validação, 618, 619, 620, definição, 122 origens de dados, 528
621, 623, 624, 625 métodos, 374 atualizando dados após
visualizador de imagens mscorlib.dll, 121 atualizações, 555, 557
baseado em miniaturas, particionamento de aplicação, classe SQLCommand, 531,
639, 640, 641 125 532, 534, 535, 536, 537,
worker Processes, 595 PIAs (Primary Interop Assem- 539
ASP.NET Server Controls and blies), 406, 407, 409 classe SQLCommandBuilder,
Components, 869 criando, 406 528, 529, 530
aspnet_wp.exe, 595 nomeando, 407 classe SQLDataAdapter, 539,
.aspx, extensão de arquivo, 595, personalizando, 408, 409 540, 541, 543, 544
598 registrando, 407 questões de concorrência,
propriedades, 374 551, 552, 553, 554, 555
assemblies, 13
referenciando, 134 SQLCommandBuilder, 531
assemblies básicos, 121
reflection, 372, 374, 375 stored procedure, 544, 545,
atribuição de nomes fortes, 136
listagem de código de
878 Índice

546, 547, 548, 550, 551 COM com vinculação inicial, vinculação de dados simples,
atualizando dados após 397, 398, 399 643, 644, 647
atualizações, 555, 557 Microsoft Word campos monetários, 525
evento RowUpdated, 557 automação com vinculação conexões, 449
instrução INSERT, 555 tardia, 395 abrindo, 452, 453
instrução UPDATE, 556 objeto Automation, classe BdpConnection, 580,
valores UpdatedRowSource, instanciando, 394 581
555, 556 registrando assemblies .NET eventos, 453, 454, 455
<authentication>, seção para, 409, 410 fechando, 452, 453
(arquivo web.config), 773 vinculação inicial com COM, interface IDbConnection, 449,
400 450
<authentication>, seção
automação com vinculação OdbcConnection.Connection
(ASP.NET web.config), 796
tardia, 393, 394, 395, 411, 412 String, 451, 452
autenticação, 770 OleDbConnection.Connectio
autenticação baseada em Automation, objeto
nString, 451
formulário, 772, 773 instanciando, 394 pool, 455
autenticação Passport, 780, 781 AutoResetEvent, classe, 360 SqlConnection.ConnectionStr
autenticação Windows, 770, autorização ing, 450, 451
771, 772 autorização baseada em papéis, consultas
autenticação Basic, 772 783, 784, 785 consultas parametrizadas,
autenticação Digest, 772 autorização de arquivo, 781 462, 464
autenticação NTLM, 770, autorização de URL, 782, 783 DataAdapters, 445, 446, 475
772 definição, 769 BDPDataAdapter, 475
configurando, 770, 772 classe BdpDataAdapter, 583,
configurando, 770 584
definição, 769 B composição de, 475, 476
Web Services, 82, 83, 85, 108, BackColor, propriedade (classe criando, 476, 478
117, 120, 190, 191, 211, 241, WebControl), 626 interface IDBDataAdapter, 475
255, 278, 280, 300, 376, 460, resultados de consulta,
Background, estado de thread,
516, 541, 542, 543, 550, 638, mapeando, 480, 482, 483
348
640, 641, 646, 706, 708 resultados de consulta,
bancos de dados recuperando, 478, 479, 480
autenticação baseada em resultsets SQLDataAdapter, 475
formulário, 772, 773 resultsets únicos, DataReaders, 446, 467
arquivo web.config consultando, 468, 469 resultsets únicos,
seção <authentication>, 773 múltiplos resultsets, consultando, 468, 469
seção <credentials>, 776, 777 consultando, 469, 470 classe BdpDataReader, 582,
seção <forms>, 773, 774 Ver também consultas, 545 583
arquivos .xml, 777, 778 aplicações orientadas a banco classe SQLDataReader, 468,
páginas individuais, de dados ASP.NET, 642 469
autenticando, 774, 775, 776 aplicação de solicitação de dados de BLOB, recuperando,
SQL Server, 778, 779, 780 download, 678, 679, 684 470, 472
Authenticate( ), método, 708, controle CheckBoxList, 648 informações de estrutura,
777 controle DataGrid, 72, 239,
AuthenticateRequest, evento, recuperando, 472, 473
240, 241, 343, 369, 370, interface IDataReader, 468
784
384, 667, 668, 669, 670, múltiplos resultsets,
Auto, valor (tipo enumerado
671, 672, 673, 675, 676, consultando, 469, 470
CharSet), 422
AutoEventWireup, atributo 677, 678 DataSets, 447
(páginas ASP.NET), 598 controle DataList, 662, 663, adicionando tabelas, 486
AutoEventWireup, atributo (tag 664, 665, 666, 667 clonando, 486
@ Control %>), 844 controle DropDownList, 650, composição de, 484
AutoEventWireup, atributo (tag 651, 652 copiando, 486
<pages>), 798 controle ListBoxes, 653, 654 criando, 485, 486
AutoIncrement, propriedade controle RadioButtonList, DataRelationCollection, 485
(DataColumns), 488 655, 656, 657 DataSets de tipificação forte,
AutoIncrementSeed, controle Repeater, 657, 658, 484
propriedade (DataColumns), 659, 660, 661 DataSets não-tipificados, 483
488 expressões de vinculação, DataTableCollection, 484, 485
automação avaliando, 660 DataTables, 484, 485
automação com vinculação vinculação de dados DataViewManager, 485
tardia, 393, 394, 395, 411, complexa, 647 definição, 483
412
Índice 879

indexando tabelas, 487 renderizando imagens de, 666 584


preenchendo, 478, 479 tabelas classe BdpDataReader, 582, 583
removendo tabelas, 486 navegando a partir de classe BdpParameter, 584, 585
DataTables, 447, 487 páginas Web, 644 classe BdpParameterCollection,
adicionando DataSets, 486 vinculando CheckBoxLists a, 584, 585
chave primária, 489 649, 650 classe BdpTransaction, 586
colunas, 487, 488 vinculando DropDownLists Command Text Editor, 587
Constraint, 447 a, 651, 652 Connections Editor, 587
DataColumns, 447 vinculando ListBoxes a, 654 Parameter Collection Editor, 587
DataRelation, 447 vinculando RadioButtonLists visão geral de arquitetura, 578,
DataRelations, 492, 494 a, 653, 656, 657 579
DataRows, 447, 495, 496, BankExample, aplicação, 724 bdpCn_StateChange( ), handler
497, 499 arquivo BankServer.dpr, 732 de evento, 581
DataTableCollection, 447 classe TAccount, 729 BdpCommand, classe, 581, 582
indexando, 487 configurando, 725, 726 BdpConnection, classe, 580, 581
pesquisando com método implementação de cliente, 733, BdpDataAdapter, classe, 583, 584
Find( ), 497, 498 734, 735, 736 BDPDataAdapter, classe, 475
pesquisando com método Se- handler de evento Click do BdpDataReader, classe, 582, 583
lect( ), 498, 499 botão Refresh, 734 BdpParameter, classe, 462, 584,
preenchendo, 479 handler de evento Click do 585
removendo a partir de botão Retrieve, 735 BdpParameterCollection, classe,
DataSets, 486 handler de evento Click do 584, 585
restrições, 489, 490, 491, 492 botão Transfer, 736 BdpTransaction, classe, 586
DataViews, 447 handler de evento OnLoad, .bdsproj, extensão de arquivo,
execução de comando, 456 128
734
classe CommandBuilder, Begin SeekOrigin, valor, 273
interface de IBankManager, 729
466, 467 Begin( ), método, 565
referências, 726, 727
begin, instrução, 82
comandos DDL (Data Defini- unit BankServer_Impl.pas, 728, BeginEdit( ), método, 495, 496,
tion Language), 460, 462 729 577
comandos não-consulta, unit BankShared.pas, 729 BeginGetEmployees( ), método,
458, 459 BankServer.dpr, arquivo, 732, 703, 705
interface IDbCommand, 733 BeginInvoke( ), função, 554
456, 457 BeginInvoke( ), método, 352,
BankServer_Impl.pas, unit, 728,
método ExecuteScalar( ), 364, 365
729
459, 460 BeginRead( ), método, 278, 279
parâmetros, derivando, 466, BankShared.pas, unit, 729
BeginTransaction( ), método,
467 barra (/), 267 450, 558, 586
parâmetros, especificando notação de comentário (//), 49 BeginWrite( ), método, 278
com IDbParameter, 462, barra (092), 267 BelowNormal, prioriedade de
464 barra invertida, (\), 261 thread, 347
stored procedure, 464, 465, Bezier, splines, 149, 151, 152
barras de ferramentas
466 bibliotecas
Align, 27
valores únicos, recuperando, Ver também assemblies, 121
Position, 27
459, 460 biblioteca de classe, 5, 6, 17
Spacing, 27
origens de dados, atualizando, Borland.Delphi.dll, 121
BaseValidator, classe, 618, 619 comparadas com packages, 133,
528
atualizando dados após Basic, autenticação (Windows), 134
atualizações, 555, 557 772 exportações de DLL Win32 no
classe SQLCommand, 531, BasicUserControlPage .aspx, código .NET, 419, 420
532, 534, 535, 536, 537, arquivo, 845 códigos de erro HResult, 428,
539 Bcc, propriedade (classe 429, 430
classe SQLCommandBuilder, MailMessage), 637 códigos de erro Win32, 426,
528, 529, 530 427, 428
BDP (Borland Data Provider),
classe SQLDataAdapter, 539, empacotamento (marshaling),
578, 579, 580
540, 541, 543, 544 423, 424, 425, 426
questões de concorrência, caixa de diálogo Data Adapter
questões de desempenho, 430,
551, 552, 553, 554, 555 Configuration, 588, 589
431, 432, 433, 434, 435
SQLCommandBuilder, 531 classe BdpCommand, 581, 582
sintaxe de atributo
stored procedure, 544, 545, classe BdpConnection, 580,
personalizado, 421, 422,
546, 547, 548, 549, 550, 581
423
551 classe BdpDataAdapter, 583,
sintaxe Delphi tradicional,
880 Índice

420, 421 blittable, tipos, 415 c_sel_emp, constante, 479


tipos de parâmetro, 423, 424, BLOB (Binary Large Object), da- cabeçalhos
425, 426 dos cabeçalhos de unit, 40
tratamento de erro, 426 recuperando, 470, 472 títulos de arquivos de programa,
GDI+, 139 39
blocos de código
classe Graphics, 140 cabeçalhos (HTTP), 594
recolhendo/expandindo, 30
classe GraphicsPath, 147, cabeçalhos (SOAP), 740, 741
recuando, 32
148, 156, 157
Regions, 31 cache
classe Pen, 142, 143
Body, propriedade (classe armazenamento em cache da
classes Brush, 142, 143, 144,
MailMessage), 637 saída (ASP.NET), 802, 803
145
BodyEncoding, propriedade GAC (Global Assembly Cache),
curvas, desenhando, 148,
(classe MailMessage), 637 123
149, 151, 152
elipses, desenhando, 153, BodyFormat, propriedade cache (ASP.NET), 811
154 (classe MailMessage), 637 cache de dados, 816
exemplo de animação, 174, cache de fragmentos de página,
BorderColor, propriedade
175, 176, 177, 182 816
(classe WebControl), 626
imagens. Ver imagens, 161 cache de página, 811
BorderStyle, propriedade (classe cache utilizando métodos de re-
linhas, desenhando, 142, WebControl), 626
143, 144, 145, 147, 148 torno, 823, 825
BorderWidth, propriedade dependências de arquivo de
métod ClearCanvas( ), 142
(classe WebControl), 626 cache, 820, 821, 823
método ClearCanvas( ), 142
namespaces, 139, 140 Borland Data Provider. Ver criando, 820, 821
polígonos, desenhando, 154 BDP, 578 método GetData( ), 821, 823
regiões, 157, 159, 160, 161 Borland, site da Web, 24 cache de dados, 816
retângulos, desenhando, Borland.Delphi.dll, 121 classe Cache, 817, 818
152, 153 Borland.Delphi.System, unit, 39 exemplo, 818, 819
sistema de coordenadas Win- métodos, 817, 818
Borland.Vcl.ComObj, unit, 429
dows, 140, 142 propriedades, 817, 818
Borland.Vcl.SysUtils, unit, 429 exemplo, 819, 820
tortas, desenhando, 154, 155
Interop Type Libraries, 412, Both, valor (propriedade cache de página, 811
413 UpdatedRowSource), 556 diretiva @ OutputCache, 812,
componentes de, 412, 413 BoundColumn, tipo de coluna, 813
criando, 412 668 arquivo .aspx de exemplo,
mscoree.dll, 410 boxing (empacotamento), 59, 812
mscorlib.dll, 121 60 atributos, 812, 813
projeto de biblioteca de teste break, instrução, 85 variando por cabeçalhos, 815
D8DG.LibU.pas unit, 133 Brush, classes, 142, 143, 144, variando por parâmetros, 813,
145 815
D8DG.TestLib.dpr, 132
btnLogin_Click( ), handler de variando por strings
projeto de teste de biblioteca
evento, 775 personalizadas, 815, 816
D8DG.TestLib.dpr, 130
btnSubmit_Click( ), handler de
unit D8DG.LibU.pas, 132 evento, 199 Cache, classe, 817, 818, 837, 838
Binary Large Object (BLOB), da- buffer, atributo (tag <pages>), exemplo, 818
dos 798 métodos, 817, 818
recuperando, 470, 472 Buffer, parâmetro (método propriedades, 817, 818
BinaryFormatter, classe, 283 BeginRead( )), 278 CacheText ControlStyle, valor do
BufferedStreams, 264 tipo enumerado, 336
BinaryReaders, 264, 275, 276
Button1_Click, handler de caixas de diálogo
BinarySearch( ), método, 227 evento, 196, 595 Add Reference, 726
BinaryWriters, 264, 275, 276 ButtonColumn, tipo de coluna,
Add To-Do, 36
Bindable, propriedade 668
Add Web Reference, 696, 697
(controles Web), 851 BytesToHex( ), método, 780
Application Name, 686
binder, parâmetro (método Data Adapter Configuration,
InvokeMember( )), 382 C 588, 589
BindingContext, classe, 515 Folder Properties, 781
C#
BindingManagerBase, classe, New Items, 291, 686
assemblies, chamando, 135
515 Processing Transaction, 189
C# com Delphi, comparação de Table Mappings, 567
BitBlt( ), método, 174 tipo, 60, 61 Users, 770
Bitmap, classe, 162 c_cnstr, constante, 201, 479 Virtual Directory Properties, 789
bitwise, operadores, 58
Índice 881

caixas de seleção , 137, 138 AutoResetEvent, 360


controle CheckBoxList Cascade, valor (propriedade BaseValidator, 618, 619
propriedades, 648 AcceptRejectRule), 490 BdpCommand, 581, 582
vinculando a arrays, 649 Cascade, valor (propriedade BdpConnection, 580, 581
vinculando a tabelas de DeleteRule), 491 BdpDataAdapter, 583, 584
banco de dados, 649, 650 BDPDataAdapter, 475
Cascade, valor (propriedade
Callback, parâmetro (método BdpDataReader, 582, 583
UpdateRule), 491
BeginRead( )), 279 BdpParameter, 462, 584, 585
Cascading Style Sheets (CSS),
BdpParameterCollection, 584,
caminhos 612, 627, 628
585
classe GraphicsPath, 156, 157 case, instrução, 82 BdpTransaction, 586
campos, 96, 97 Category, propriedade BinaryFormatter, 283
definição, 94 (controles Web), 851 BinaryReader, 264, 275, 276
especificadores de visibilidade, BinaryWriter, 264, 275, 276
Cc, propriedade (classe
105, 106, 107 BindingContext, 515
MailMessage), 637
estáticos, 96 BindingManagerBase, 515
monetários, 525 CCWs (COM Callable Wrap-
pers), 409 Bitmap, 162
private, 106 BufferedStream, 264
protected, 106 .cfg, extensão de arquivo, 128
Cache, 817, 818, 837, 838
public, 106 chamando assemblies a partir
exemplo, 818
published, 106 de C#, 135
métodos, 817, 818
strict private, 106 Change( ), método, 351 propriedades, 817, 818
strict protected, 106 Changed, evento, 281 campos, 96, 97
VIEWSTATE, 600 especificadores de visibilidade,
ChangeDatabase( ), método,
canais (Remoting), 724 450 105, 106, 107
Cancel( ), método, 457 ChannelServices, classe, 720 private, 106
cancelando protected, 106
Chaos, nível de isolamento, 564
alterações em DataRow, 496 public, 106
Char, tipo, 61
CancelCommand, evento, 663 published, 106
Chars, propriedade (classe
strict private, 106
CancelCurrentEdit( ), método, StringBuilder), 251
strict protected, 106
520, 521 CharSet, tipo enumerado, 422
chave primária Class Completion, 31
CancelEdit( ), método, 496, 577 classes ancestrais, 289, 290
definindo, 489
CanExtend( ), método, 325 classes friend, 107
chaves
CAOs (Client Activated Objects), classes helper, 107, 108
pares de chaves
743 classes proxy
criando, 136
padrão factory criando, 696, 698
arquivo DPR de cliente, 749 chaves ({ }), 49, 253 exemplos, 698, 700, 701, 703,
arquivo DPR de servidor, 748 CIL (Common Intermediate 704
classe de TSimpleServer, 746 Language), 14, 15, 16 classes thread-safe, 361
classe TSimpleFactory, 744, Class Completion, 31 método synchronized( ), 361
747 class var, bloco, 97 propriedade IsSynchronized,
definição, 743, 744 362
interface ISimpleFactory, class, instrução, 95, 102
propriedade SyncRoot, 362
744, 745 classe, métodos, 98
ClientDataSet, 484
interface ISimpleServer, 745 classes, 78 CollectionBase, 234, 237
problemas de, 751 (ADO.NET), 447 Command, 446
capacidade de reutilização de Ver também componentes, 446 CommandBuilder, 466, 467
Web Services, 10 Ver também objetos, 96 CompareValidator, 620, 621
Capacity, propriedade (classe ApartmentState, 348, 349 exemplo de, 620, 621
StringBuilder), 251 ArithmeticException, 117 propriedades, 620
ArrayList, 227 Component, 290
Capacity, propriedade (coleção
construtores, 221, 225, 227 ConfigurationSettings, 809
ArrayList), 227
exemplo, 230 Connection, 446
Caption, propriedade
métodos, 227, 228 Constraint, 447
(DataColumns), 488
propriedades, 227 ConstructorBuilder, 387
carregando Assembly construtores, 211
arquivos de clientes, 635, 636 métodos, 374 Control, 290
assemblies, 137, 138 propriedades, 374 método BeginInvoke( ), 364
mapas de bits, 163 reflection, 372, 374, 375 método CreateGraphics( ),
carregando dinamicamente AssemblyBuilder, 387 365, 368
882 Índice

método EndInvoke( ), 364 EmployeeDataTable, 571 HTTPCookie, 607


método Invoke( ), 363 indexando, 487 HTTPRequest, 605
propriedade InvokeRequired, métodos, 573, 574 exemplo, 605
363, 364 propriedades, 573, 574 HTTPResponse, 601, 604, 605
CultureInfo, 248 removendo a partir de redirecionamento de
CurrencyManager, 515 DataSets, 486 navegador, 605
CustomAttributeBuilder, 387 DataTable. Ver DataTables, 487 saída, filtrando, 603, 604
CustomValidator, 624, 625 DataView, 447, 500, 501 texto, gravando em clientes,
DataAdapter, 445, 446, 475 comparados com 602
BDPDataAdapter, 475 DataSources, 501 HttpSessionState, 830
composição de, 475, 476 métodos, 502 IlGenerator, 390
criando, 476, 478 propriedades, 502 ILGenerator, 388
interface IDBDataAdapter, DataViewManager, 485, 502 Interlocked, 355, 358, 359
475 declarando, 78 LinearGradientBrush, 144, 156
processamento de transação, definição, 94 LocalBuilder, 387
562 desserialização, 282 MailMessage, 637
resultados de consulta, DictionaryBase, 238, 241 ManualResetEvent, 359
mapeando, 480, 482, 483 Directory, 263 MD5CryptoServiceProvider, 780
resultados de consulta, DirectoryInfo, 263, 265 MemoryStream, 264
recuperando, 478, 479, DotNETClassForCOM, 413 Metafile, 163
480
EmployeeRow, 360, 574, 575, MethodBuilder, 387, 390
SQLDataAdapter, 475
576 métodos
DataColumn, 447
EnumBuilder, 387 declarando, 97, 98
DataReader, 446
estilo, 628 definição, 97
DataRelation, 447, 492, 494
EventBuilder, 387 especificadores de visibilidade,
DataRow, 447, 495
Exception, 117, 118 105, 106, 107
adicionando linhas, 495
FieldBuilder, 387 métodos de classe, 98
cancelando alterações em,
File, 264 métodos de mensagem, 99
496
FileInfo, 264 métodos dinâmicos, 99
excluindo linhas, 496
FileStream, 264, 271 métodos estáticos, 98
modificando linhas, 495
criando FileStreams, 271, métodos regulares, 98
valores RowState, 496, 497,
272, 274, 275 métodos virtuais, 98
499
dados binários, 275, 276 private, 106
DataSet, 483, 484
gravando em FileStreams, protected, 106
adicionando tabelas, 486
271, 272, 275 public, 106
clonando DataSets, 486
lendo FileStreams, 274, 275 published, 106
composição de, 484
valores de FileAccess, 273 reintroduzindo nomes de
copiando DataSets, 486
valores de FileMode, 272 método, 100
criando Datasets, 485, 486
valores de SeekOrigin, 273 sobrecarregando, 99
DataRelationCollection, 485
FileSystemInfo, 264 sobrescrevendo, 99
DataSets fortemente
propriedades, 268, 269 strict private, 106
tipificados, 484
FileSystemWatcher, 279, 280, strict protected, 106
DataSets não-tipificados, 483
281 variável Self, 100
DataTableCollection, 484,
eventos, 281 Module
485
listagem de código de métodos, 374, 376
DataTables, 484, 485
exemplo, 279 propriedades, 374, 376
DataViewManager, 485
valores de NotifyFilters, 281 reflection, 376, 377
indexando tabelas, 487
ForeignKeyConstraint, 490, Reflection, 375
removendo tabelas, 486
491 ModuleBuilder, 387, 390
DataSets, 447
FormsAuthentication, 775 Monitor, 356, 357
DataSets fortemente
FormsIdentity, 774 Mutex, 356
tipificados, 307, 360, 566,
Graphics, 140 NetworkStream, 264
567, 568, 570, 571, 573,
GraphicsPath, 147, 148, 156, Object, 96
574, 575, 576, 577 157 OdbcConnection
DataTable, 447, 484, 485 HashTable, 230, 231 propriedade
adicionando a DataSets, 486 construtores, 231 ConnectionString, 451, 452
Constraint, 447 exemplo, 225, 228, 232, 233 OleDbConnection
DataColumns, 447 métodos, 231 propriedade
DataRelation, 447 HatchBrush, 144 ConnectionString, 451
DataRows, 447 HttpApplicationState, 836, 837, OleDbParameter, 462
DataTableCollection, 447 838 Page
Índice 883

propriedade IsPostBack, 608, 451 551, 553, 561, 563, 606, 659,
609 SqlDataAdapter 759, 761, 762
Parameter, 446 stored procedure, 544 TSimpleFactory, 744, 747, 748
ParameterBuilder, 387 SQLDataAdapter, 475, 539, TSimpleServer, 746, 747
Path, 264 540, 541, 543, 544 TStateInfoCollection, 234, 238
PathGradientBrush, 144 exemplo do bloco principal, TStateInfoDictionary, 238, 242
Pen, 142, 143 539 TWebService1, 688
ProcessInfo, 803, 804 método TypeBuilder, 387
PropertyBuilder, 387 GetDataAdapterDelete- TypeConverter, 298
propriedades, 101, 102 Command( ), 543, 544 TypeLibConverter, 412
ProvidePropertyAttribute, 324, método UniqueConstraint, 489
325 GetDataAdapterInsert- UserControl, 290, 843
Queue, 223 Command( ), 541 ValidationSummary, 625
construtor, 224 método WaitHandle, 355, 356
exemplo, 223, 226 GetDataAdapterUpdate- WebControl
métodos, 221, 224 Command( ), 541, 542
propriedades fortemente
RangeValidator, 623, 624 SQLDataReader, 468, 469
tipificadas, 300, 571, 572,
ReaderWriterLock, 357, 358 SqlParameter, 462, 464
573, 625, 627
Rectangle, 152, 153 SqlTransaction, 558
classes (ASP.NET), 601
referências de classe, 100, 101 Ver também processamento
ChannelServices, 720
Region de transação, 558
RemotingConfiguration, 719,
listagem de código de Stack, 220
720
exemplo, 157, 159 construtor, 221
TAccount, 729
recortando, 160, 161 exemplo, 221, 223
métodos, 221 classes ancestrais, 289, 290
RegistrationServices, 409
RegularExpressionValidator, Stream, 264 classes conectadas (ADO.NET),
621, 623 StreamReader, 264 446
RequiredFieldValidator, 619, StreamWriter, 264 classes desconectadas
620 string. Ver strings, 243 (ADO.NET), 447
serialização, 281, 282 StringBuilder, 250 classes proxy
atributo, 282 exemplo, 252 criando, 696, 698
exemplo, 283, 285, 286 métodos, 251 exemplos, 698, 700, 701, 703,
formatadores, 283 propriedades, 251 704
gráficos de objeto, 283 StringReader, 264 classes/métodos thread-safe, 361
interface ISerializable, 282, StringWriter, 264 método synchronized( ), 361
283 Style, 629 propriedade IsSynchronized, 362
SmtpMail, 637 SynchronizationLockExcep- propriedade SyncRoot, 362
SoapHttpClientProtocol, 701 tion, 371 Classic, layout de área de
SolidBrush, 144 System.GC, 210, 211 trabalho, 27
SomeObject, 296 TCriticalSection, 213, 215
ClassIDToProgID( ), método, 429
SQLCommand, 531, 532, 534, TextReader, 264
TextureBrush, 144 classificando
535, 536, 537, 539
TextWriter, 264 DataGrids, 678
instrução DELETE, 537, 538,
thread, 345, 347 DataViews, 511
539
instrução INSERT, 533, 534 Thread, 342 cláusula de namespaces, 46
instrução UPDATE, 535, 536, ThreadAbortException, 368, cláusula de namespaces, 46
537 370 cláusulas. Ver instruções, 191
listagem de código de ThreadInterruptedException, Clear( ), método, 219, 220, 486
exemplo, 531, 532 370 ClearCanvas( ), método, 142
método SubmitAddedRow( ), ThreadPool, 349, 350, 351
Click, hander de evento
534, 535 ThreadPriority, 347, 348
aplicação BankExample, 734,
método SubmitUpdates( ), ThreadState, 348
532, 533 735, 736
ThreadStateException, 371
SQLCommandBuilder, 528, Timer, 351, 352 client-activated, objetos, 723
529, 530, 531 TMyStringWriter, 40 clientConnectedCheck, atributo
limitações, 529 TMyType, 42, 385 (tag <processModel>), 801
salvando atualizações com, TObject, 290 ClientDataSet, classe, 484
530 Transaction, 446 Client Activated Objects. Ver
SqlConnection TRemotingHelper, 70, 85, 92, CAOs, 743
propriedade 96, 126, 127, 132, 173, 195, clientes
ConnectionString, 450, 277, 292, 363, 473, 474, 489, aplicação BankExample, 733,
884 Índice

734, 735, 736 arquivo de configuração de construtores, 231


arquivos DPR de cliente, 85, cliente, 766 exemplo, 225, 228, 232, 233
176, 267, 302, 343, 366, 367, arquivo de configuração de métodos, 231
381, 481, 540, 547, 548, 549, servidor, 765 interfaces
569, 645, 702, 746, 750, 763, código ICollection, 218, 219
764 código gerenciado, 13 IComparer, 218
clientes Web Service (Smart código não-gerenciado, 13 IDictionary, 218, 220
Clients), 9, 10 código inseguro, 73, 74 IDictionaryEnumerator, 218
configuração de .NET código thread-safe, escrevendo, IEnumerable, 218
Remoting, 758, 759 354, 355 IEnumerator, 218, 219, 220
gravando texto em, 602 armazenamento de thread lo- IHashCodeProvider, 218
clonando cal, 360, 361 IList, 218, 219
DataSets, 486 classes/métodos thread-safe, Queue, 223
Clone( ), método, 221, 486 361 construtor, 224
método synchronized( ), 361 exemplo, 223, 226
Close( ), método, 274, 450, 452,
propriedade IsSynchronized, métodos, 221, 224
580
362 Stack, 220
CLR (Common Language construtor, 221
Runtime), 4, 12 propriedade SyncRoot, 362
comunicações entre processos exemplo, 221, 223
assemblies, 13 métodos, 221
código gerenciado, 13 Win32, 361
eventos, 359 TableMappings, 476, 482
código não-gerenciado, 13
classe AutoResetEvent, 360 Collect( ), método, 210
módulos gerenciados, 12, 38
esqueleto de arquivos de classe ManualResetEvent, CollectionBase, classe, 234, 237
programa, 39 359 colunas
módulos principais, 38 mecanismos bloqueadores, 355 (DataColumns), 447, 487
referências circulares de unit, classe Interlocked, 358, 359 (DataGrid), 668
42, 43 classe Monitor, 356, 357 definindo, 488, 488
sintaxe da cláusula uses, 39, classe Mutex, 356 filtrando DataViews por, 506
42 classe ReaderWriterLock, propriedades, 488
units, 39, 40, 41, 45, 46, 47 357, 358 removendo, 488
seqüência de classe WaitHandle, 355, 356 verificando existência de, 488
carregamento/compilação/ códigos de erro (HResult), 428, COM (Component Object Model)
execução, 14 429, 430 Ver também COM Interop,
CLR com Delphi, comparação códigos de erro (Win32), 426, 391
de tipo, 60, 61 427, 428 eventos COM, 404
CLR, cabeçalhos, 12 coerção de tipo, 79, 80 vinculação inicial com COM,
CLS (Common Language coleções, 217 400
Specification), 17 ArrayList, 227 objetos .NET no código COM,
documentação, 17 construtores, 221, 225, 227 409
verificando compatibilidade de exemplo, 230 automação com vinculação
CLS, 17 métodos, 227, 228 tardia, 411, 412
propriedades, 227 CCWs (COM Callable
CLSIDFromProgID( ), método,
coleções fortemente tipificadas, Wrappers), 409
428
234 empacotamento (marshaling),
clusters, 716 415, 416, 417, 418
aplicação de exemplo, 237,
Code Block Indentation, 32 implementação de interface,
238
code browsing, 33 descendentes de 413, 415
Code Editor, 30, 31, 33 CollectionBase, 234, 237 Interop Type Libraries, 412,
Code Explorer, 36 Cookies, 607 413
DataColumnCollection, 487 registro de assembly, 409, 410
Code Folding, 30
DataRelationCollection, 485 tipos de parâmetro, 415, 416,
CodeBehind, atributo (páginas 417, 418
ASP.NET), 598 DataTableCollection, 447, 484,
485 tratamento de erro, 418
CodeBehind, atributo (tag @ objetos COM no código .NET,
dicionários fortemente
Control %>), 844 393
tipificados, 238
code-behind, formulários, 600 aplicação de exemplo, 241, automação com vinculação
codificação binária 242 tardia, 393, 394, 395
comparada com SOAP, 766, descendentes de COM com vinculação inicial,
767, 768 DictionaryBase, 238, 241 397, 398, 399
configurando, 765, 766 HashTable, 230, 231 controle de tempo de vida
Índice 885

COM, 405 registro de assembly, 409, atributo (tag <processModel>),


eventos COM, 403, 405 410 801
Interop Assemblies, 400, sintaxe de atributo CombineMode, tipo enumerado,
401, 402, 403, 408, 409 personalizado, 421, 422, 160
parâmetros de referência, 423 comentários, 49
396, 397 sintaxe Delphi tradicional, aninhando, 49
parâmetros de valor, 396, 420, 421
397 comImpersonationLevel,
tipos de parâmetro, 415, 416,
parâmetros opcionais, 396, atributo (tag <processModel>),
417, 418
397 801
tratamento de erro, 418, 426
PIAs (Primary Interop questões de interoperabilidade COM-Interop, 712
Assemblies), 406, 407, 409 comuns, 392, 393 comma (,), 256
RCWs (Runtime Callable
rotinas .NET no código Win32, Command Text Editor, 587
Wrappers), 402
435, 436 Command, classe, 446
tratamento de erro, 406
declarações de importação, CommandBuilder, classe, 466,
COM Callable Wrappers 440
(CCWs), 409 467
empacotamento, 437, 438,
com estado, 723 439, 440 CommandText, propriedade (in-
sintaxe Delphi tradicional, terface IDbCommand), 457
COM Interop, 391
436, 437 CommandType, propriedade
definição, 393
tipos de parâmetro, 437, 438, (interface IDbCommand), 457
exportações de DLL Win32 no Commit( ), método, 558, 559,
439, 440
código .NET, 419, 420 586
vantagens de, 391, 392
objetos COM no código .NET, Common Intermediate Language
393 comandos. Ver também
(CIL), 14, 15, 16
automação com vinculação instruções. Common Language Runtime. Ver
tardia, 393, 394, 395 classe BdpCommand, 581, 582 CLR, 4, 12
COM com vinculação inicial, classe BdpDataAdapter, 583, Common Language Specification
397, 398, 399 584 (CLS), 17
controle de tempo de vida classe BdpDataReader, 582, 583 documentação, 17
COM, 405 comandos de menu File verificando compatibilidade de
eventos COM, 403, 405 personalizando, 28 CLS, 17
Interop Assemblies, 400, executando contra bancos de Common Object Request Broker
401, 402, 403, 408, 409 dados, 456 Architecture (CORBA), 711
parâmetros de referência, classe CommandBuilder,
Common Type System (CTS), 16,
396, 397 466, 467
17
parâmetros de valor, 396, comandos DDL (Data Defini-
tion Language), 460, 462 comparação, operadores, 55, 56
397
comandos não-consulta, comparando
parâmetros opcionais, 396,
397 458, 459 strings, 246, 247, 248
PIAs (Primary Interop Assem- interface IDbCommand, Compare( ), método, 247, 248
blies), 406, 407, 409 456, 457 CompareOrdinal( ), método, 247,
RCWs (Runtime Callable método ExecuteScalar( ), 248
Wrappers), 402 459, 460 CompareValidator, classe, 620,
tratamento de erro, 406 parâmetros, derivando, 466, 621
objetos .NET no código COM, 467 exemplo de, 620, 621
409 parâmetros, especificando propriedades, 620
automação com vinculação com IDbParameter, 462,
compilação
tardia, 411, 412 464
compilação JIT (just-in-time),
códigos de erro HResult, 428, stored procedure, 464, 465,
14
429, 430 466
valores únicos, recuperando, compilador
códigos de erro Win32, 426,
459, 460 diretivas
427, 428
mcs, 190 $define, 55
CCWs (COM Callable Wrap-
menu Run $IFDEF, 49
pers), 409
Run Without Debugging, $J, 55
empacotamento (marshal-
689 $UNSAFECODE ON, 73, 134
ing), 415, 416, 417, 418
rcd, 188 $WRITEABLECONST, 55
implementação de interface,
rpm, 188 %DelphiDotNetAssembly-
413, 415, 423, 424, 425,
su, 188 Compiler, 133
426
XCOPY, 791, 792 @ OutputCache, 812, 813
Interop Type Libraries, 412,
erros
413 comAuthenticationLevel,
886 Índice

Mono, 194 estrutura de arquivos XML, Connection, classe, 446


Component, classe, 290 755, 757 Connection, propriedade
componentes.Ver também proxies, criando, 759, 761, (interface IDbCommand), 457
classes. 762, 764 Connections Editor, 587
TClientDataSet, 446 XSP, 197 ConnectionString, propriedade
configuração (ASP.NET) (interface IDbConnection), 449
TDataSetProvider, 446
armazenamento em cache da ConnectionString, propriedade,
componentes (.NET), 6 449, 450
saída, 802, 803
comportamento em tempo de classe OdbcConnection, 451,
arquivo machine.config, 792,
projeto (controles WinForms), 452
793
313 classe OleDbConnection, 451
arquivo web.config, 792, 793,
composta, formatação, 252 795 classe SqlConnection, 450, 451
Compressed FileAttribute, 268 esquema de arquivo, 793, ConnectionString, propriedade
comprimento de strings, 795 (classe BdpConnection), 580,
localizando, 250 seção <appSettings>, 809 581
computação móvel, 15 seção <authentication>, 796 console da aplicação MonoMenu
seção <authorization>, 796, .NET, 192
comunicação baseada em
797 console de gerenciamento (IIS),
evento, 599, 600
seção <customErrors>, 799 788
comunicações entre processos
seção <httpRuntime>, 797 const, instrução, 53, 88, 418
(Win32), 361
seção <location>, 795
ComVisibleAttribute, constantes
seção <pages>, 798
parâmetro, 412 c_cnstr, 201, 479
seção <processModel>, 800,
c_sel_emp, 479
Concat( ), método, 248 801, 802
declarando, 53, 54
concatenando seção <sessionState>, 798
retrocompatibilidade, 55
strings, 248, 249 seção <trace>, 807, 808
segurança de tipo, 54
concorrência seções de configuração
tipificadas, 54
gerenciando, 551, 552, 553, personalizadas, 809, 810
ValParam, 397
554, 555 processo monitorando, 803,
804 Constraint, 447
handler de evento
processos trabalhadores, ConstructorBuilder, classe, 387
OnRowUpdated( ), 553
listagem de código de exemplo, reiniciando, 800, 801, 802 construindo assemblies
552 rastreando, 804 bibliotecas
no nível da aplicação, 807, diretiva
condições de teste, 81
808 %DelphiDotNetAssembly-
instrução case, 82
no nível da página, 804, 806, Compiler, 133
instrução if, 81, 82
807 projeto de teste para
instrução switch, 82
redirecionamento de erros, biblioteca, 130, 132
conexões, 449
799, 800 unit de teste, 132, 133
abrindo, 452, 453
configurando packages
classe BdpConnection, 580,
autenticação, 770 arquivo package de exemplo,
581
autenticação Windows, 770, 125, 127
eventos, 453, 454, 455
772 atributos assembly, 127
fechando, 452, 453
codificação binária, 765, 766 cláusula contains, 127
interface IDbConnection, 449,
arquivo de configuração de cláusula requires, 127
450
cliente, 766 diretiva package, 127
OdbcConnection.Connection-
arquivo de configuração de instalação de package, 135
String, 451, 452
servidor, 765 projeto package de teste, 128,
OleDbConnection.Connection-
ConfigurationSettings, classe, 129, 130
String, 451
809 tipos de arquivo, 128
pool, 455
Configure( ), método, 720 construtores, 95, 211, 312
SqlConnection.Connection-
coleção ArrayList, 221, 225, 227
String, 450, 451 conjuntos, 71
coleção HashTable, 231
<configuration>, seções atribuindo valores a, 71
coleção Queue, 224
(ASP.NET web.config), 810 declarando, 71
coleção Stack, 221
configuração interseção, 73
membro, 72 consultando
.NET Remoting, 755
operador de adição (+), 72 múltiplos resultsets, 469, 470
configuração de cliente, 758,
operador de subtração (-), 72 resultsets únicos, 468, 469
759
configuração de servidor, operador in, 72 consultas
757, 758 união, 72 consultas Delete
Índice 887

classe SqlCommand, 537, ContainsKey( ), método, 231 declarando, 842, 843


538 ContainsValue( ), método, 231 exemplo SimpleUserControl,
método 840, 841, 842, 843, 845
continue, instrução, 85
GetDataAdapterDelete- LoginControl, 845, 846, 848
Command( ), 543 Control, classe, 290 SimpleUserControl, 843, 845
stored procedure método BeginInvoke( ), 364 tag <@ Control %>, 844
DeleteProduct, 551 método CreateGraphics( ), 365, tag <@ Register %>, 845
consultas Insert 368 controles Web, 848
classe SQLCommand, 533, método EndInvoke( ), 364, 365 controles compostos, 150,
534 método Invoke( ), 363 235, 236, 237, 280, 299,
método propriedade InvokeRequired, 383, 385, 388, 389, 518,
GetDataAdapterInsert- 363, 364 519, 524, 525, 545, 615,
Command( ), 541 CheckBoxList, controle 666, 680, 681, 682, 683,
stored procedure propriedades, 648 699, 700, 707, 854, 862,
InsertProduct, 547 vinculando a arrays, 649 863, 864, 865, 866, 867,
consultas parametrizadas, 462, vinculando a tabelas de banco 868, 869
464 de dados, 649, 650 criando, 848, 849, 850, 851
recuperando resultados a
ListBox, controle dados postback, 857
partir de, 480
vinculação de dados, 516 dados postback, tratando, 75,
consultas Select
controle de painel, 633, 635 100, 177, 229, 232, 233,
stored procedure
controle de servidor 234, 252, 284, 285, 304,
SelectProduct, 545, 546
controles de lista, 307, 308, 309, 316, 317,
consultas Update
pré-preenchendo, 616, 617, 318, 319, 320, 321, 322,
classe SqlCommand, 535,
618 327, 331, 332, 333, 334,
536, 537
handler de evento OnInit, 335, 345, 346, 378, 453,
método
617, 618 461, 471, 490, 493, 571,
GetDataAdapterUpdate-
método 573, 575, 604, 606, 660,
Command( ), 541
PopulateDdlFromFile( ), 665, 729, 730, 731, 819,
stored procedure
617 856, 857, 858, 859, 860,
UpdateProduct, 549
controle de tempo de vida 861
resultados, mapeando, 480,
(COM), 405 propriedades, 850, 851
482, 483
renderização personalizada,
resultados, recuperando com controles
853, 855
DataAdapters, 478 ASP.NET Server Controls and
tipo de bloco HTML, 855, 856
consultas parametrizadas, Component, 869
TNewUserInfoControl, 150,
480 caixa de Listagem
235, 236, 237, 280, 299,
DataSets, preenchendo, 478, vinculação de dados, 516
479 383, 385, 388, 389, 518,
controle CheckBoxList
DataTables, preenchendo, 519, 524, 525, 545, 615,
propriedades, 648
479 666, 680, 681, 682, 683,
vinculando a arrays, 649
resultados, recuperando com 699, 700, 707, 854, 862,
vinculando a tabelas de
DataAdapters 863, 864, 865, 866, 867,
banco de dados, 649, 650
DataTables, preenchendo, 868, 869
controle DropDownList
479 valores persistentes, 81, 100,
vinculando a arrays, 650,
Ver também comandos, 129, 131, 161, 178, 179,
651
executando contra bancos de vinculando a tabelas de 180, 222, 225, 259, 308,
dados, 462 banco de dados, 651, 652 353, 373, 395, 440, 522,
controle ListBoxes 523, 526, 554, 560, 562,
consumindo
vinculando a arrays, 653 606, 687, 688, 728, 756,
Web Services, 695
vinculando a tabelas de 794, 824, 837, 851, 852
classes proxy, 696, 698, 700,
701, 703, 704 banco de dados, 654 controles (ASP.NET), 610, 611
por meio do diálogo Add controle RadioButtonList controles de HTML, 611
Web Reference, 702, 703, vinculando a arrays, 648, controles de lista,
704 653, 655, 656 pré-preenchendo, 616, 617,
processo de descoberta, 696 vinculando a tabelas de 618
banco de dados, 653, 656, handler de evento OnInit,
ContainerControl ControlStyle,
657 617, 618
tipo enumerado, 336
controles de usuário, 840 PopulateDdlFromFile( ), 617
Contains( ), método, 219, 220,
arquivos *.ascx, 843 controles de servidor Web, 611
487, 488
classe UserControl, 843 controles de usuário, 611
contains, instrução, 127 criando, 840, 841, 842 controles de validação, 611
888 Índice

BaseValidator, 501, 618, 619 destrutores, sobrescrevendo, vinculando a arrays, 648, 653,
CompareValidator, 620, 621 312, 313 655, 656
CustomValidator, 624, 625 interdependências, 311 vinculando a tabelas de banco
RangeValidator, 623, 624 métodos private, 311 de dados, 653, 656, 657
RegularExpressionValidator, métodos protected, 311 controles de servidor
621, 623 métodos public, 312 controles de validação
RequiredFieldValidator, 619, métodos published, 312 CompareValidator, 621
620 métodos strict private, 311 CustomValidator, 625
ValidationSummary, 625 métodos strict protected, 311 RangeValidator, 624
controles vinculados a dados, passos para escrever RegularExpressionValidator,
611 componentes, 289 623
HtmlInputFile, 635, 636 propriedades, 293 ValidationSummary, 625
painel, 633, 635 propriedades de array, 300, Web, 611, 869
propriedades fortemente 302, 303, 304 controles de usuário, 611, 840
tipificadas WebControl, 300, propriedades de objeto, 296, arquivos *.ascx, 843
571, 572, 573, 625, 627 297, 298, 300 classe UserControl, 843
controles (WinForms), 287, 288 propriedades de tipo criando, 840, 841, 842
AlarmClock, 307 enumerado, 294, 295 declarando, 842, 843
classes ancestrais, 289, 290 propriedades simples, 293,
exemplo SimpleUserControl,
comportamento em tempo de 294
840, 841, 842
projeto, 313 valores default, 303
arquivo .ascx, 843
eventos, 304 quando utilizar, 288, 289
arquivo .aspx
criando, 307 testando, 313
BasicUserControlPage, 844
definição, 305 units de componente, 290, declaração, 842
handlers de evento, 305 291, 293 tag <@ Control %>, 844
métodos de despacho de controles compostos Web, 862 tag <@ Register %>, 845
evento, 305, 306 criando, 862 LoginControl, 845, 846, 848
propriedades de evento, 305, TNewUserInfoControl, 150, SimpleUserControl example
306, 309, 310, 311 235, 236, 237, 280, 299, 383, BasicUserControlPage .aspx
exemplo ExplorerViewer, 314 385, 388, 389, 518, 519, 524, file, 845
FillListView( ), método, 323 525, 545, 615, 666, 680, 681, declaração, 843
listagem de código, 315 682, 683, 699, 700, 707, 854, tag <@ Control %>, 844
método ActivateFile( ), 323 862, 863, 864, 865, 866, 867, tag <@ Register %>, 845
método ExtractIcon( ), 323 868, 869 controles de validação
método FillTreeView( ), 323 controles de lista CompareValidator, 621
método GetDirectories( ), pré-preenchendo, 616, 617, CustomValidator, 625
323 618 RangeValidator, 624
método RefreshNode( ), 323 handler de evento OnInit, RegularExpressionValidator, 623
método SHGetFileInfo( ), 617, 618 ValidationSummary, 625
323 método controles iterativos
exemplo PlayingCard, 328 PopulateDdlFromFile( ), controle DataGrid, 667, 668
declaração de classe, 329, 617 adicionando itens, 677
330 controles de lista vinculados a classificando, 678
listagem de código, 331 dados editando, 671, 672, 673, 675,
método InitComp( ), 335 controle CheckBoxList 676, 677
método OnPaint( ), 337 propriedades, 648 paginação, 72, 239, 240, 241,
SetStyle( ), método, 335 vinculando a arrays, 649 343, 369, 370, 384, 668,
valores do tipo enumerado vinculando a tabelas de 669, 670, 671
ControlStyles, 335, 336 banco de dados, 649, 650 tipos de coluna, 667, 668
exemplo SimpleStatusBars, 324 controle DropDownList, 650, controle DataList, 662, 663, 664,
classe 651, 652 665, 666, 667
ProvidePropertyAttribute, vinculando a arrays, 650, declaração, 556, 663, 664
324, 325 651 eventos, 662, 663
interface IExtenderProvider, vinculando a tabelas de exemplo, 664
325, 326 banco de dados, 651, 652
propriedades, 662, 663
listagem de código, 326, 328 controle ListBoxes, 653, 654
renderização de imagem, 666
ícones de componente, 314 vinculando a arrays, 653
templates, 662
implementação de vinculando a tabelas de
TypeConverter, 298, 300 controle Repeater, 657, 658, 659,
banco de dados, 654
métodos, 311 660, 661
controle RadioButtonList, 655,
construtores, 312 declaração, 659
656, 657
Índice 889

eventos, 658 453, 461, 471, 490, 493, listagem de código, 331
exemplo, 658 571, 573, 575, 604, 606, método InitComp( ), 335
propriedades, 658 660, 665, 729, 730, 731, método OnPaint( ), 337
saída, 230, 661 819, 857, 858, 859, 861 SetStyle( ), método, 335
templates, 657, 658 propriedades, 850, 851 valores do tipo enumerado
controles iterativos vinculados a renderização personalizada, ControlStyles, 335, 336
dados 853 exemplo SimpleStatusBars,
controle DataGrid, 667, 668 método Render( ), 853 324
adicionando itens, 677 método RenderContents( ), classe
classificando, 678 853, 855 ProvidePropertyAttribute,
editando, 671, 672, 673, 675, tipo de bloco HTML, 855, 856 324, 325
676, 677 TNewUserInfoControl, 150, interface IExtenderProvider,
paginação, 72, 239, 240, 241, 235, 236, 237, 280, 299, 383, 325, 326
343, 369, 370, 384, 668, 385, 388, 389, 518, 519, 524, listagem de código, 326, 328
669, 670, 671 525, 545, 615, 666, 680, 681, ícones de componente, 314
tipos de coluna, 667, 668 682, 683, 699, 700, 707, 854, implementação de
controle DataList, 662, 663, 862, 863, 864, 865, 866, 867, TypeConverter, 298, 300
664, 665, 666, 667 868, 869 métodos, 311
declaração, 556, 663, 664 valores persistentes, 81, 100, construtores, 312
eventos, 662, 663 129, 131, 161, 178, 179, 180, destrutores, sobrescrevendo,
exemplo, 664 222, 225, 259, 308, 353, 373, 312, 313
propriedades, 662, 663 395, 440, 522, 523, 526, 554, interdependências, 311
renderização de imagem, 666 560, 562, 606, 687, 688, 728, métodos private, 311
templates, 662 756, 794, 824, 837, 851, 852 métodos protected, 311
controle Repeater, 657, 658, controles Web persistentes, 81, métodos public, 312
659, 660, 661 100, 129, 131, 161, 178, 179, métodos published, 312
declaração, 659 180, 222, 225, 259, 308, 353, métodos strict private, 311
eventos, 658 373, 395, 440, 522, 523, 526, métodos strict protected, 311
exemplo, 658 554, 560, 562, 606, 687, 688, passos para escrever
propriedades, 658 728, 756, 794, 824, 837, 851, componentes, 289
saída, 230, 661 852 propriedades, 293
templates, 657, 658 controles WinForms, 287, 288 propriedades de array, 300,
controles Web, 848 302, 303, 304
controles WinForms
controles compostos, 862 propriedades de objeto, 296,
personalizados, 287, 288
criando, 862 297, 298, 300
AlarmClock, 307
TNewUserInfoControl, 150, propriedades de tipo
classes ancestrais, 289, 290
235, 236, 237, 280, 299, enumerado, 294, 295
comportamento em tempo de
383, 385, 388, 389, 518, propriedades simples, 293,
projeto, 313
519, 524, 525, 545, 615, 294
eventos, 304
666, 680, 681, 682, 683, valores default, 303
criando, 307
699, 700, 707, 854, 862, quando utilizar, 288, 289
definição, 305
863, 864, 865, 866, 867, testando, 313
868, 869 handlers de evento, 305
units de componente, 290, 291,
criando, 848, 849, 850, 851 métodos de despacho de
293
dados postback, tratando, 856 evento, 305, 306
propriedades de evento, 305, ControlStyles, valores do tipo
interface enumerado, 335, 336
IPostBackDataHandler, 306, 309, 310, 311
exemplo ExplorerViewer, 314 ControlToCompare, propriedade
857
FillListView( ), método, 323 (classe CompareValidator), 620
interface
IPostBackEventHandler, listagem de código, 315 ControlToValidate, propriedade
856 método ActivateFile( ), 323 (classe BaseValidator), 619
método LoadPostData( ), 857 método ExtractIcon( ), 323 ConvertAssemblyToTypeLib( ),
método método FillTreeView( ), 323 método, 412
RaisePostBackEvent( ), 857 método GetDirectories( ), convertendo
TPostbackInputWebControl, 323 namespaces, 46
75, 100, 177, 229, 232, método RefreshNode( ), 323 strings em letras maiúsculas, 250
233, 234, 252, 284, 285, método SHGetFileInfo( ), strings em letras minúsculas,
304, 307, 308, 309, 316, 323 250
317, 318, 319, 320, 321, exemplo PlayingCard, 328
ConvertFrom( ), método, 298
322, 327, 331, 332, 333, declaração de classe, 329,
ConvertTo( ), método, 298
334, 335, 345, 346, 378, 330
890 Índice

ConvertTypeLibToAssembly( ), CreateDirectory( ), método, 265 splines de Bezier, 149, 151, 152


método, 402, 407 CreateGraphics( ), método, 364 <customErrors>, seção (ASP.NET
CreateInstance( ), método, 386, web.config), 799
cookies
394
(classe HTTPCookie), 607 CustomAttributeBuilder, classe,
CreateInstanceFrom( ), método,
avaliando conteúdo de, 607 381 387
cookies persistentes, 826, 827 CreateNew, modo de arquivo, CustomValidator, classe, 624, 625
criando, 826 272
definição, 825 CreateOleObject( ), método, 393
desvantagens, 827, 828 CreateParameter( ), método, 457
D
excluindo, 607 CreateProcess( ), método, 422, d, especificador de formato, 257,
recuperando valores de cookie, 424 260
826 CreateProcessA( ), método, 422 D, especificador de formato, 257
Cookies, coleção, 607 CreateProcessW( ), método, 422
D8DG.LibU.pas, 132
CreateText( ), método, 270
coordenadas globais, 173, 174 D8DG.PkgUnit, 129
CreateWLCall( ), método, 389,
copiando 390 D8DG.TestLib.dpr, 130
arquivos, 270 CreationTime, propriedade D8DG.TestPkg, 128
datasets, 486 (classe FileSystemInfo), 268
dados de postback, tratando
diretórios, 266, 267, 268 CreationTime, valor (tipo
TPostbackInputWebControl, 860
strings, 250 enumerado NotifyFilters), 281
CreationTimeUtc, propriedade dados desconectados,
Copy Local, opção (Interop
(classe FileSystemInfo), 268 arquitetura, 443, 444
Assemblies), 401
creative round tripping, 408, dados do processo global, 338
Copy( ), função, 69
409 dados hierárquicos, 577
Copy( ), método, 250, 486 seção <credentials> (arquivo
dados postback, 857
CopyDirectory( ), método, 266 web.config), 776, 777, 796
dados postback, tratamento, 856
CopyTo( ), método, 219, 502 criando threads manualmente
interface IPostBackDataHandler,
COR_E_FILENOTFOUND, criando com métodos de
857
mensagem de erro, 406 instância, 344
interface
CORBA (Common Object Re- criando com métodos estáticos,
IPostBackEventHandler, 856
quest Broker Architecture), 345
método LoadPostData( ), 857
711 definição, 344
método RaisePostBackEvent( ),
corpo (packages de resposta de criptografia 857
HTTP), 594 assemblies, 136 TPostbackInputWebControl, 75,
corpo (packages SOAP), 741 critérios de classificação 100, 177, 229, 232, 233, 234,
(método Select( )), 498 252, 284, 285, 304, 307, 308,
correio eletrônico
classe MailMessage, 637 CSS (Cascading Style Sheets), 309, 316, 317, 318, 319, 320,
classe SmtpMail, 637 612, 627, 628 321, 322, 327, 331, 332, 333,
enviando a partir de CssClass, propriedade (classe 334, 335, 345, 346, 378, 453,
formulários, 636, 637, 638 WebControl), 626 461, 471, 490, 493, 571, 573,
CTS (Common Type System), 16 575, 604, 606, 660, 665, 729,
Count, propriedade
Reference Types, 17 730, 731, 819, 857, 858, 859,
interface ICollection, 219
861
Count, propriedade (DataTable), Value Types, 17
573 culture, parâmetro (método Data Adapter Configuration,
Count, propriedade InvokeMember( )), 382 caixa de diálogo, 588, 589
(classeDataView), 501 Data Definition Language (DDL),
CultureInfo, classe, 248
Count, parâmetro (método comandos, 460, 462
BeginRead( )), 279 CurrencyManager, classe, 515
Data Explorer, 34
Count, propriedade (classe CurrencyStringToDecimal( ),
Cache), 817 método, 525 Data Provider. Ver BDP (Borland
cpuMask, atributo (tag Data Provider), 578
Current DataRowVersion, 497
<processModel>), 801 data/hora, especificadores de
Current, propriedade
Create, modo de arquivo, 272 formato, 257, 258, 259, 260,
interface IEnumerator, 220
Create( ), método, 95, 163, 265, 261
272, 345, 385, 492, 517, 519, Current SeekOrigin, valor, 273 listagem de código de exemplo,
525 CurrentChanged, evento, 520 258
CreateChildControls( ), método, CurrentRows, valor personalizados, 260, 261
862, 869 (DataViewRowState), 508 tabela de, 257, 258
CreateCommand( ), método, especificadores de formato
450 curvas, desenhando
splines cardinais, 148, 149 personalizados, 260, 261
Created, evento, 281
Índice 891

DataAdapters, classe. Ver templates, 662 definições de DataTable, 571


DataAdapters, 445, 446, 475 DataMember, propriedade editando linhas em, 576
BDPDataAdapter, 475 (controle Repeater), 658 excluindo linhas em, 577
classe BdpDataAdapter, 540, DataMember, propriedade listagem de código de
583, 584 (ListControl), 648 exemplo, 307, 568, 570
classe SqlDataAdapter, 539, localizando linhas em, 577
DataReader, classe. Ver
541, 543, 544 propriedades/métodos de
DataReaders, 446
exemplo do bloco principal, DataTable, 573, 574
539 DataReaders, 446, 467 vantagens/desvantagens, 567
método classe BdpDataReader, 582, 583 DataSets não-tipificados, 483
GetDataAdapterDeleteCo classe SQLDataReader, 468, 469 DataTableCollection, 484, 485
mmand( ), 543 dados de BLOB, recuperando, DataTables, 484, 485
método 470, 472 DataViewManager, 485
GetDataAdapterInsertCom informações de estrutura, definição, 483
mand( ), 541 recuperando, 472, 473 indexando tabelas, 487
método interface IDataReader, 468 preenchendo, 478, 479
GetDataAdapterUpdateCo múltiplos resultsets, removendo tabelas, 486
mmand( ), 541 consultando, 469, 470 retornando com Web Services,
stored procedure, 544 resultsets únicos, consultando, 692, 693
composição de, 475, 476 468, 469 strongly typed DataSets
criando, 476, 478 DataRelation, classe, 447 definições de DataRow, 576
interface IDBDataAdapter, 475 DataRelationCollection, 485 DataSnap, 446
processamento de transação, DataRelations, 492, 494 DataSource, propriedade
562
DataRows, 447, 495 (controle Repeater), 658
resultados de consulta,
adicionando, 495 DataSource, propriedade
mapeando, 480, 482, 483
adicionando a DataSets (ListControl), 648
resultados de consulta,
fortemente tipificados, 576 DataTable, classe. Ver
recuperando, 478, 479, 480
cancelando alterações em, 496 DataTables, 447
SQLDataAdapter, 475
editando em DataSets
DataBind( ), método, 644 DataTableCollection, 447, 484,
fortemente tipificados, 576
485
DataColumnCollection, 487 excluindo, 496
excluindo de DataSets DataTables, 447, 484, 485, 487
DataColumns, 447
fortemente tipificados, 577 adicionando a DataSets, 486
definição, 487, 488
localizando em DataSets chave primária
propriedades, 488
fortemente tipificados, 577 definindo, 489
removendo, 488
modificando, 495 colunas
verificando existência de, 488
valores RowState, 496, 497, 499 definição, 487, 488
DataGrid, controle, 667, 668 propriedades, 488
adicionando itens, 677 DataSet, propriedade
removendo, 488
classificando, 678 (DataViewManager), 502
verificando existência de, 488
editando, 671, 672, 673, 675, DataSet, classe. Ver DataSets, Constraint, 447
676, 677 447 DataColumns, 447
comando Update, 676, 677 DataSets, 447 DataRelation, 447
declaração de adicionando tabelas, 486 DataRelations, 492, 494
TemplateColumn, 672, clonando, 486 DataRows, 447, 495
673
composição de, 484 adicionando, 495
handler de evento
consumindo a partir de Web cancelando alterações em, 496
Page_Load( ), 673, 674
Services, 702, 703, 704 tipos enumerados
método GetData( ), 673, 674
tipo EditCommandColumn, copiando, 486 DataRowVersion, 496, 497
671 criando, 485, 486 excluindo, 496
paginação, 72, 239, 240, 241, DataRelationCollection, 485 modificando, 495
343, 369, 370, 384, 668, 669, DataSets fortemente valores RowState, 496, 499
670, 671 tipificados, 484 DataTableCollection, 447
tipos de coluna, 667, 668 adicionando linhas, 576 EmployeeDataTable, 571
arquivos .pas, 307, 568, 570, indexando, 487
DataList, controle, 662, 663,
574, 576 métodos, 573, 574
664, 665, 666, 667
arquivos .xsd, 568 pesquisando com método
declaração, 556, 663, 664
criando, 567, 568 Find( ), 497, 498
eventos, 662, 663,664
dados hierárquicos, 577 pesquisando com método Se-
propriedades, 662, 663
definições de DataRow, 360, lect( ), 498, 499
renderização de imagem, 666
574, 575 preenchendo, 479
892 Índice

propriedades, 573, 574 dddd, especificador de formato, DeleteCommand, evento, 663


removendo a partir de 260 DeleteCommand, objeto, 476
DataSets, 486 DDL (Data Definition
Deleted, evento, 281
restrições, 489 Language), comandos, 460,
classe ForeignKeyConstraint, 462 Deleted, valor
490, 491 Debug, atributo (páginas (DataViewRowState), 508
classe UniqueConstraint, 489 ASP.NET), 598 Deleted RowState, 496
recuperando de origens de Debug, layout de área de DeleteProduct, stored procedure,
dados, 491, 492 trabalho, 27 551
Dec( ), função, 58, 59
DataTextField, propriedade DeleteRule, propriedade
DecimalToCurrencyString( ),
(ListControl), 648 (ForeignKeyConstraint), 491
método, 525
DataTextFormatString, Delphi Guru, site da Web, 24
declaração/execução, blocos, 39
propriedade (ListControl), Delphi IDE. Ver IDE (Integrated
declarando
648 Development Environment),
arrays, 67
DataType, propriedade 24
(DataColumns), 488 arrays dinâmicos, 68
classes, 78 <deny>, seção (arquivo
DataValueField, propriedade web.config), 782
(ListControl), 648 conjuntos, 71
dependências
DataView, classe, 447 constante, 53, 54
dependências de arquivo de
DataViewManager, 485 interfaces, 110
cache, 820, 821, 823
DataViewManager, propriedade métodos, 97, 98
criando, 820, 821
(classe DataView), 501 namespaces, 43, 45
método GetData( ), 821, 823
DataViewManager, classe, 502 objetos, 95
DataViewRowState, propriedade ponteiros, 74 Dependencies, propriedade
(classe DataView), 508 sprites, 175 (classe Cache), 818
DataViews, 500, 501 threads, 342, 343 Dequeue( ), método, 223, 224
classe DataView, 501 variáveis, 51, 53 derivando
classe DataViewManager, 502 decremento, procedures, 58, 59 parâmetros, 466, 467
classificando, 511 DeriveParameters( ), método, 467
Default DataRowVersion, 497
comparados com DataSources,
default, diretiva, 304 desativando
501
Default, layout de área de ViewState, 830
filtrando, 504, 506
funções, 508 trabalho, 27 Description, propriedade
funções agregadas, 510, 511 default.htm, arquivo, 25 (controles Web), 851
operador LIKE, 507, 508 DefaultValue, propriedade desempenho
operadores de comparação, (controles Web), 851 exportações de DLL Win32 no
506, 507 código .NET, 430, 431, 432,
DefaultValue, propriedade
por valores de coluna, 506 433, 434, 435
(DataColumns), 488
propriedade finalização, 216
DefineDynamicAssembly( ),
DataViewRowState, 508 desenhando, 139
método, 390
propriedade RowFilter, 504 classe Graphics, 140
propriedade RowStateFilter, DefineDynamicModule( ), classe GraphicsPath, 156, 157
509, 510 método, 390 curvas
joins, 512, 513 DefineMethod( ), método, splines cardinais, 148, 149
métodos, 502 390 splines de Bezier, 149, 151,
pesquisando, 512 delegates, 306 152
propriedades, 502 definição, 344, 352 elipses, 153, 154
vinculação de dados, 503, 504 executando assincronamente, imagens, 165, 166
DataViewSettings, propriedade 352, 354 linhas
(DataViewManager), 502 ThreadStart, 345, 346 classe GraphicsPath, 147, 148
WaitCallback, 349 classe Pen, 142, 143
DateToStr, método, 198
Delete( ), método, 265, 270, 496, classes Brush, 142, 143, 144,
Datye, Vandana, 869 145
502
DCOM (Distributed Component listagem de código de
Object Model), 712 DELETE, consultas exemplo, 143, 144, 145
classe SqlCommand, 537, 538, método ClearCanvas( ), 142
dcpil, extensão de arquivo, 128
539 pontas de linha, 145
dcuil, extensão de arquivo, 128 método pontas de seta, 147
dd, especificador de GetDataAdapterDeleteCom unindo linhas, 147, 148
formato,260 mand( ), 543 namespaces GDI+, 139, 140
ddd, especificador de formato, stored procedure polígonos, 154
260 DeleteProduct, 551 regiões
Índice 893

listagem de código de diretivas de compilador no código .NET


exemplo, 157, 159 @OutputCache, 802, 803 códigos de erro HResult, 428,
recortando, 160, 161 diretório-raiz da aplicação 429, 430
retângulos, 21, 152, 153 (XSP), 197 questões de desempenho, 430,
sistema de coordenadas Win- 431, 432, 433, 434, 435
diretório-raiz Web (XSP), 197
dows, 140, 142 exportações de DLL Win32 no
tortas, 154, 155 diretórios, 265 código .NET, 419, 420
\\bins, 790 códigos de erro Win32, 426,
desenvolvimento, 11
\\images, 790 427, 428
arquitetura multicamada, 717,
classe Directory, 263 empacotamento (marshaling),
718
classe DirectoryInfo, 263, 265 423, 424, 425, 426
vantagens de .NET, 2, 3
copiando, 266, 267, 268 sintaxe de atributo
Deserialize( ), método, 286 criando, 265, 266 personalizado, 421, 422,
Designer, 25, 26, 28 diretórios virtuais, 787, 788, 423
desreferenciando 789, 790, 791 sintaxe Delphi tradicional,
ponteiros, 75 excluindo, 265, 266 420, 421
desserialização, 282, 283, 285, informações de diretório, tipos de parâmetro, 423, 424,
286 visualizando, 268, 269 425, 426
Destroy( ), método, 215, 312, monitorando, 279, 280, 281 tratamento de erro, 426
313 movendo, 266, 267, 268 mscoree.dll, 410
destruindo DLLs (Dynamic Link Libraries)
Display, propriedade (classe
objetos, 95, 96 Ver também assemblies, 121
BaseValidator), 619
destrutores, sobrescrevendo, Borland.Delphi.dll, 121
DisplayMode, propriedade
312, 313 mscorlib.dll, 121
(classe ValidationSummary),
Detached RowState, 496 625 documentação
Device FileAttribute, 268 Dispose( ), método, 213, 214, .NET Framework, 4
dicionários 312 CLS (Common Language Specifi-
dicionários fortemente cation), 17
dispose, padrão, 213
tipificados, 238 exemplo de IDisposable, 213, dois-pontos (:), 261
aplicação de exemplo, 241, 214 domínios
242 implementando AppDomains, 718, 719
descendentes de automaticamente, 214, 215, DoSomething( ), método, 436
DictionaryBase, 238, 241 216
DoSomethingElse( ), método, 436
DictionaryBase, classe, 238, distinção entre maiúsculas e
DoTextChanged( ), método, 860
241 minúsculas, 52
DotNETClassForCOM, classe, 413
Digest, autenticação (Windows), distribuição
772 DoubleBuffer ControlStyle, tipo
aplicações ASP.NET, 787
diretórios virtuais, 787, 788, enumerado, 336
directives
@OutputCache 789, 790, 791 dpk, extensão de arquivo, 128
sample .aspx file, 812 distribuição XCOPY, 791, DPR, arquivos
792 arquivos DPR de cliente, 85, 176,
Directory, classe, 263
arquitetura multicamada, 717, 267, 302, 343, 366, 367, 381,
Directory FileAttribute, 268
718 481, 540, 547, 548, 549, 569,
DirectoryInfo, classe, 263, 265 assemblies, 123, 124, 125 645, 702, 746, 750, 763, 764
DirectoryName, valor (tipo vantagens de .NET, 3 arquivos DPR de servidor, 748,
enumerado NotifyFilters), 281 Distributed Component Object 749, 761
diretivas Model (DCOM), 712 DrawAngle( ), método, 145
$define, 55 div, operador, 57 DrawBezier( ), método, 151
$IFDEF, 49
dividindo DrawEllipse( ), método, 154
$J, 55
strings, 249, 250 DrawImage( ), método, 165, 180
$UNSAFECODE ON, 73, 134
divisão (/), operador, 506 DrawLine( ), método, 144, 173
$WRITEABLECONST, 55
%DelphiDotNetAssemblyComp divisão de inteiro, operador, 57 DrawPath( ), método, 157
iler, 133 divisão de ponto flutuante (/), DrawPolygon( ), método, 154
Ver também instruções, 127 operador, 57
DrawSaucerTrans( ), método, 181
@OutputCache, 812, 813 divisão, operadores, 57
DropDownList, controle, 650,
arquivo .aspx de exemplo, dll, extensão de arquivo, 128 651, 652
812
DllImportAttribute, 421 vinculando a arrays, 650, 651
atributos, 812, 813
DLLs vinculando a tabelas de banco
@Page, 598
exportações de DLL do HResult de dados, 651, 652
894 Índice

Duration, atributo (@ 671 EmployeeRow, classe, 360, 574,


OutputCache), 812 linhas 575, 576
Duration, configuração DataSets fortemente
empty, variantes, 66
(@OutputCache), 803 tipificados, 576
EmptyParam, variável, 397
EditCommand, evento, 663
Enable, atributo (tag
EditCommandColumn, tipo de
E <httpRuntime>), 797
coluna, 668
enable, atributo (tag
e comercial (&), 265 EditItemIndex, propriedade
<processModel>), 801
E/S (entrada/saída) (controle DataList), 662
EnableClientScript, propriedade
arquivos EditItemStyle (controle
(classe BaseValidator), 619
classe FileSystemInfo, 268, DataGrid), 668
269 EnableClientScript, propriedade
EditItemStyle, propriedade
copiando, 270 (classe ValidationSummary),
(controle DataList), 662
criando, 270 625
excluindo, 270 EditItemTemplate, template,
enabled, atributo (tag <trace>),
informações de arquivo, 662
808
visualizando, 270, 271 editores
Enabled, propriedade (classe
modos de arquivo, 272 Code Editor, 30, 31, 33
BaseValidator), 619
monitorando, 279, 281 Command Text Editor, 587
movendo, 270 EnableKernelModeCache,
Connections Editor, 587
valores de acesso de arquivo, atributo (tag <httpRuntime>),
gedit, 190
273 797
Parameter Collection Editor,
valores do tipo enumerado 587 EnableNotifyMessage, valor do
FileAttributes, 267, 268 tipo enumerado ControlStyles,
eliminando a ambigüidade de
desserialização, 282, 283, 285, 336
tipos, 42
286 enableSessionState, atributo
diretórios, 265 elipses, desenhando, 153, 154
(<pagestag), 798
classe DirectoryInfo, 265 Emit( ), método, 390
enableViewState, atributo
copiando, 266, 267, 268 Emit, namespace, 386, 387
(<pagestag), 798
criando, 265 EmitDemo, aplicação, 388, 390
enableViewStateMac, atributo
excluindo, 265 emitindo (tag <pages>), 798
informações de diretório, MSIL por reflection, 386, 387
visualizando, 268, 269 encapsulamento, 93
aplicação de exemplo, 388,
monitorando, 279, 281 Encrypted FileAttribute, 268
390
movendo, 266, 267, 268 classe AssemblyBuilder, 387 End SeekOrigin, valor, 273
namespace System.IO, 263, 264 classe End( ), método, 603
serialização, 281, 282 CustomAttributeBuilder, end, instrução, 82
exemplo, 283, 285, 286 387 end, palavra-chave, 39
formatadores, 283 classe EnumBuilder, 387
gráficos de objeto, 283 EndCurrentEdit( ), método, 520,
classe EventBuilder, 387
interface ISerializable, 282, 521
classe FieldBuilder, 387
283 EndEdit( ), método, 495, 577
classe ILGenerator, 388
atributo , 282 classe LocalBuilder, 387 endereço de operador (@), 73
streams, 271 classe MethodBuilder, 387 EndGetEmployees( ), método,
acesso assíncrono ao stream, classe ModuleBuilder, 387 703, 705
276, 278, 279 classe ParameterBuilder, 387 EndInvoke( ), função, 554
BinaryReaders, 275, 276 classe PropertyBuilder, 387 EndInvoke( ), método, 364, 365
BinaryWriters, 275, 276 classe TypeBuilder, 387
FileStreams, 271, 272, 273, Enqueue( ), método, 223, 224
namespace System.Reflec-
274, 275, 276 EnsureCapacity( ), método,
tion.Emit, 386, 387
editando 251
processo de emissão, 388
controle DataGrid, 671, 672, EnsureChildControls( ), método,
EmitWriteLine( ), método, 390
673, 675, 676, 677 862
empacotamento (marshaling),
comando Update, 676, 677 Enter( ), método, 357
409, 415, 416, 417, 418, 423,
declaração de entrada/saída. Ver E/S, 263
424, 425, 426
TemplateColumn, 672,
exportações não-gerenciadas, EnumBuilder, classe, 387
673
handler de evento 437, 438, 439, 440, 441 tipos enumerados
Page_Load( ), 673 EmployeeDataTable, 571, 573, DataRowEnumeration, 497
método GetData( ), 673, 674 574 LineCap, 147
tipo EditCommandColumn, EmployeeRow, 360, 575, 576 Equals( ), método, 251
Índice 895

ERROR_FILE_NOT_FOUND, 359 EVariantTypeCast, 65


mensagem de erro, 406 AuthenticateRequest, 784 FileNotFoundException, 406
ErrorMessage, propriedade CancelCommand, 663 fluxo de execução, 118
(classe BaseValidator), 619 Changed, 281 regerando, 120
comunicação baseada em SOAP (Simple Object Access Pro-
erros
evento, 599, 600 tocol), 742, 743
Ver também exceções, 406
conexões, 453, 454, 455 tratamento de exceções de E/S
códigos de erro HResult, 428,
Created, 281 de arquivo, 114, 115
429, 430
criando, 307 Exception, classe, 117, 118
códigos de erro Win32, 426,
CurrentChanged, 520 Exclude( ), função, 72
427, 428
definição, 305
COR_E_FILENOTFOUND, 406 excluindo
DeleteCommand, 663
ERROR_FILE_NOT_FOUND, arquivos, 270
Deleted, 281
406 caracteres de string, 249
EditCommand, 663
escalabilidade cookies, 607
eventos COM, 403, 404, 405
arquitetura multicamada, 716, DataColumns, 488
eventos multicast, 103, 105
717 DataRows, 496
eventos singleton, 102
escopo, 89, 90 DataTables, 486
eventos postback, 608, 609
diretórios, 265, 266
espaço de endereço virtual, 338 handlers de evento, 305
linhas
especificadores de formato, 256 InfoMessage, 453, 454
DataSets fortemente
data/hora, 257, 258, 259, 260, ItemChanged, 520
tipificados, 577
261 ItemCommand, 658, 663
ItemCreated, 658, 663 executando
listagem de código de
ItemDataBound, 658, 663 assemblies sob Mono, 191
exemplo, 258
Load aplicação console MonoMenu
personalizados, 260, 261
.NET, 191
tabela de, 257, 258 processando, 613, 614
erros de compilador, 194
definição, 254 MetaDataChanged, 520
unit MonoFuncs, 193
especificadores de formato de métodos de despacho de
assemblies sob o Mono
tipo enumerado, 261, 262 evento, 305, 306
console da aplicação
especificadores de formato ordem de processamento de
MonoMenu .NET, 192
numérico, 254, 255, 256, evento de WebForm, 616
MonoFuncs unit, 193
257 PositionChanged, 520
comandos, 456
especificadores ProcessExit, 42
classe CommandBuilder, 466,
personalizados de formato propriedades de evento, 305,
467
numérico, 256, 257 306, 309, 310, 311
comandos DDL (Data Defini-
listagem de código de Renamed, 281
tion Language), 460, 462
exemplo, 255 RowUpdating, 529
comandos não-consulta, 458,
tabela de, 254 Session_End, 834, 835
459
especificadores de visibilidade, Session_Start, 834, 835
interface IDbCommand, 456,
105, 106, 107 SortCommand, 678
457
StateChange, 453, 455
espelho, efeito, 167, 168 método ExecuteScalar( ), 459,
UpdateCommand, 663
esqueleto de arquivos de 460
evolução, 184 parâmetros, derivando, 466,
programa, 39
exceções, 114, 368 467
estados
(Structured Exception Han- parâmetros, especificando
estados de apartment, 348, 349
dling), 114 com IDbParameter, 462,
estados de thread, 338, 348
Ver também erros, 406 464
valores de RowState, 496, 499
ArithmeticException, 117 stored procedure, 464, 465,
estruturas bloco try201except, 115, 116 466
informações de estrutura, classe Exception, 117, 118 valores únicos, recuperando,
recuperando, 472, 473 classe 459, 460
Eval( ), método, 661 SynchronizationLockExcepti delegates, 352
EVariantTypeCast, exceção, 65 on, 371 ExecuteNonQuery( ), método,
EventBuilder, classe, 387 classe ThreadAbortException, 457, 458, 459, 562
evento, handlers. Ver handlers 368
ExecuteReader( ), método, 457,
de evento, 305 classe
468, 469, 472, 473, 582, 583
ThreadInterruptedException,
eventos, 102, 304 ExecuteScalar( ), método, 457,
370
aplicações multithread, 359 459, 460
classe ThreadStateException,
classe AutoResetEvent, 360 ExecuteXMLReader( ), método,
371
classe ManualResetEvent,
896 Índice

457 namespaces, 18, 43, 45 FileNotFoundException, 406


ExecutionTimeout, atributo (tag cláusula de namespaces, 46 FileStreams, 264, 271
<httpRuntime>), 797 convertendo, 46 criando, 271, 272, 274, 275
Exists, propriedade (classe declarando, 43, 45 dados binários, 275, 276
FileSystemInfo), 268 namespaces padrão de gravando em, 271, 272, 275
projeto, 43 lendo, 274, 275
Exists( ), método, 265
System, 18 valores de FileAccess, 273
Exit( ), método, 357 System.CodeDOM, 18 valores de FileMode, 272
expandindo blocos de código, System.Collections, 18 valores de SeekOrigin, 273
30 System.ComponentModel,
FileSystemInfo, classe
ExplorerViewer, controle, 314 18
propriedades, 264, 268, 269
listagem de código, 315 System.Configuration, 18
método ActivateFile( ), 323 System.Data, 18 FileSystemWatcher, classe, 279,
método ExtractIcon( ), 323 System.DirectoryServices, 18 280, 281
método FillListView( ), 323 System.Drawing, 18, 19, 21 eventos, 281
método FillTreeView( ), 323 System.EnterpriseServices, 19 listagem de código de exemplo,
método GetDirectories( ), 323 System.IO, 19 279
método RefreshNode( ), 323 System.Management, 19 valores de NotifyFilters, 281
método SHGetFileInfo( ), 323 System.Messaging, 19 Fill( ), método, 479, 486, 491
exportações não-gerenciadas, System.Net, 19 FillEllipse( ), método, 154
435, 436 System.Reflection, 19 FillListView( ), método, 323
declarações de importação, 440 System.Resources, 19 FillPath( ), método, 157
empacotamento (marshaling), System.Runtime.Compiler-
FillPolygon( ), método, 154
437, 438, 439, 440, 441 Services, 19
Sysem.Runtime.InteropSer- FillSchema( ), método, 491, 492
sintaxe Delphi tradicional, 436,
437 vices, 19 FillTreeView( ), método, 323
tipos de parâmetro, 437, 438, System.Runtime.Remoting, filtrando
439, 440, 441 19 DataViews, 504, 506
exports, instrução, 134 System.Runtime.Serializa- funções, 508
tion, 19 funções agregadas, 510, 511
expressões
System.Security, 19 operador LIKE, 507, 508
expressões de vinculação,
System.ServiceProcess, 19 operadores de comparação,
avaliando, 660
System.Text, 19 506, 507
tipos Variant, 64, 65, 66
System.Threading, 19 por valores de coluna, 506
Extensible Markup Language. System.Timers, 19 propriedade
Ver XML. System.Web, 19 DataViewRowState, 508
Extension, propriedade (classe System.Windows.Forms, 19, propriedade RowFilter, 504
FileSystemInfo), 268 21 propriedade RowStateFilter,
ExtractIcon( ), método, 323 System.XML, 19 509, 510
FclearSkyImage, objeto, 177 RowState, 499
fechando saída (ASP.NET), 603, 604
F finalização, 211, 212
conexões, 452, 453
f, especificador de formato, 257 método Finalize( ), 212, 213
ferramentas
F, especificador de formato, 257 FxCop, 17 questões de desempenho, 216
factory, padrão Reflector, 374 finalização, seção (units), 41
arquivo DPR de cliente, 749 f-fffff, especificador de formato, finalization, instrução, 91
arquivo DPR de servidor, 748, 260 Finalize( ), método, 95, 212, 213,
749 216
FieldBuilder, classe, 387
classe TSimpleFactory, 744,
File, classe, 264 Find( ), método, 497, 498, 502,
747, 748
File, comandos de menu 512
classe TSimpleServer, 746, 747
personalizando, 28 FindByEmployeeID( ), método,
definição, 743, 744
FileAccess, valores, 273 573
interface ISimpleFactory, 744,
745 FileAttributes, valores do tipo FindRows( ), método, 502, 512
interface ISimpleServer, 745 enumerado, 267, 268 FindTableNameByID( ), método,
fazer e atribuir, operadores, 59 FileInfo, classe, 264 577
FCL (Framework Class Library), FileMode, valores, 272 FirstReturnedRecord, valor
17 (propriedade
FileName, valor (tipo
Ver também namespaces, UpdatedRowSource), 556
enumerado NotifyFilters), 281
217 FixedHeight ControlStyle, valor
Índice 897

do tipo enumerado, 336 300, 571, 572, 625, 627 376


FixedSize( ), método, 227 FormatMessage( ), método, 426 funções. Ver também métodos.
Flush( ), método, 274 formato numérico, AddEmUp( ), 88
fluxo de execução, 118 especificadores, 254, 255, 256, AddInts( ), 51
257 BeginInvoke( ), 554
Folder Properties, caixa de
especificadores personalizados Copy( ), 69
diálogo, 781
de formato numérico, 256, Dec( ), 58, 59
Font, propriedade (classe definição, 50, 85
257
WebControl), 626 em expressões de filtro, 508
listagem de código de exemplo,
Foo( ), função, 87 255 EndInvoke( ), 554
FooterStyle (controle DataGrid), tabela de, 254 Exclude( ), 72
668 FormsAuthentication, classe, exemplo, 86
FooterStyle, propriedade 775 Foo( ), 87
(controle DataList), 662 funções agregadas
FormsIdentity, classe, 774
FooterTemplate, template, 658 em expressões de filtro, 510,
formulário de solicitação de 511
for, loop, 83 download, 611 GetDAUpdateCommand( ), 554
ForeColor, propriedade (classe controles de lista,
HasDefVal( ), 50
WebControl), 626 pré-preenchendo, 616, 617,
High( ), 67
ForeignKeyConstraint, classe, 618
inc( ), 58, 59
490, 491 handler de evento OnInit,
Include( ), 72
617, 618
form, tag, 593 IsPositive( ), 86
método
formas, desenhando PopulateDdlFromFile( ), Low( ), 67
classe GraphicsPath, 156, 157 617 parâmetros com valor padrão,
curvas criando, 613 50
splines cardinais, 148, 149 eventos Load, 613, 614 parênteses ( ), 50
splines de Bezier, 149, 151, layout de página, 612 passando parâmetros para, 87
152 ordem de processamento de parâmetros com valor, 87
elipses, 153, 154 evento, 616 parâmetros constantes, 88
linhas salvando informações a partir parâmetros de array aberto,
classe GraphicsPath, 147, de, 614, 615, 616 88, 89
148 parâmetros out, 87, 88
formulário principal (exemplo
classe Pen, 142, 143 parâmetros por referência, 87
de animação), 176
classes Brush, 142, 143, 144, parâmetros por valor, 87
<forms>, seção (arquivo parâmetros variáveis, 87
145
web.config), 773, 774, 796 Pred( ), 59
listagem de código de
exemplo, 143, 144, 145 formulários. Ver WinForms, ReadLine( ), 554
método ClearCanvas( ), 142 controles, 287 Round( ), 80
método DrawLine( ), 144 Framework Class Library. Ver SetLength( ), 68, 69
pontas de linha, 145 FCL, 17 SizeOf( ), 61
pontas de seta, 147 framework. Ver .NET Frame- sobrecarregando, 50
unindo linhas, 147, 148 work, 17 SomeProc( ), 90
polígonos, 154 Free( ), método, 211, 215 SubmitDeletedRow( ), 538
regiões Succ( ), 59
friend, classes, 107
listagem de código de Trunc( ), 80
friend, instrução, 107
exemplo, 157, 159 funções agregadas
recortando, 160, 161 From, propriedade (classe em expressões de filtro, 510, 511
retângulos, 152, 153 MailMessage), 637
funções. Ver métodos, 424
tortas, 154, 155 Fsaucer, objeto, 177
FxCop, 17
Format( ), método, 252, 253 FSection, 212
FxCop, utilitário, 294
formatadores, 283 FskyGraphics, objeto, 177
formatando FskyImage, objeto, 177
strings, 252, 253, 254 Fstep, objeto, 177 G
vinculação de dados, 523, 525 FullName, propriedade (classe g, especificador de formato, 257,
WebForms, 625 Assembly), 374 260
classe Style, 628, 629 FullName, propriedade G, especificador de formato, 258
CSS (Cascading Style Sheets), (classeFileSystemInfo), 268 GAC (Global Assembly Cache),
627, 628
FullyQualifiedName, 123
propriedades fortemente
propriedade (classe Module), garbage collection
tipificadas WebControl,
898 Índice

aplicações multithread, 371 153 374


geracional, 207, 209, 210 sistema de coordenadas Win- GetChanges( ), método, 486
Garbage Collector. Ver GC. dows, 140, 142 GetChars( ), método, 472
GC (Garbage Collector), 206, tortas, desenhando, 154, 155
GetChildRow( ), método, 494
207 gedit, editor, 190 GetChildTableNameRows( )
classe System.GC, 210, 211 GenerateThumbs( ), método, método (ChildTableName), 577
garbage collection geracional, 640 GetCommandLine( ), método,
207, 209, 210 gerenciamento de estado 425
invocando, 210, 211 (ASP.NET), 825 GetConstructor( ), método, 383
raízes, 207 cookies GetCountry( ), método, 816
GDI+, biblioteca, 139 cookies persistentes, 826, GetCustomAttributes( ), método,
Brush classe, 142 374, 376
827
GetData( ), método, 671, 673,
classe Brush , 142 criando, 826
674, 683, 820, 821, 823
classe Graphics, 140 definição, 825
GetDataAdapterDeleteCommand
classe GraphicsPath, 156, 157 desvantagens, 827, 828 ( ), método, 543, 544, 550, 551
classe Pen, 142, 143 recuperando valores de GetDataAdapterInsertCommand(
classes Brush, 142, 143, 144, cookie, 826 ), método, 541, 546
145 estado de aplicação, 835, 836 GetDataAdapterUpdateComman
curvas, desenhando acesso a dados de d( ), método, 541, 542, 548
splines cardinais, 148, 149 sincronização, 837 GetDAUpdateCommand( ),
splines de Bezier, 149, 151, classe Cache, 837, 838 função, 554
152 classe HttpApplicationState, GetDeclareString( ), método, 378
elipses, desenhando, 153, 154 837, 838 GetDirectories( ), método, 323
exemplo de animação, 174 objeto Application, 837 GetDotNETVersion( ), método,
declaração de sprite, 175 estado de sessão, 830, 831 438
declaração do formulário eventos de sessão, 834, 835 GetEmployees( ), método, 694,
principal, 176 objeto Session, 831 703
implementação de demo de GetEnumerator( ), método, 219,
Session State Server, 832, 833
animação, 177, 182 227
sessões sem cookies, 832
imagens, 161 GetEvent( ), método, 383
SQL Server, 833, 834
coordenadas globais, 173, GetField( ), método, 383, 647
tempo-limite padrão da GetFields( ), método, 376
174 sessão, 831 GetFirstName( ), método, 647
desenhando, 165, 166 ViewState, 828, 829, 830 GetGeneration( ), método, 211
efeito espelho, 167, 168 adicionando valores ao state GetHistory( ), método, 804
interpolação, 166, 167 bag, 830 GetInput, método, 192, 193
mapas de bits, 162, 163 desativando na página, 830 GetInsertCompanyCmd( ),
metarquivos, 163 desativando para controles, método, 562
miniaturas, 172 830 GetInsertContactCmd( ),
resolução, 163, 164, 165 método, 562
gerenciamento de tempo de
rotacionando, 167, 168 GetLastError( ), método, 426,
vida, 751, 752, 753, 754
transformações, 168, 171, 427, 430
interface ILease, 752
172 GetLastName( ), método, 647
interface ISponsor, 752
linhas, desenhando GetMembers( ), método, 378
método
Brush classe, 142 GetMethod( ), método, 382, 383,
InitializeLifetimeService( ),
classe GraphicsPath, 147, 390
752, 753 GetMethods( ), método, 376
148
método Renewal( ), 753 GetModules( ), método, 374
classe Pen, 142, 143
projeto SponsorAndLease, 753, GetMyName( ), método, 643
classes Brush, 143
754 GetObject( ), método, 759
listagem de código de
exemplo, 143, 144, 145 Get( ), método, 817 GetObjectData( ), método, 231
GET, método, 593 GetParentTableNameRow( ),
pontas de linha, 145
método (ParentTableName),
pontas de seta, 147 get_DefaultSize( ), método, 330
577
unindo linhas, 147, 148 get_TagKey( ), método, 856, 861 GetPlanePosition( ), método, 303
namespaces, 139, 140 GetAccount( ), método, 52, 55, GetPlanetName( ), método, 303
polígonos, desenhando, 154 86, 103, 108, 109, 111, 113, GetPrimaryInteropAssembly( ),
regiões 119, 130, 132, 133, 292, 293, método, 407
listagem de código de 294, 400, 404, 479, 486, 537, GetProperty( ), método, 383
exemplo, 157, 159 556, 645, 719, 722, 723, 742 GetRange( ), método, 228
recortando, 160, 161 GetReferencedAssemblies( ),
GetBytes( ), método, 472
retângulos, desenhando, 152, método, 374
GetCallingAssembly( ), método,
Índice 899

GetRemoteObjectRef( ), método, Step2_Click( ), 180 navegador, 605


759, 760 Step2_DrawSaucerTrans( ), 180 saída, filtrando, 603, 604
gets, operador, 55 Step3_DrawSaucerPos( ), 182 gravando texto, 602
GetSchemaTable( ), método, 472 Timer1_Tick( ), 177 respostas, 593, 594
GetStateInfo( ), método, 237 Timer1_Timer( ), 180 RFC 2616, 592
GetThumbNailImage( ), solicitações, 592, 593
HasDefVal( ), função, 50
método, 639
GetTitle( ), método, 647 HashPasswordForStoringInConf HttpApplicationState, classe, 836,
GetTotalMemory( ), método, igFile( ), método, 776 837, 838
211 HashTable, coleção, 230, 231 HTTPCookie, classe, 607
GetType( ), método, 395 construtores, 231 HTTPRequest, classe, 605
GetTypeFromProgID, método, exemplo, 225, 228, 232, 233 HTTPResponse, classe, 601
394 métodos, 231 redirecionamento de navegador,
GetTypes( ), método, 374, 376
HatchBrush, classe, 144 605
GetUserName( ), método, 423,
Headers, propriedade (classe saída, filtrando, 603, 604
427, 432
GetUserRoles( ), método, 785 MailMessage), 637 texto, gravando em clientes, 602
GetVaryByCustomString( ), HeaderStyle (controle HttpSessionState, classe, 830
método, 816 DataGrid), 668 HyperLinkColumn, tipo de
Global Assembly Cache (GAC), HeaderStyle, propriedade coluna, 668
123 (controle DataList), 663 Hypertext Transfer Protocol. Ver
GlobalAssemblyCache,
HeaderTemplate, template, 657 HTTP, 592
propriedade (classe
Assembly), 374 HeaderText, propriedade (classe
globalização, 248 ValidationSummary), 625 I
globally uniques identifiers Height, propriedade (classe
(GUIDs), 110 I/O (input/output)
WebControl), 626
GNOME Project, 184 arquivos
Hello.exe, aplicação, 190, 191
gráficos de objeto, 283 classe FileSystemInfo, 264
Graphics, classe, 140 HelloWorld( ), método, 688, classe File, 264
GraphicsPath, classe, 147, 148, 701, 708 classe FileInfo, 264
156, 157 helper, classes, 107, 108 classe Path, 264
gravando herança criando, 270
com BinaryWriters, 264, 275, páginas de ASP.NET, 601 excluindo, 270
276 hh, especificador de formato, monitorando, 280
com StreamWriters, 264 260 diretórios
com StringWriters, 264 criando, 266
HH, especificador de formato,
com TextWriters, 264 classe Directory, 263
260
em FileStreams, 271, 272, 275 classe DirectoryInfo, 263
Hidden FileAttribute, 268
GridLines, propriedade excluindo, 266
(controle DataList), 663 High( ), função, 67 monitorando, 280
GUIDs (globally uniques identi- Highest, prioriedade de thread, streams
fiers), 110 347 BinaryReaders, 264
HTML BinaryWriters, 264
tipo de bloco, 855, 856 BufferedStreams, 264
H HTML (Hypertext Markup Lan- FileStreams, 264, 272
h, especificador de formato, 260 guage) MemoryStreams, 264
H, especificador de formato, tag form, 593 NetworkStreams, 264
260 Stream, 264
HTML, controles, 611
handlers de evento, 305 StreamReaders, 264
HtmlInputFile, controle, 635,
Animate_Click( ), 180 StreamWriters, 264
636
bdpCn_StateChange( ), 581 StringReaders, 264
<httpRuntime>, seção (ASP.NET StringWriters, 264
btnLogin_Click( ), 775
web.config), 797 TextReaders, 264
btnSubmit_Click( ), 199
Button1_Click, 196, 595 HTTP (Hypertext Transfer Pro- TextWriters, 264
ImageButton1_Click( ), 641 tocol), 592 IAsyncResult, interface, 705
OnInit, 617, 618 classe HTTPCookie, 607
IBankManager, interface, 729
OnRowUpdated( ), 553 classe HTTPRequest, 605
Ibar, interface, 403
Page_Load( ), 200, 783 exemplo, 605
classe HTTPResponse, 601, 604, IBindingList, interface, 514
Step1_Click( ), 180
Step1_DrawSaucerTrans( ), 180 605 Icaza, Miguel de, 184
Step1_Erase( ), 180 redirecionamento de ICloneable, interface, 221
900 Índice

ICollection, interface, 218, 219 325, 326 resolução, 163, 164, 165
IComparer, interface, 218 $IFDEF, diretiva de compilador, rotacionando, 167, 168
49 transformações, 168, 171, 172
ícones
if, instrução, 81, 82 linhas
controles WinForms, 314
classe GraphicsPath, 147
IDataErrorInfo, interface, 514 IFF( ), método, 508
classe Pen, 142, 143
IDataReader, interface, 468 Ifoo, interface, 403 classes Brush, 142, 143, 144
IDbCommand, interface IHashCodeProvider, interface, listagem de código de
métodos, 457 218 exemplo, 143, 144, 145
ExecuteNonQuery( ), 458, IIdentity, interface, 774 método ClearCanvas( ), 142
459, 461 IIS método DrawLine( ), 144
ExecuteScalar( ), 459, 460 console de gerenciamento, pontas de linha, 145
propriedades, 456, 457 788 unindo linhas, 147, 148
IDbConnection, interface, 449, ILease, interface, 752 namespaces GDI+, 139, 140
450 polígonos, 154
IlGenerator, classe, 390
IDBDataAdapter, interface, 475 regiões
ILGenerator, classe, 388 listagem de código de
IDbParameter, interface, 462,
464 IList, interface, 218, 219, 514 exemplo, 157, 159
IDE (Integrated Development ImageButton1_Click( ), handler recortando, 160, 161
Environment), 24 de evento, 641 retângulos, 152, 153
atalhos pelo teclado, 30 imagens, 161 sistema de coordenadas Win-
Code Editor, 30, 31, 33 coordenadas globais, 173, 174 dows, 140, 142
Code Explorer, 36 desenhando, 165, 166 tortas, 154, 155
Data Explorer, 34 efeito espelho, 167, 168 implementando
Designer, 25, 26, 28 interpolação, 166, 167 interfaces, 111, 112
formulários, 28 mapas de bits, 162, 163 implementation, instrução, 91
VCL Forms, 28 carregando, 163 implementation, palavra-chave,
Web Forms, 28 classe Bitmap, 162 40, 41
Windows Forms, 28 criando, 163 imutabilidade de strings, 244,
lista To-Do, 36 metarquivos, 163 245, 246
Model View, 34 miniaturas, 172 in, operador, 72
Object Inspector, 29 renderizando de bancos de da-
IN, operador, 506
Object Repository, 34, 35 dos, 666
página Welcome, 25 resolução, 163, 164, 165 INamingContainer, interface,
paleta Tool, 29 rotacionando, 167, 168 862
Project Manager, 33 transformações, 168, 171, 172 inc( ), função, 58, 59
trechos de código, 29 WebForms, 638, 639 Include( ), função, 72
IDictionary, interface, 218, 220 imagens gráficas IncludeTrailingPathDelimiter( ),
classe Graphics, 140 método, 267
IDictionary,
classe GraphicsPath, 156, 157 incremento, procedures, 58, 59
interfaceEnumerator, 218
indexando
Idispatch, interface, 393, 412 curvas
DataTables, 487
splines cardinais, 148, 149
IDisposable, interface IndexOf( ), método, 219
splines de Bezier, 149, 151,
exemplo, 213, 214
152 índice, componentes, 253
implementando
elipses, 153, 154 InfoMessage, evento, 453, 454
automaticamente, 214, 215,
exemplo de animação, 174 informação de tipo em tempo de
216
declaração de sprite, 175 execução (runtime type infor-
IdleTimeOut, atributo (tag
declaração do formulário mation – RTTI), 372
<httpRuntime>), 797
principal, 176 inherited, instrução, 215
IdleTimeout, atributo (tag implementação de demo de
<processModel>), 801 Inherits, atributo (páginas
animação, 177, 182
ASP.NET), 598
IDotNETClassForCOM, inter- imagens, 161
face, 413, 414 coordenadas globais, 173, Inherits, atributo (tag <@ Control
174 %>), 844
IEditableObject, interface, 514
desenhando, 165, 166 inicialização
IEnumerable, interface, 218
efeito espelho, 167, 168 variáveis, 53
IEnumerator, interface, 218,
interpolação, 166, 167 inicialização, seção (units), 41
219, 220
mapas de bits, 162, 163 inicialization, palavra-chave, 41
IErrorInfo, interface, 406
metarquivos, 163 Init( ), método, 298, 312
IExtenderProvider, interface, miniaturas, 172
InitComp( ), método, 330, 335,
Índice 901

336 instrução break, 85 automaticamente, 214, 215,


initialization, instrução, 91 instrução continue, 85 216
InitializeComponent( ), loop for, 83 IDotNETClassForCOM, 413, 414
método, 312 loop repeat..until, 84 IEditableObject, 514
InitializeLifetimeService( ), loop while, 84 IEnumerable, 218
método, 752, 753 terminando, 85 IEnumerator, 218, 219, 220
InProc, modo (estado de sessão), message, 99 IErrorInfo, 406
798 msw.Free, 40 IExtenderProvider, 325, 326
inserindo nodefault, 304 IFoo, 403
strings, 249 overload, 50, 99 IHashCodeProvider, 218
Insert( ), método, 219, 246, 249, override, 99 IIdentity, 774
251, 818 package, 123, 127 ILease, 752
Insert, consultas private, 106 IList, 218, 219, 514
classe SQLCommand, 533, 534 protected, 106 implementação, 413, 415
GetDataAdapterInsert- public, 106 implementando, 111, 112, 414
Command( ), 541 published, 106 INamingContainer, 862
stored procedure InsertProduct, reintroduce, 100 IPostBackDataHandler, 857
547 requires, 127 IPostBackEventHandler, 856
resourcestring, 80 ISerializable, 282, 283
InsertCommand, objeto, 475
set of, 71 ISimpleFactory, 744, 745
InsertCompany( ), método, 562 strict private, 106 ISimpleServer, 745
InsertContact( ), método, 562 strict protected, 106 ISpeechVoice, 403
InsertProduct, stored procedure, switch, 82 ISpeechVoiceEvents, 403
547 type, 108 ISponsor, 723, 752
InsertRange( ), método, 228 Type, 66 ISpVoice, 399, 403
instalando unsafe, 73 IUnknown, 393
Mono, 187 uses, 92, 191 métodos, 112
packages, 135 var, 87 variáveis de interface, 113, 114
VAR, 53 Interlocked, classe, 355, 358, 359
InstallPersistSqlState.sql, 834
Integrated Development Envi- Interop Assemblies, 400, 401
InstallSqlState.sql, 834
ronment. Ver IDE, 24 componentes de, 402, 403
instanciando
interdependências (métodos), criando, 402
objeto Automation, 394
311 opção Copy Local, 401
objetos, 95, 722
interface, instrução, 91 personalizando, 408, 409
ativação por singleton, 722
interface, palavra-chave, 40 PIAs (Primary Interop Assem-
ativação single-call, 722
blies), 406, 407, 409
client-activated, 723 interfaces
criando, 406
instruções. Ver também declarando, 110
nomeando, 407
consultas. definição, 110
personalizando, 408, 409
array of, 69 IAsyncResult, 705
registrando, 407
begin, 82 IBankManager, 729
RCWs (Runtime Callable
bloco class var, 97 IBar, 403
Wrappers), 402
bloco try..finally 115, 116 IBindingList, 514
ICloneable, 221 Interop Type Libraries, 412, 413
break, 85
ICollection, 218, 219 componentes de, 412, 413
case, 82
IComparer, 218 criando, 412
class, 95, 102
const, 53, 88, 418 IDataErrorInfo, 514 interoperabilidade, 391
contains, 127 IDataReader, 468 exportações de DLL Win32 no
continue, 85 IDbCommand código .NET, 419, 420
default, 304 métodos, 457, 458, 459, 460, códigos de erro HResult, 428,
end, 82 461 429, 430
exports, 134 propriedades, 456, 457 códigos de erro Win32, 426,
finalization, 91 IDbConnection, 449, 450 427, 428
friend, 107 IDBDataAdapter, 475 empacotamento (marshaling),
if, 81, 82 IDbParameter, 462, 464 423, 424, 425, 426
implementation, 91 IDictionary, 218, 220 questões de desempenho, 430,
inherited, 215 IDictionaryEnumerator, 218 431, 432, 433, 434, 435
inicialization, 91 IDispatch, 393, 412 sintaxe de atributo
interface, 91 IDisposable personalizado, 421, 422,
library, 123 exemplo, 213, 214 423
loops, 83 implementando sintaxe Delphi tradicional,
902 Índice

420, 421 439, 440 Wrappers – COM ), 409


tipos de parâmetro, 423, 424, vantagens de, 391, 392 RCWs (Runtime Callable Wrap-
425, 426 interpolação pers), 402
tratamento de erro, 426 imagens, 166, 167 InvProject, aplicação, 380, 381
exportações Win32 DLL no
Interprocess Communications IPC (Interprocess Communica-
código .NET, 419
(IPC), 339, 718 tions), 339,718
.NET objetos in COM code
implementação de interface, Interrupt( ), método, 370 IPostBackDataHandler, interface,
414 interseção 857
objetos .NET no código COM, conjuntos, 73 IPostBackEventHandler, inter-
409 IntPtr, variável, 426 face, 856
automação com vinculação InvMemb, aplicação, 383 IsAuthenticated, propriedade
tardia, 411, 412 invocação de membro (classe FormsIdentity), 774
CCWs (COM Callable Wrap- método CreateInstanceFrom( ), ISerializable, interface, 282, 283
pers), 409 381 IsFixedSize, propriedade
empacotamento (marshal- método GetConstructor( ), 383 interface ICollection, 219, 220
ing), 415, 416, 417, 418 método GetEvent( ), 383 ISimpleFactory, interface, 744,
implementação de interface, método GetField( ), 383 745
413, 415 método GetMethod( ), 382,
Interop Type Libraries, 412, ISimpleServer, interface, 745
383
413 IspeechVoice, interface, 403
método GetProperty( ), 383
registro de assembly, 409, IspeechVoiceEvents, interface,
método InvokeMember( ), 381, 403
410 382
tipos de parâmetro, 415, 416, ISponsor, interface, 723, 752
método InvokeType( ), 381 IsPositive( ), função, 86
417, 418 método WriteMessage( ), 381 IsPostBack, propriedade (classe
tratamento de erro, 418 listagem de código de exemplo Page), 608, 609
objetos COM no código .NET, InvMemb, 383 ISpVoice, interface, 399, 403
393 listagem de código de exemplo IsReadyOnly, propriedade
automação com vinculação InvProject, 380 interface ICollection, 219, 220
tardia, 393, 394, 395
invocando IsResource( ), método, 376
COM com vinculação inicial,
GC (Garbage Collector), 210, IsSychronized, propriedade
397, 398, 399
211 interface ICollection, 219
controle de tempo de vida membros
COM, 405 IsSynchronized, propriedade, 362
listagem de código de
eventos COM, 403, 405 exemplo InvMemb, 383 IsValid, propriedade (classe
Interop Assemblies, 400, listagem de código de BaseValidator), 619
401, 402, 403, 408, 409 exemplo InvProject, 380 Item, propriedade
parâmetros de referência, método interface ICollection, 219, 220
396, 397 CreateInstanceFrom( ), Item, propriedade (classe Cache),
parâmetros de valor, 396, 381 817
397 método GetMethod( ), 382
Item, propriedade (DataTable),
parâmetros opcionais, 396, método InvokeMember( ),
397 573
381, 382
PIAs (Primary Interop método InvokeType( ), 381 Item propriedade (classe
Assemblies), 406, 407, método WriteMessage( ), 381 DataView), 502
409 Web Services assincronamente, ItemChanged, evento, 520
RCWs (Runtime Callable 691, 704, 705 ItemCommand, evento, 658, 663
Wrappers), 402
Invocation Platform Service. ItemCreated, evento, 658, 663
tratamento de erro, 406
Ver P/INVOKE, 418 ItemDataBound, evento, 663
questões de interoperabilidade
comuns, 392, 393 Invoke( ), método, 352, 363, ItemDataBound, evento, 658
rotinas .NET em código Win32, 395, 701
Items, propriedade (controle
435, 436 invokeAttr, parâmetro (método DataList), 663
declarações de importação, InvokeMember( )), 382
Items, propriedade (controle Re-
440 InvokeMember( ), método, 381, peater), 658
empacotamento (marshal- 382, 386, 395, 396
Items, propriedade (ListControl),
ing), 437, 438, 439, 440, InvokeRequired, propriedade 648
441 (classe Control), 363, 364
sintaxe Delphi tradicional, ItemStyle (controle DataGrid),
InvokeType( ), método, 381 668
436, 437
invólucros ItemStyle, propriedade (controle
tipos de parâmetro, 437, 438,
CCWs (Callable
Índice 903

DataList), 663 dados de BLOB, arquivo DPR de cliente, 85,


ItemTemplate, template, 658 recuperando, 470, 472 176, 267, 302, 343, 366,
IUnknown, interface, 393 informações de estrutura, 367, 381, 481, 540, 547,
recuperando, 472, 473 548, 549, 569, 645, 702,
interface IDataReader, 468 746, 750, 763, 764
J múltiplos resultsets, arquivo DPR de servidor, 757,
consultando, 469, 470 760
Java RMI, 711
resultsets únicos, canal TCP, 765
JIT (just-in-time), compilação,
consultando, 468, 469 classe RemotingHelper, 761
14
StreamReaders, 264 estrutura de arquivos XML de
Join( ), método, 363 StringReaders, 264 configuração, 755
joins TextReaders, 264 resposta SOAP, 741
DataViews, 512, 513 leituras de transação, 563, 564 solicitação SOAP, 741
just-in-time, compilação. Ver leituras fantasmas, 564 acesso assíncrono ao stream, 276
JIT, compilação, 14 aplicação BankExample
leituras não-repetíveis, 563
JustWorthless, controle de handler de evento Click do
leituras sujas (Dirty Reads), 563 botão Refresh, 734
exemplo, 291, 294
LEN( ), método, 508 handler de evento Click do
$J, diretiva de compilador, 55
lendo botão Retrieve, 735
FileStreams, 274, 275 unit BankServer.dpr, 732
Length, propriedade (classe unit BankServer_Impl.pas, 730
K StringBuilder), 251 unit BankShared.pas, 728
aplicações orientadas a banco de
Key, propriedade (classe Cache), letras maiúsculas, convertendo
dados ASP.NET
818 strings em, 250
controle CheckBoxList, 649
Keys, propriedade letras minúsculas, convertendo
controle DropDownList, 652
interface ICollection, 220 strings em, 250
controle ListBoxes, 654
Kothari, Nikhil, 869 liberando recursos controle RadioButtonList, 656
método Finalize( ), 211, 212, DataGrid Page_Load( ) e
213, 214 GetData( ), 673
L método Free( ), 211 declaração de controle
questões de desempenho, 216 DataList, 663
language, atributo (páginas
ASP.NET), 598 libra, sinal (#), 256 declaração de DataGrid, 669
library, instrução, 123 declaração de
Language, atributo (tag <@ Con-
LIKE, operador, 506, 507 TemplateColumn, 672, 673
trol %>), 844
LinearGradientBrush, classe, declaração de controle Re-
LastAccess, valor (tipo
144, 156 peater, 659
enumerado NotifyFilters), 281
LineCap, tipos enumerados, exemplo de controle Repeater,
LastAccessTime, propriedade 145, 147 658
(classe FileSystemInfo), 268 LineJoin, propriedade (classe exemplo de DataList, 664
LastAccessTimeUtc, propriedade Pen), 148 handler de evento de
(classe FileSystemInfo), 269 linha de status (packages de comando Update, 676
LastIndexOf( ), método, 228 resposta de HTTP), 593 página DataGrid, 72, 239,
linhas, desenhando 240, 241, 343, 369, 370,
LastWrite, valor (tipo
classe GraphicsPath, 147, 148 384, 670
enumerado NotifyFilters), 281
classe Pen, 142, 143 renderização de imagem, 666
LastWriteTime, propriedade classes Brush, 142, 143, 144, vinculação de dados simples,
(classe FileSystemInfo), 269 145 644
LastWriteTimeUtc, propriedade listagem de código de exemplo, ASP.NET
(classe FileSystemInfo), 269 143, 144, 145 conteúdo WebForm1.aspx,
layouts (área de trabalho), 27 método ClearCanvas( ), 142 596
leases, 723, 724 método DrawLine( ), 144 demo de filtro de saída, 603
leases, não conseguindo pontas de linha, 145 exemplo de HTTPRequest, 605
renovar, 754, 755 pontas de seta, 147 propriedade IsPostBack, 608
unindo linhas, 147, 148 WebForm1.aspx resultando
leitores
BinaryReaders, 264, 275, 276 listagens de código HTML, 599
classe BdpDataReader, 582, 583 .NET Remoting assemblies
DataReaders, 467 arquivo de configuração de carregando dinamicamente,
classe SQLDataReader, 468, cliente, 758 137
469 arquivo de configuração de referenciando, 134
servidor, 757 atualizando dados após
904 Índice

atualizações arquivo de configuração de 304, 307, 308, 309, 316,


evento RowUpdated, 557 servidor, 765 317, 318, 319, 320, 321,
instrução INSERT, 533, 556 concorrência, 552 322, 327, 331, 332, 333,
instrução UPDATE, 557 conexões 334, 335, 345, 346, 378,
autenticação baseada em abrindo e fechando, 452 453, 461, 471, 490, 493,
formulário eventos, 453 571, 573, 575, 604, 606,
método UserLoginValid( ) controles de usuário 660, 665, 729, 730, 731,
com SQL Server, 779 arquivo .aspx 819, 858, 859
método UserLoginValid( ) BasicUserControlPage, 844 controles WinForms
com um arquivo XML, declaração simples de criação de evento, 307
777 controle, 842 definição de SomeObject, 296
páginas individuais, controles Web exemplo ExplorerViewer, 315
autenticando, 774 controle composto exemplo JustWorthless, 291
autorização baseada em papéis TNewUserInfoControl, exemplo PlayingCard, 331
evento AuthenticateRequest, 103, 105, 115, 116, 134, exemplo SimpleStatusBars,
784 135, 137, 143, 145, 147, 326
handler de evento 148, 150, 153, 154, 155, implementação de
Page_Load( ), 784 157, 158, 160, 164, 171, TypeConverter, 298
método GetUserRoles( ), 785 172, 173, 192, 201, 223, propriedades de array, 302
BDR (Borland Data Provider) 226, 230, 235, 236, 237, propriedades de objeto, 297
classe BdpCommand, 581 257, 272, 274, 276, 278, propriedades de tipo
classe BdpConnection, 580 280, 285, 295, 297, 299, enumerado, 295
classe BdpDataAdapter, 583 303, 328, 347, 350, 352, propriedades simples, 294
classe BdpDataReader, 582 354, 355, 356, 357, 358, cookies, 827
classe BdpParameter, 585 359, 364, 365, 367, 370, curvas
classe BdpTransaction, 586 381, 397, 410, 411, 414, splines cardinais, 148
bibliotecas 421, 434, 436, 437, 439, splines de Bezier, 149
D8DG.LibU.pas, 132 441, 452, 454, 462, 463, DataAdapters
D8DG.TestLib.dpr, 130 465, 467, 469, 470, 471, consultas parametrizadas, 480
cache de dados, 819 473, 480, 482, 492, 494, DataSets, preenchendo, 478
cache de página 503, 504, 505, 510, 511, exemplo de DataMapping,
diretiva @ OutputCache, 812 513, 518, 519, 521, 524, 481
variando por parâmetros, 525, 530, 534, 536, 539, DataReaders
814 545, 550, 561, 575, 582, dados de BLOB, recuperando,
cache utilizando métodos de 583, 584, 585, 597, 599, 470
retorno, 823 604, 608, 615, 617, 621, informações de estrutura,
classe GraphicsPath, 157 623, 634, 635, 638, 650, recuperando, 472
classe SqlCommand 654, 657, 665, 666, 670, múltiplos resultsets,
exemplo, 532 674, 677, 693, 700, 703, consultando, 469
instrução DELETE, 537, 538 705, 733, 735, 742, 745, resultsets únicos,
instrução UPDATE, 535, 536 747, 748, 749, 761, 762, consultando, 468
método SubmitAddedRow( ), 764, 765, 766, 775, 778, DataRelations, 493
534 779, 795, 812, 814, 820, DataSets
procedure SubmitUpdates( ), 821, 822, 824, 849, 852, preenchendo, 478
533 854, 860, 863, 864, 865, DataSets fortemente tipificados
classe SqlCommandBuilder, 866, 867, 868 definição de
530 controle persistente, 81, 100, EmmployeeDataTable, 571
classe SqlDataAdapter 129, 131, 161, 178, 179, definição de EmployeeRow,
DataAdapter exemplo do 180, 222, 225, 259, 308, 360, 574, 575
bloco principal, 539 353, 373, 395, 440, 522, definições de DataRow, 360,
GetDataAdapterDelete- 523, 526, 554, 560, 562, 574, 575
Command( ), 543 606, 687, 688, 728, 756, definições de DataTable, 571
GetDataAdapterInsert- 794, 824, 837, 851, 852 listagem de código de
Command( ), 541 exemplo, 307, 568, 570
controle Web simples, 848
propriedades/métodos de
GetDataAdapterUpdate- método get_TegKey( ), 856
DataTable, 573, 574
Command( ), 541 método Render( ), 853
DataViews
classe TStateInfoDictionary, método RenderContents( ),
exemplo de filtragem simples,
238, 242 853
504
codificação binária TPostbackInputWebControl,
exemplo simples de
arquivo de configuração 75, 100, 177, 229, 232,
DataView, 503
binária, 766 233, 234, 252, 284, 285,
Índice 905

filtrando com funções FileSystemWatcher, 279 Page_Load( ), 200


agregadas, 510 exemplo de procedure/função, MyUnit.pas, 40, 41
RowStateFilter, 509 86 packages
DataViews exportações não-gerenciadas arquivo package de exemplo,
joins, 513 código Delphi, 440 125
dependências de arquivo de declarações de importação, D8DG.PkgUnit, 129
cache 440 D8DG.TestPkg, 128
criando, 820 exemplo, 438 padrão factory
método GetData( ), 822 FileStreams arquivo DPR de cliente, 749
diretórios criando e gravando em, 271 arquivo DPR de servidor, 748
informações de diretório, criando e lendo a partir de, implementação de
visualizando, 269 274 TsimpleFactory, 747
método CopyDirectory( ), dados binários, 275 implementação de
266 imagens TsimpleServer, 746
elipses, desenhando, 154 mapas de bits, carregamento, interfaces compartilhadas,
Empilhe exemplo de coleção, 163 745
222 método polígonos, desenhando, 154
escopo, 90 MultiplyTransform( ), 171 reflection
especificadores de formato de método SetResolution( ), 164 assemblies, 373
data/hora, 258 método invocação de membro,
especificadores de formato TranslateTransform( ), 173 aplicação InvMemb, 383
numérico modo de interpolação, 166 invocação de membro,
especificadores transformações, 170 aplicação InvProject, 380
personalizados de formato interoperabilidade módulos, 375
numérico, 256 automação com vinculação MSIL, emitindo por reflection,
exemplo de, 255 tardia, 396, 411 388
esqueleto de arquivos de declarações de importação, tipos, 377
programa, 39 437 vinculação tardia, 379
esquema de arquivo eventos COM, 403 regiões
web.config, 793 implementação de interface, exemplo de, 158
evento Session_End, 835 413 recortando, 160
evento Session_Start, 835 interface COM de exemplo, retângulos, desenhando, 153
eventos 396 serialização/desserialização, 283
eventos multicast, 104 método Win32Check( ), 428 stored procedure
eventos singleton, 102 registro de assembly .NET, DeleteProduct, 551
exceções 410 InsertProduct, 547
levantando novamente, 120 retardo na primeira chamada GetDataAdapterDeleteComma
tratamento de exceções de em chamadas P/Invoke, nd( ), 550
E/S de arquivo, 114 434 GetDataAdapterInsertComma
execução de comando rotinas .NET exportadas para nd( ), 546
comandos DDL (Data Defini- Win32, 436 GetDataAdapterUpdateComm
tion Language), 461 SAPI (Microsoft Speech API), and( ), 548
consultas parametrizadas, 399 SelectProduct, 545
463 unit de importação .NET SqlDataAdapter, 544
ExecuteNonQuery( ), bruta para uma DLL UpdateProduct, 549
método, 458, 459 simples, 421 strings
método ExecuteScalar( ), 459 unit simples de importação recortando, 160
parâmetros, derivando, 466 de DLL, 420 threads
stored procedure, 464, 465 Word Automation, 394, 395 classe AutoResetEvent, 360
exemplo da coleção HashTable, linhas, desenhando classe Interlocked, 358
232 método ClearCanvas( ), 142 classe ManualResetEvent, 359
exemplo da coleção Queue, 225 pontas de linha, 145, 146 classe Monitor, 356
exemplo de animação técnicas para desenhar classe Mutex, 356
declaração de sprite, 175 linhas, 143 classe ReaderWriterLock, 357
declaração do formulário unindo linhas, 147 classe Thread, 342
principal, 176 método FillSchema( ), 491 classe ThreadPool, 349
implementação de demo de Mono classe Timer, 351
animação, 177 aplicação console classe WaitHandle, 355
exemplo de coleção ArrayList, MonoMenu .NET, 192, criando com métodos de
228 193 instância, 344
exemplo de handler de evento criando com métodos
906 Índice

estáticos, 346 método GenerateThumbs( ), Location, configuração


delegates, 352 640 (@OutputCache), 803
exemplo método SaveUserInfo( ), 614 Location, propriedade (classe
ThreadingExceptions, 368 método SendEMail( ), 637 Assembly), 374
método CreateGraphics( ), Word Automation
Lock( ), método, 837
366 automação com vinculação
método Invoke( ), 363 tardia, 395 Locking Control (Designer), 26
métodos BeginInvoke( ) e instanciação de objeto, 394 LoggedIn( ), método, 829
EndInvoke, 364 listagens do programa. Ver lógica de atualização
métodos da classe Control, listagens de código, 40 personalizada, 531
362 listas classe SQLCommand, 531, 532,
tortas, desenhando, 155 controle CheckBoxList 534, 535, 536, 537, 539
transações propriedades, 648 instrução DELETE, 537, 538,
exemplo simples, 559 vinculando a arrays, 649 539
TStateInfoCollection, 234, 238 vinculando a tabelas de instrução INSERT, 533, 534
vinculação de dados banco de dados, 649, 650 instrução UPDATE, 535, 536,
caixa de Listagem, 516 controle DropDownList, 650, 537
formato de dados e 651, 652 listagem de código de
analisando vinculando a arrays, 650, exemplo, 531, 532
sintaticamente, 524 método SubmitAddedRow( ),
651
mestre/detalhe, 526 534, 535
vinculando a tabelas de
modificação de dados, 521 método SubmitUpdates( ),
banco de dados, 651, 652
navegação e tratamento de 532, 533
controle ListBoxes, 653, 654
evento, 518 classe SQLDataAdapter, 539,
vinculando a arrays, 653
Web Services 540, 541, 543, 544
vinculando a tabelas de
autenticação de usuário, 82, exemplo do bloco principal,
banco de dados, 654
83, 85, 108, 117, 120, 190, 539
controle RadioButtonList, 655,
191, 211, 241, 255, 278, GetDataAdapterDeleteCom-
656, 657
280, 300, 376, 460, 516, mand( ), 544
vinculando a arrays, 648,
541, 542, 543, 550, 638, GetDataAdapterInsertCom-
653, 655, 656
640, 641, 646, 706, 708 mand( ), 541
vinculando a tabelas de
classe proxy WebService1, GetDataAdapterUpdateCom-
banco de dados, 653, 656,
698 mand( ), 542
657
classe proxy WebService2, GetDataAdapterDeleteCom-
controles de lista,
702 mand( ), 543
pré-preenchendo, 616, 617,
exemplo, 687 GetDataAdapterInsertCom-
618
invocando assincronamente, mand( ), 541
handler de evento OnInit,
705 GetDataAdapterUpdateCom-
617, 618
retornando dados a partir de, mand( ), 541
método
692 questões de concorrência, 551,
PopulateDdlFromFile( ),
WebForms 552, 553, 554, 555
617
classe de estilo, 628 handler de evento
vinculação de dados, 516, 517
controle CompareValidator, OnRowUpdated( ), 553
ListBoxes, controle, 653, 654 listagem de código de
621 vinculando a arrays, 653
controle HtmlInputFile, 635 exemplo, 552
vinculando a tabelas de banco stored procedure, 544, 545, 546,
controle Panel, 633 de dados, 654
controle 547, 548, 549, 550, 551
Load, evento DeleteProduct, 551
RegularExpressionValida-
processando, 613, 614 exemplo SqlDataAdapter, 544
tor, 622
estilos fortemente tipificados LoadModule, diretiva, 203 InsertProduct, 547
WebControl, 627 LoadPostData( ), método, 857, método
evento ImageForm Load, 638 861 GetDataAdapterDeleteCom-
exemplo arquivo .css, 627 LocalBuilder, classe, 387 mand( ), 550, 551
função método
localização, 248
PopulateDdlFromFile( ), GetDataAdapterInsertCom-
localOnly, atributo (tag mand( ), 546
617 <trace>), 808
handler de evento método
<location>, seção (ASP.NET GetDataAdapterUpdate-
ImageButton1_Click( ),
web.config), 795 Command( ), 548
641
handler de evento OnInit, Location, atributo SelectProduct, 545, 546
617 (@OutputCache), 812 UpdateProduct, 549, 550
Índice 907

LoginControl, 845, 846, 848 maxIoThreads, atributo (tag <processModel>), 801


logins <processModel>), 801 MemoryStreams, 264
LoginControl, 845, 846, 848 MaxLength, propriedade mensagem, métodos, 99
loginUrl, atributo (seção (DataColumns), 488 mensagens
<forms> de web.config), 773 MaxRequestLength, atributo rastreando, 738, 739
logLevel, atributo (tag (tag <httpRuntime>), 797 message, instrução, 99
<processModel>), 801 maxWorkerThreads, atributo MessageBeep, método, 419
loops, 83 (tag <processModel>), 801 mestre/detalhe
instrução break, 85 mcs, comando, 190 vinculação de dados, 526, 527
instrução continue, 85 MD5CryptoServiceProvider, metadados, 12
loop for, 83 classe, 780 Reflection, 374
loop repeat..until, 84 mecanismos bloqueadores, 355 MetaDataChanged, evento, 520
loop while, 84 classe Interlocked, 358, 359
terminando, 85 Metafile, classe, 163
classe Monitor, 356, 357
Low( ), função, 67 metarquivos, 163
classe Mutex, 356
Lowest, prioriedade de thread, classe ReaderWriterLock, 357, MethodBuilder( ), método, 390
347 358 MethodBuilder, classe, 387, 390
classe WaitHandle, 355, 356 métodos, 311
membros de dados, 94 Ver também funções, 50
M AcceptChanges( ), 490
memória
m, especificador de formato, alocando, 75 AcquireReaderLock( ), 358
260 ASP.NET, armazenamento em AcquireWriterLock( ), 358
M, especificador de formato, cache ActivateFile( ), 323
260 em cache de página, 814 Adapter( ), 227
em cache da saída Add( ), 219, 220, 463, 483, 487,
Macdonald, Matthew, 768
(ASP.NET), 802, 803 494, 495, 513, 517, 701, 817
machine.config, arquivo, 792,
GAC (Global Assembly AddAttributesToRender( ), 861
793
Cache), 123 AddEmployeeRow( ), 573, 576
MailMessage, classe, 637 AddNew( ), 502, 520
cache de ASP.NET, 811
manifestos AddRange( ), 227, 486, 487, 488
cache de dados, 816, 817,
definição, 122 AllocateDataSlot( ), 361
818, 819, 820
ManualResetEvent, classe, 359 cache de fragmentos de AllocateNamedDataSlot( ), 361
manutenção de estado página, 816 Append( ), 251, 273
Web Forms, 600 cache de página, 811, 812, AppendFormat( ), 251
813, 815, 816 ApplyStyle( ), 628
.map, arquivos, 697
cache utilizando métodos de Authenticate( ), 708, 777
mapa da estrada (Mono), 185
retorno, 823, 825 Begin( ), 565
objetivos do Mono 1.0, 186
dependências de arquivo de BeginEdit( ), 495, 496, 577
objetivos do Mono 1.2, 186,
cache, 820, 821, 823 BeginGetEmployees( ), 703, 705
187
finalização, 211, 212 BeginInvoke( ), 352, 364, 365
objetivos do Mono 1.4, 187
método Finalize( ), 212, 213 BeginRead( ), 278, 279
mapas de bits, 162, 163 questões de desempenho, BeginTransaction( ), 450, 558,
carregando, 163 216 586
classe Bitmap, 162 garbage collection BeginWrite( ), 278
criando, 163 aplicações multithread, 371 BinarySearch( ), 227
mapeando GC (Garbage Collector), 206, BitBlt( ), 174
resultados de consulta, 480, 207 BytesToHex( ), 780
482, 483 classe System.GC, 210, 211 cache utilizando métodos de re-
tabelas garbage collection torno, 823, 825
coleção TableMappings, 476, geracional, 207, 209, 210 Cancel( ), 457
482 invocando, 210, 211 CancelCurrentEdit( ), 520, 521
MapPath( ), método, 821 raízes, 207 CancelEdit( ), 496, 577
MarshalAsAttribute, parâmetro, padrão dispose, 213 CanExtend( ), 325
416 exemplo de IDisposable, 213, Change( ), 351
214 ChangeDatabase( ), 450
Marshal-By-Reference, objetos,
implementando ClassIDToProgID( ), 429
721, 722
automaticamente, 214, Clear( ), 219, 220, 486
matemática, operadores, 56 215, 216 ClearCanvas( ), 142
MaxCapacity, propriedade memoryLimit, atributo (tag Clone( ), 221, 486
(classe StringBuilder), 251
908 Índice

Close( ), 274, 450, 452, 580 DoTextChanged( ), 860 GetCallingAssembly( ), 374


CLSIDFromProgID( ), 428 DrawAngle( ), 145 GetChanges( ), 486
Collect( ), 210 DrawBezier( ), 151 GetChars( ), 472
Commit( ), 558, 559, 586 DrawEllipse( ), 154 GetChildRow( ), 494
Compare( ), 247, 248 DrawImage( ), 165, 180 GetChildTableNameRows( )
CompareOrdinal( ), 247, 248 DrawLine( ), 144, 173 (ChildTableName), 577
Concat( ), 248 DrawPath( ), 157 GetCommandLine( ), 425
Configure( ), 720 DrawPolygon( ), 154 GetConstructor( ), 383
construtores, 211, 312 DrawSaucerTrans( ), 181 GetCountry( ), 816
Count( ), 511 Emit( ), 390 GetCustomAttributes( ), 374,
Contains( ), 219, 220 EmitWriteLine( ), 390 376
Contains( ), 487, 488 End( ), 603 GetData( ), 671, 673, 674, 683,
ContainsKey( ), 231 EndCurrentEdit( ), 520, 521 820, 821, 823
ContainsValue( ), 231 EndEdit( ), 495, 577 GetDataAdapterDeleteCom-
ConvertAssemblyToTypeLib( ), EndGetEmployees( ), 703, 705 mand( ), 543, 544, 550, 551
412 EndInvoke( ), 364, 365 GetDataAdapterInsertCommand
ConvertFrom( ), 298 Enqueue( ), 223, 224 ( ), 541, 546
ConvertTo( ), 298 EnsureCapacity( ), 251 GetDataAdapterUpdateCom-
ConvertTypeLibToAssembly( ), EnsureChildControls( ), 862 mand( ), 541, 542, 548
402, 407 Enter( ), 357 GetDeclareString( ), 378
Copy( ), 250, 486 Equals( ), 251 GetDirectories( ), 323
CopyDirectory( ), 266 especificadores de visibilidade, GetDotNETVersion( ), 438
CopyTo( ), 219, 502 105, 106, 107 GetEmployees( ), 694, 703
Create( ), 95, 163, 265, 272, Eval( ), 661 GetEnumerator( ), 219, 227
345, 385, 492, 517, 519, 525 ExecuteNonQuery( ), 457, 458, GetEvent( ), 383
CreateChildControls( ), 862, 459, 562 GetField( ), 383, 647
869 ExecuteReader( ), 457, 468, GetFields( ), 376
CreateCommand( ), 450 469, 472, 473, 582, 583 GetFirstName( ), 647
CreateDirectory( ), 265 ExecuteScalar( ), 457, 459, 460 GetGeneration( ), 211
CreateGraphics( ), 365, 368 ExecuteXMLReader( ), 457 GetHistory( ), 804
CreateInstance( ), 386, 394 Exists( ), 265 GetInput, 192, 193
CreateInstanceFrom( ), 381 Exit( ), 357 GetInsertCompanyCmd( ), 562
CreateOleObject( ), 393 ExtractIcon( ), 323 GetInsertContactCmd( ), 562
CreateParameter( ), 457 Fill( ), 479, 486, 491 GetLastError( ), 426, 427, 430
CreateProcess( ), 422, 424 FillEllipse( ), 154 GetLastName( ), 647
CreateProcessW( ), 422 FillListView( ), 323 GetMembers( ), 378
CreateText( ), 270 FillPath( ), 157 GetMethod( ), 382, 383, 390
CreateWLCall( ), 389, 390 FillPolygon( ), 154 GetMethods( ), 376
CurrencyStringToDecimal( ), FillSchema( ), 491, 492 GetModules( ), 374
525 FillTreeView( ), 323 GetMyName( ), 643
DataBind( ), 644 Finalize( ), 95, 212, 213, 216 GetObject( ), 759
DateToStr, 198 Find( ), 497, 498, 502, 512 GetObjectData( ), 231
DecimalToCurrencyString( ), FindByEmployeeID( ), 573 GetParentTableNameRow( )
525 FindRows( ), 502, 512 (ParentTableName), 577
declarando, 97, 98 FindTableNameByID( ), 577 GetPlanePosition( ), 303
DefineDynamicAssembly( ), FixedSize( ), 227 GetPlanetName( ), 303
390 Flush( ), 274 GetPrimaryInteropAssembly( ),
DefineDynamicModule( ), 390 Format( ), 252, 253 407
DefineMethod( ), 390 FormatMessage( ), 426 GetProperty( ), 383
definição, 50, 94, 97 Free( ), 211, 215 GetRange( ), 228
Delete( ), 265, 270, 496, 502 GenerateThumbs( ), 640 GetReferencedAssemblies( ), 374
Dequeue( ), 223, 224 Get( ), 817 GetRemoteObjectRef( ), 759, 760
DeriveParameters( ), 467 GET, 593 GetSchemaTable( ), 472
Deserialize( ), 286 get_DefaultSize( ), 330 GetStateInfo( ), 237
Destroy( ), 215, 313 get_TagKey( ), 856, 861 GetThumbNailImage( ), 639
destrutor Destroy( ), 312 GetAccount( ), 52, 55, 86, 103, GetTitle( ), 647
destrutores, sobrescrevendo, 108, 109, 111, 113, 119, 130, GetTotalMemory( ), 211
312, 313 132, 133, 292, 293, 294, 400, GetType( ), 395
Dispose( ), 213, 214, 312 404, 479, 486, 537, 556, 645, GetTypeFromProgID, 394
DoSomething( ), 436 719, 722, 723, 742 GetTypes( ), 374, 376
DoSomethingElse( ), 436 GetBytes( ), 472 GetUserName( ), 423, 427, 432
Índice 909

GetUserRoles( ), 785 Move( ), 266, 270 RenderContents( ), 853, 855


GetVaryByCustomString( ), 816 MoveNext( ), 220 Renewal( ), 753
HashPasswordForStoringInCon MoveSaucer( ), 180, 181 Repeat( ), 228
figFile( ), 776 MultiplyTransform( ), 168, 171 Replace( ), 246, 249, 251
HelloWorld( ), 688, 701, 708 NewEmployeeRow( ), 573 Reset( ), 220, 360
IFF( ), 508 NewRow( ), 495 ResetAbort( ), 368
IncludeTrailingPathDelimiter( ) NextResult( ), 470 Resume( ), 343
, 267 OleCheck( ), 430 ResumeBinding( ), 520
IndexOf( ), 219 OnDeserialization( ), 231 Reverse( ), 228
Init( ), 298, 312 OnJump( ), 305, 306 Rollback( ), 558, 559, 565
InitComp( ), 330, 335, 336 OnPaint( ), 330, 337 RollBack( ), 586
InitializeComponent( ), 312 Open( ), 450, 452, 580 RotateFlip( ), 167
InitializeLifetimeService( ), 752, PadLeft( ), 250 RotateTransform( ), 168, 170,
753 PadRight( ), 250 171
Insert( ), 219, 246, 249, 251, Peek( ), 221, 224, 274 Save( ), 565
818 Pop( ), 221 SaveUserInfo( ), 614, 615, 616,
InsertContact( ), 562 PopulateDdlFromFile( ), 617 683, 684
InsertRange( ), 228 POST, 593, 629, 630 ScaleTransform( ), 168
interdependências, 311 Prelink( ), 432, 435 Seek( ), 273
Interrupt( ), 370 PrelinkAll( ), 432 Select( ), 498, 499, 778
InsertCompany( ), 562 Prepare( ), 457 SendEMail( ), 637, 683
Invoke( ), 352, 363, 395, 701 PrintItems( ), 237 Serialize, 285
InvokeMember( ), 381, 382, Pulse( ), 357 Set( ), 360
386, 395, 396 Push( ), 221 SetClip( ), 160
InvokeType( ), 381 QueueUserWorkItem( ), 349 SetEntryPoint( ), 390
IsResource( ), 376 RaiseLastOSError( ), 427 SetLastError, 426
Join( ), 363 RaiseLastWin32Error( ), 427 SetMode( ), 523
LastIndexOf( ), 228 RaisePostBackEvent( ), 857 SetRange( ), 228
LEN( ), 508 RaisePostDataChangedEvent( ), SetResolution( ), 163, 165
LoadPostData( ), 857, 861 860 SetRowNum( ), 647
Lock( ), 837 Read( ), 274, 468, 469 SetSomeProp( ), 309
LoggedIn( ), 829 ReadBlock( ), 274 SetStateInfo( ), 237
MapPath( ), 821 ReadChars( ), 276 SetStyle( ), 335
MessageBeep, 419 ReadLine( ), 274 SetWindowText( ), 423
MethodBuilder( ), 390 ReadOnly( ), 228 SetWindowTextW( ), 423
métodos de classe, 98 ReadToEnd( ), 274 SHGetFileInfo( ), 323
métodos de despacho de ReBind( ), 647 sobrecarregando, 99
evento, 305, 306 Redirect( ), 630 sobrescrevendo, 99
métodos de instância RedirectFormLoginPage( ), 775, Some_Func( ), 420
threads, criando, 344 776 Sort( ), 228
métodos de mensagem, 99 ReferenceEquals( ), 246 Split( ), 249, 617
métodos dinâmicos, 99 ReflectAssembly( ), 374, 376 StartFigure( ), 157
métodos estáticos, 98 ReflectType( ), 378 SubmitAddedRow( ), 534, 535
threads, criando, 345 Refresh( ), 520 SubmitUpdates( ), 532, 533
métodos private, 106, 311 RefreshNode( ), 323 SubString( ), 508
métodos protected, 106, 311 RefreshSchema( ), 529 SuppressFinalize( ), 213, 214
métodos public, 106, 312 reintroduzindo nomes de Suspend( ), 343
métodos published, 106, 312 método, 100 SuspendBinding( ), 520
métodos regulares, 98 ReleaseComObject( ), 405 synchronized( ), 361
métodos static ReleaseMutex( ), 356 Syncronize( ), 224
threads, criando, 347 RemoteAt( ), 486 Syncronized( ), 221, 228, 231
métodos strict private, 106, 311 Remove( ), 219, 220, 251, 818 SysErrorMessage( ), 426, 427
métodos strict protected, 106, Remove( ), 249, 486, 837 ThreadMePlease( ), 345
311 RemoveAll( ), 837 ToArray( ), 221, 224, 228
métodos thread-safe, 361 RemoveAt( ), 219, 520 ToLower( ), 250
método synchronized( ), 361 RemoveAt( ), 486 ToString( ), 251
propriedade IsSynchronized, RemoveEmployeeRow( ), 573 ToString( ), 269
362 RemoveRange( ), 228 ToUpper( ), 250
propriedade SyncRoot, 362 RemoveTableNameRow( ) Transfer( ), 631, 632
métodos virtuais, 98 (TableName), 577 TranslateTransform( ), 168, 170,
Module( ), 435 Render( ), 853 171, 173
910 Índice

Trim( ), 250 vinculação de dados, 520, 521, distribuição de ASP.NET no


TrimEnd( ), 250 523 Mono, 197
TrimStart( ), 250 Modified RowState, 496 handler de evento
TrimToSize( ), 224, 228, 230 ModifiedCurrent, valor Button1_Click, 196
TryEnter( ), 357 (DataViewRowState), 508 parâmetros runtime de XSP,
Unlock( ), 837 197, 198
ModifiedOriginal, valor
Update( ), 529, 562 portabilidade, 198, 199
(DataViewRowState), 508
UpperCase( ), 39 assemblies, executando, 191
UserLoginValid( ), 775, 776, modifiers, parâmetro (método aplicação console MonoMenu
777, 778, 779, 780 InvokeMember( )), 382 .NET, 191
Validate( ), 619 mod-mono, package, 189, 203 console da aplicação
variável Self, 100 mod-mono-server, package, 203 MonoMenu .NET, 192
VerifyRenderingInServer- erros de compilador, 194
Module( ), método, 435
Form( ), 861 MonoFuncs unit, 193
Module, classe unit MonoFuncs, 193
VerifyType( ), 237 métodos, 374, 376
Wait( ), 357, 371 comunidade do código-fonte
propriedades, 374, 376 aberto, 205
WaitAll( ), 356, 360 reflection, 375, 376, 377
WaitAny( ), 356, 360 definição, 183
listagem de código de história de, 184
WaitOne( ), 356, 360, 705 exemplo, 375
Win32Check( ), 427, 428 instalando com Red Carpet, 187
método objetivos, 184
Write( ), 273, 275, 347, 602, GetCustomAttributes( ),
806, 850 objetivos do Mono 1.0, 186
376
WriteLine( ), 244, 273, 347, 390 objetivos do Mono 1.2, 186, 187
método GetFields( ), 376
WriteMessage( ), 381 objetivos do Mono 1.4, 187
método GetMethods( ), 376
WriteSomething( ), 385 método GetTypes( ), 376 packages, 189
Microsoft Developers Network método IsResource( ), 376 recursos de, 183, 184
(MSDN), 4 método ReflectAssembly( ), site Web, 205
376 site Web (FAQ), 184
Microsoft Intermediate Lan-
ModuleBuilder, classe, 387, 390 SWF (System.Window.Forms),
guage (MSIL), 14, 15, 16, 49
204, 205
Microsoft .NET Distributed Ap- módulo (%), operador, 506
vantagens de, 185
plications módulo, operador, 57
Mono Project, história, 184
Integrating XML Web Services módulos
and .NET Remoting, 768 Mono Project, objetivos
módulos gerenciados, 12, 38
Mono 1.0, 186
Microsoft Speech API. Ver SAPI, esqueleto de arquivos de
Mono 1.2, 186, 187
398 programa, 39
Mono 1.4, 187
Microsoft Word módulos principais, 38
referências circulares de unit, Mono, package, 189
automação
42, 43 Mono, projeto, 28
automação com vinculação
tardia, 395 sintaxe da cláusula uses, 39, MonoADO, aplicação, 199, 200,
objeto Automation, 42 201, 202
instanciando, 394 units, 39, 40, 41, 45, 46, 47 controles Web, 199
principais, 38 handler de evento
MIDAS, tecnologia, 446
minFreeLocalRequestFree- Monitor, classe, 356, 357 btnSubmit_Click( ), 199
Threads, atributo (tag handler de evento Page_Load( ),
monitorando
<httpRuntime>), 797 200
ASP.NET, 803, 804
minFreeThreads, atributo (tag MonoASP, aplicação, 195, 196,
Mono
<httpRuntime>), 797 197
Apache, 203, 204
miniaturas, 172 configuração de XSP, 197
aplicação de exemplo
mm, especificador de formato, controles Web, 196
260 Hello.exe, 190, 191
distribuição de ASP.NET no
MM, especificador de formato, aplicações ADO.NET, 199, 200,
Mono, 197
260 201, 202
handler de evento
MMM, especificador de controles Web, 199
Button1_Click, 196
formato, 260 handler de evento
parâmetros runtime de XSP, 197
MMMM, especificador de btnSubmit_Click( ), 199
diretório-raiz da aplicação,
formato, 260 handler de evento
mod, operador, 57 197, 198
Page_Load( ), 200
mode, atributo (tag diretório-raiz Web, 197
aplicações ASP.NET, 195, 196,
<sessionState>), 798 porta e endereço, 198
197
Model View, 34 portabilidade, 198, 199
configuração de XSP, 197
modificação de dados controles Web, 196 monodoc, package, 189
Índice 911

MonoFuncs, unit, 193 N 719


MonoMenu .NET, aplicação classe ChannelServices, 720
name, atributo (seção <forms> classe
console, 191
de web.config), 773 RemotingConfiguration,
mono-wine, package, 189
name, parâmetro (método 719, 720
Move( ), método, 266, 270 InvokeMember( )), 382 System.Runtime.Serialization, 19
movendo Name,propriedade System.Security, 19
arquivos, 270 (classeFileSystemInfo), 269 System.ServiceProcess, 19
diretórios, 266, 267, 268 System.Text, 19
namedParameters, parâmetro
MoveNext( ), método, 220 (método InvokeMember( )), System.Threading, 19, 342
MoveSaucer( ), método, 180, 382 classe ApartmentState, 348,
181 349
namespace de globalização, 248
mscoree.dll, 410 classe AutoResetEvent, 360
namespaces, 18, 43, 45, 90, 91
classe de threadstate, 348
mscorlib.dll, 121 cláusula de namespaces, 46
classe Interlocked, 358, 359
MSDN (Microsoft Developers convertendo, 46 classe ManualResetEvent, 359
Network), 4 criando, 130 classe Monitor, 356, 357
MSDN Online, 768 declarando, 43, 45 classe Mutex, 356
MSIL namespace System.Collections, classe ReaderWriterLock, 357,
emitindo por reflection, 386, 18, 217 358
387 coleção ArrayList, 221, 225, classe SynchronizationLock-
aplicação de exemplo, 388, 227, 228, 230 Exception, 371
coleção HashTable, 225, 228, classe Thread, 342, 345, 347
390
230, 231, 232, 233 classe ThreadAbortException,
classe AssemblyBuilder, 387
coleção Queue, 221, 223, 370, 368
classe ConstructorBuilder,
224, 226 classe ThreadInterrupted-
387 Exception, 370
classe coleção Stack, 220, 221, 223
coleções fortemente classe ThreadPool, 349, 350,
CustomAttributeBuilder, 351
387 tipificadas, 234, 237, 238
classe ThreadPriority, 347, 348
classe EnumBuilder, 387 dicionários fortemente
classe ThreadState, 348
classe EventBuilder, 387 tipificados, 238, 241, 242
classe ThreadStateException,
classe FieldBuilder, 387 interfaces, 218, 219, 220
371
classe ILGenerator, 388 namespace classe Timer, 351, 352
classe LocalBuilder, 387 System.ComponentModel, classe WaitHandle, 355, 356
classe MethodBuilder, 387 18 System.Timers, 19
classe ModuleBuilder, 387 namespace System.Data, 449 System.Web, 19
classe ParameterBuilder, 387 namespaces padrão de projeto, System.Windows.Forms, 19, 21
classe PropertyBuilder, 387 43 System.XML, 19
classe TypeBuilder, 387 System, 18
namespaces genéricos
namespace System.Reflec- System.CodeDom, 387
convertendo, 46
tion.Emit, 386, 387 System.CodeDOM, 18
System.Configuration, 18 NameValueSectionHandler, han-
processo de emissão, 388 dler de configuração, 810
System.Data, 18
MSIL (Microsoft Intermediate navegação
System.DirectoryServices, 18
Language), 14, 15, 16, 49 vinculação de dados, 517, 519,
System.Drawing, 18, 19, 21,
msw.Free, instrução, 40 139 520
MTA, estado de apartment, 349 System.EnterpriseServices, 19 navegadores
multicast, eventos, 103, 105 System.Globalization, 248 comunicação de
multiplicação (*), operador, 57, System.IO, 19, 263, 264 navegador/servidor Web, 592,
506 System.Management, 19 594, 595
System.Messaging, 19 redirecionando, 605
múltiplos resultsets
System.Net, 19 navegadores da Web, 605
consultando, 469, 470
System.Reflection, 19 navegando, código, 33
MultiplyTransform( ), método,
System.Reflection.Emit, 386, navegando entre WebForms, 629
168, 171
387 método
Mutex, classe, 356 System.Resources, 19 HttpResponse.Redirect( ), 630
MyUnit.pas, arquivo, 39 System.Runtime.Compiler- método POST, 629, 630
MyUnit.pas, arquivo, 40 Services, 19 método Server.Transfer( ), 631,
System.Runtime.InteropServi- 632
ces, 19 variáveis de sessão, 632, 633
System.Runtime.Remoting, 19, .NET Framework, 1, 2
912 Índice

Ver também ADO.NET, 443 System.EnterpriseServices, 19 Protocol), 685


Ver também namespaces, 217 System.IO, 19 UDDI (Universal Description,
assemblies. Ver assemblies, 121 System.Management, 19 Discovery, and Integration),
bibliotecas de classes, 5 System.Messaging, 19 686
VCL for .NET, 5, 6 System.Net, 19 atributo, 691, 692, 694, 695
CLR (Common Language System.Reflection, 19 WSDL (Web Service
Runtime), 4, 12 System.Resources, 19 Description Language), 686
assemblies, 13 Sysem.Runtime.CompilerSer- XML (Extensible Markup
código gerenciado, 13 vices, 19 Language), 685
código não-gerenciado, 13 Sysem.Runtime.InteropServi- .NET Reflector, 289
módulos gerenciados, 12, 38 ces, 19 .NET Remoting
seqüência de System.Runtime.Remoting, Advanced .NET Remoting, 768
carregamento/compilação/ 19 aplicação BankExample, 724
execução, 14 System.Runtime.Serializa- arquivo BankServer.dpr, 732,
CLS (Common Language Speci- tion, 19 733
fication), 17 System.Security, 19 classe TAccount, 729
documentação, 17 System.ServiceProcess, 19 configurando, 725, 726
verificando compatibilidade System.Text, 19
implementação de cliente,
de CLS, 17 System.Threading, 19
733, 734, 735, 736
compilação JIT (just-in-time), System.Timers, 19
interface de IBankManager,
System.Web, 19
14 729
System.Windows.Forms, 19,
computação móvel, 15 referências, 726, 727
21
CTS (Common Type System), unit BankServer_Impl.pas,
System.XML, 19
16 728, 729
objetivos, 2
Reference Types, 17 unit BankShared.pas, 729
acesso a serviços, 4
Value Types, 17 AppDomains, 718, 719
aplicações colaboradoras, 4
documentação, 4 arquitetura cliente/servidor, 714
desenvolvimento mais
FCL (Framework Class Library), arquitetura multicamada, 715,
rápido e mais fácil, 2, 3
17 716
distribuição transparente de
Ver também namespaces, 18 desenvolvimento, 717, 718
aplicações, 3
GC (Garbage Collector), 206, distribuição, 717, 718
P/Invoke, 419, 420, 431, 432,
207 escalabilidade, 716, 717
433, 434, 435
classe System.GC, 210, 211 segurança, 718
provedores de dados (data pro-
garbage collection tolerância a falhas, 716, 717
viders), 447, 448
geracional, 207, 209, 210 arquitetura pear-to-pear, 714,
Reflection API, 372
invocando, 210, 211 715
assemblies, 372, 373, 374,
raízes, 207 ativação de objeto, 722
375
mecanismos bloqueadores, 355 ativação por singleton, 722
invocação de membro, 380,
classe Interlocked, 358, 359 ativação single-call, 722
381, 382, 383, 385, 386
classe Monitor, 356, 357 client-activated, 723
metadados, 374
classe Mutex, 356 canais, 724
módulos, 375, 376, 377
classe ReaderWriterLock, CAOs (Client Activated Objects),
MSIL, emitindo por reflec-
357, 358 743
tion, 386, 387, 388, 390
classe WaitHandle, 355, 356 padrão factory, 743, 744, 745,
tipos, 377, 378
MSIL (Microsoft Intermediate 746, 747, 748, 749, 750,
utilitário Reflector, 374
Language), 14, 15, 16 751
vinculação tardia, 379, 380
namespaces, 18, 43, 45 problemas de, 751
Web Services, 685
cláusula de namespaces, 46 classe ChannelServices, 720
consumindo, 695, 696, 698,
convertendo, 46 classe RemotingConfiguration,
700, 701, 702, 703, 704
declarando, 43, 45 719, 720
criando, 686, 687, 688, 689,
namespaces padrão de codificação binária
691
projeto, 43 comparada com SOAP, 766,
exemplo, 687
System, 18 767, 768
invocando, 691
System.CodeDOM, 18 configurando, 765, 766
invocando assincronamente,
System.Collections, 18 COM-Interop, 712
704, 705
System.ComponentModel, configuração, 755
retornando dados a partir de,
18 configuração de cliente, 758,
692, 693
System.Configuration, 18 759
segurança, 705, 706, 708,
System.Data, 18 configuração de servidor, 757,
709
System.DirectoryServices, 18 758
SOAP (Simple Object Access
System.Drawing, 18, 19, 21 estrutura de arquivos XML,
Índice 913

755, 757 binária, 766, 767, 768 FileAttribute, 268


CORBA (Common Object Re- especificação, 743 NotifyFilters, valores, 281
quest Broker Architecture), estrutura de package, 740, NTLM, autenticação (Windows),
711 741 770, 772
DCOM (Distributed Compo- exceções, 742, 743 Null, tipos de variante, 66
nent Object Model), 712 respostas, 741, 742
definição, 710, 713 solicitações, 741 O
gerenciamento de tempo de soquetes, 710, 711
vida, 751, 752, 753, 754 sponsors, 723, 724 Object Inspector, 29
interface ILease, 752 TCP (Transmission Control Object Repository, 34, 35
interface ISponsor, 752 Protocol), 764, 765 Object, classe, 96
método visão geral arquitetônica, 718 objetos, 78
InitializeLifetimeService( ), XML-RPC, 711, 712 Application, 837
752, 753 .NET, componentes, 6 classe System.Object, 96
método Renewal( ), 753 .NET, objetivos, 2 COM objetos in .NET code
projeto de SponsorAndLease, acesso a serviços, 4 eventos COM, 404
753, 754 aplicações colaboradoras, 4 vinculação inicial com COM,
Java RMI, 711 desenvolvimento mais rápido e 400
leases, 723, 724 mais fácil, 2, 3 CORBA (Common Object Re-
leases, não conseguindo distribuição transparente de quest Broker Architecture),
renovar, 754, 755 aplicações, 3 711
Microsoft .NET Distributed Ap- DCOM (Distributed Component
NetworkStreams, 264
plications Object Model), 712
Integrating XML Web Services neutralidade de linguagem, 10
declarando, 95
and .NET Remoting, 768 New Items, caixa de diálogo,
definição, 94
objetos remotable, 720 291, 686
DeleteCommand, 476
context-bound, 721 NewEmployeeRow( ), método, destruindo, 95, 96
objetos bem conhecidos, 722 573 FClearSkyImage, 177
objetos Mar- NewRow( ), método, 495 FSaucer, 177
shal-By-Reference, 721 NextResult( ), método, 470 FSkyGraphics, 177
objetos Marshall-By-Value, FSkyImage, 177
nil, ponteiros, 76
721, 722 FStep, 177
projeto de template, 737 níveis
nível de isolamento, 563, 564 gerenciamento de tempo de
proxies, 724 vida, 751, 752, 753, 754
proxies, criando, 759, 761, 762, nível de isolamento, 563, 564
interface ILease, 752
764 nodefault, diretiva, 304 interface ISponsor, 752
arquivos DPR de cliente, 85, nomeando método
176, 267, 302, 343, 366, PIAs (Primary Interop Assem- InitializeLifetimeService( ),
367, 381, 481, 540, 547, blies), 407 752, 753
548, 549, 569, 645, 702, nomes método Renewal( ), 753
746, 750, 763, 764 nomes de método, projeto de SponsorAndLease,
arquivos DPR de servidor, reintroduzindo, 100 753
760, 761 projeto SponsorAndLease, 754
non-blittable, tipos, 415
classe TRemotingHelper, 70, InsertCommand, 475
85, 92, 96, 126, 127, 132, None, valor
instanciando, 95
173, 195, 277, 292, 363, (DataViewRowState), 508
Object Inspector, 29
473, 474, 489, 551, 553, None, valor (propriedade
Object Repository, 34, 35
561, 563, 606, 659, 759, DeleteRule), 491
objetos .NET no código COM,
761, 762 None, valor (propriedade 409
método UpdatedRowSource), 556 automação com vinculação
GetRemoteObjectRef( ), None, valor (propriedade tardia, 411, 412
759, 760 UpdateRule), 490, 491 CCWs (COM Callable
rastreamento de mensagem, None, valor (tipo enumerado Wrappers), 409
738, 739 CharSet), 422 empacotamento (marshaling),
RPC (Remote Procedure Call), 415, 416, 417, 418
711 Normal FileAttribute, 268
Normal, prioridade de thread, implementação de interface,
SAOs (Server Activated
347 413, 415
Objects), 743
Not (binário), operador, 58 Interop Type Libraries, 412,
SOAP (Simple Object Access
Protocol), 712, 713 NOT, operador, 506 413
NotContentIndexed registro de assembly, 409, 410
comparado com codificação
914 Índice

tipos de parâmetro, 415, 416, OleDbParameter, classe, 462 atualizações, 555, 557
417, 418 evento RowUpdated, 557
OleDbType, tipo enumerado,
tratamento de erro, 418 instrução INSERT, 555
464
objetos bem conhecidos, 722 instrução UPDATE, 556
objetos COM no código .NET, OnDeserialization( ), método,
valores UpdatedRowSource,
393 231
555, 556
automação com vinculação OnJump( ), método, 305, 306 classe SQLCommand, 531, 532,
tardia, 393, 394, 395 OnLoad, hander de evento 534, 535, 536, 537, 539
COM com vinculação inicial, aplicação BankExample, 734 instrução DELETE, 537, 538,
397, 398, 399 OnPaint( ), método, 330, 337 539
controle de tempo de vida OnRemoveCallback, instrução INSERT, 533, 534
COM, 405 propriedade (classe Cache), instrução UPDATE, 535, 536,
eventos COM, 403, 405 818 537
Interop Assemblies, 400, listagem de código de
OnRowUpdated( ), handler de
401, 402, 403, 408, 409 exemplo, 531, 532
evento, 553
parâmetros de referência, método SubmitAddedRow( ),
396, 397 OOP (object-oriented program- 533, 534
parâmetros de valor, 396, ming), 93, 94 método SubmitUpdates( ),
397 Opaque ControlStyle, valor de 532
parâmetros opcionais, 396, tipo enumerado, 336 classe SQLCommandBuilder,
397 Opcodes (códigos de operação), 528, 529, 530
PIAs (Primary Interop Assem- 388 limitações, 529
blies), 406, 407, 409 Open Database Connectivity. listagem de código de
RCWs (Runtime Callable Ver ODBC, 448 exemplo, 530
Wrappers), 402 classe SQLDataAdapter, 539,
Open file mode, 272
tratamento de erro, 406 540, 541, 543, 544
Open Group, site Web, 711 exemplo do bloco principal,
objetos remotable, 720
ativação, 722, 723 Open( ), método, 450, 452, 580 539
context-bound, 721 OpenOrCreate file mode, 272 método GetDataAdapterDele-
objetos bem conhecidos, 722 operadores, 55 te Command( ), 543, 544
objetos Mar- aritméticos, 56, 57 método
shal-By-Reference, 721 as, 80 GetDataAdapterInsertCom-
objetos Marshall-By-Value, decremento, 58, 59 mand( ), 541
721, 722 endereço (@), 73 método
raízes, 207 div, 57 GetDataAdapterUpdate-
SelectCommand, 475, 479 fazer e atribuir, 59 Command( ), 541, 542
Session, 831 in, 72 questões de concorrência, 551,
SqlCommand incremento, 58, 59 552, 553, 554, 555
criando, 459 operador LIKE, 507, 508 handler de evento
UpdateCommand, 476 operadores aritméticos, 56, 57 OnRowUpdated( ), 553
OCI (Oracle Call Interface), 448 operadores de atribuição, 55 listagem de código de
operadores de bitwise, 58 exemplo, 552
ODBC (Open Database Connec-
operadores de comparação, 55, SQLCommandBuilder, 531
tivity), 448
56, 506, 507 stored procedure, 544, 545, 546,
strings de conexão, 451
operadores lógicos, 56, 57, 506 547, 548, 549, 550, 551
OdbcConnection, objeto exemplo SqlDataAdapter, 544
operadores não-lógicos, 56
propriedade ConnectionString, método
operadores matemáticos, 57
451, 452 GetDataAdapterDelete-
sobrecarregando, 109
Off, modo (estado de sessão), Command( ), 550, 551
Operator, propriedade (classe
798 método
CompareValidator), 620
Offline FileAttribute, 268 GetDataAdapterInsertCom-
Or (binário), operador, 58
Offset, parâmetro (método mand( ), 546
OR, operador, 506 método
BeginRead( )), 278
or, operador lógico, 56 GetDataAdapterUpdate-
Ole Db
strings de conexão, 451 Oracle, 448 Command( ), 548
conexões Original DataRowVersion, 497
OLE DB, 448
strings de conexão, 452 OriginalRows, valor
OleCheck( ), método, 430
Oracle Call Interface (OCI), 448 (DataViewRowState), 508
OleDbConnection, objeto origens de dados, atualizando,
propriedade ConnectionString, out, parâmetros, 87, 88
528
451 OutputParameters, valor
atualizando dados após
Índice 915

(propriedade páginas individuais, classe BaseValidator, 618, 619


UpdatedRowSource), 556 autenticando, 774, 775, 776 classe CompareValidator, 620,
@OutputCache, diretiva, 802, páginas Web, construindo 621
803 WebForms com ASP.NET classe CustomValidator, 624
overload, instrução, 50, 99 adicionando controles classe RangeValidator, 623
dinamicamente, 639, 641 classe
override, instrução, 99
arquivos, carregando clientes, RegularExpressionValidator,
635, 636 621, 623
P controles de lista, classe RequiredFieldValidator,
pré-preenchendo, 616, 617, 619, 620
P/Invoke, 419, 420, 431, 432,
618 classe ValidationSummary,
433, 434, 435
handler de evento OnInit, 625
(Invocation Platform Service),
617, 618 validação do lado do cliente,
418
método 618
package, instrução, 123, 127 validação do lado do servidor,
PopulateDdlFromFile( ),
packages 618
617
arquivo package de exemplo, visualizador de imagens baseado
controles de servidor, 610, 611
125, 127 em miniaturas, 639, 641
controles de HTML, 611
atributos assembly, 127 WebForms, 611
controles de servidor Web,
cláusula contains, 127 palavras-chave
611
cláusula requires, 127 end, 39
controles de usuário, 611
comparados com bibliotecas, implementation, 40, 41
controles de validação, 501,
133, 134 inicialization, 41
611, 618, 619, 620, 621,
definição, 93 interface, 40
623, 624, 625
diretiva package, 127 namespaces, 46
controles vinculados a da-
instalando, 135 threadvar, 360
dos, 611
packages (HTTP), 592 unit, 40
propriedades fortemente
packages (SOAP), 740, 741 uses, 39, 42
tipificadas WebControl,
packages de resposta, 593, 594 var, 39
300, 571, 572, 573, 625,
packages de solicitação, 592, instruções, 66
627
593 PAnsiChar, tipo, 76
criando, 613
packages do Mono, 189
eventos Load, 613, 614 papéis, autorização baseada em,
packages do Red Carpet, 187
formatando, 625 783, 784, 785
projeto de package de teste
classe de estilo, 628 Parameter Collection Editor, 587
D8DG.PkgUnit, 130
CSS (Cascading Style Sheets),
projeto package de teste Parameter, classe, 446
627, 628
D8DG.PkgUnit, 129 ParameterBuilder, classe, 387
propriedades fortemente
D8DG.TestPkg, 128 ParameterModifier, estrutura,
tipificadas WebControl,
tipos de arquivo, 128 396
300, 571, 572, 573, 625,
PadLeft( ), método, 250 627 Parameters, propriedade
padrão de projeto, namespaces, imagens, 638, 639 (interface IDbCommand), 457
43 layout de página, 612 parâmetros
classe BdpParameter, 584, 585
PadRight( ), método, 250 navegando entre WebForms,
classe BdpParameterCollection,
Page, classe 629
584, 585
propriedade IsPostBack, 608, método HttpResponse.Redi-
COM (Component Object
609 rect( ), 630
Model)
Page, propriedade (classe método POST, 629
parâmetros de referência, 396,
BaseValidator), 619 método Server.Transfer( ),
397
Page_Load( ), handler de 631
parâmetros de valor, 396, 397
evento, 783 variáveis de sessão, 632
parâmetros opcionais, 396,
pageBaseType, atributos (tag ordem de processamento de
<pages>), 798 397
evento, 616
pageOutput, atributo (tag derivando, 466, 467
painéis, 633, 635
<trace>), 808 especificando com
respostas de correio eletrônico,
PagerStyle (controle DataGrid), IDbParameter, 462, 464
enviando, 636, 637, 638
668 especificando com
salvando informações a partir
paginação IDbParameter, 462
de, 614, 615, 616
controle DataGrid, 72, 239, funções, 50
simulação de múltiplos
240, 241, 343, 369, 370, 384, tipos de parâmetro, 415, 416,
formulários, 633, 635
668, 669, 670, 671 417, 418
validação, 618
916 Índice

parâmetros com valor padrão 795 portabilidade


(funções), 50 PATH, variável de ambiente, aplicações Mono ASP.NET, 198,
402 199
parâmetros constantes, 88
PathGradientBrush, classe, 144
parâmetros de array aberto, 88, portas
PChar, tipo, 76
89 PE, cabeçalhos, 12 XSP, 198
parâmetros de referência PeakMemoryUsed, propriedade Position, barra de ferramentas,
(classe ProcessInfo), 803 27
(COM), 396, 397
Peek( ), método, 221, 224, 274 PositionChanged event, 520
parâmetros de valor (COM), 396 POST, método, 593, 629, 630
Pen, classe, 142, 143
personalizando postback, eventos
parâmetros identificados, 421 passando, 608, 609
Interop Assemblies, 408, 409
parâmetros implícitos, 306 menu File, 28 Pred( ), função, 59
parâmetros opcionais (COM), PIAs (Primary Interop Assem- preenchendo
396, 397 blies), 408, 409 DataSets, 478, 479
parâmetros runtime personificação, 785, 786 DataTables, 479
XSP, 197 pesquisando strings, 250
diretório-raiz da aplicação, DataTables Prelink( ), método, 432, 435
197, 198 método Find( ), 497, 498 PrelinkAll( ), método, 432
diretório-raiz Web, 197 método Select( ), 498, 499 Prepare( ), método, 457
porta e endereço, 198 DataViews, 512 pré-preenchendo controles de
parâmetros variáveis, 87 PIAs (Primary Interop Assem- lista, 616, 617, 618
parâmetros, passando para blies), 406, 407, 409 handler de evento OnInit, 617,
funções/procedures, 87 criando, 406 618
parâmetros com valor, 87 nomeando, 407 método PopulateDdlFromFile( ),
parâmetros constantes, 88 personalizando, 408, 409 617
parâmetros de array aberto, 88, registrando, 407 pré-processadores, 55
89 PInvoke (Platform Invoke), 361 PreserveSigAttribute,
parâmetros out, 87, 88
Planets, componente, 301, 302, propriedade, 419
parâmetros por referência, 87
303 Primary Interop Assemblies
parâmetros por valor, 87
parâmetros variáveis, 87 Platform Invoke (PInvoke), 361 (PIAs), 406, 407, 409
PlayingCard, controle, 328 criando, 406
parênteses ( ), 50
declaração de classe, 329, 330 nomeando, 407
particionando
listagem de código, 331 personalizando, 408, 409
assemblies, 125
método InitComp( ), 335 registrando, 407
.pas, arquivos (DataSets método OnPaint( ), 337 PrintItems( ), método, 237
fortemente tipificados), 307, método SetStyle( ), 335 prioridades
568, 570, 574, 576 valores do tipo enumerado threads, 347, 348
definições de DataRow, 360, ControlStyles, 335, 336
574, 575, 576 Priority, propriedade (classe
polígonos, desenhando, 154 Cache), 818
definições de DataTable, 571
listagem de código de exemplo, ponteiros, 74, 75, 76 Priority, propriedade (classe
307, 568, 570 alocação de memória, 75 MailMessage), 637
propriedades/métodos os de declarando, 74
private, instrução, 106
DataTable, 573, 574 desreferenciando, 75
ponteiros nulos, 76 procedures
.pas, extensão de arquivo, 128, Ver também métodos, 50
ponteiros tipificados, 74
600 definição, 50, 85
ponteiros tipificados, 74
<passport>, seção (arquivo exemplo, 86
web.config), 796 ponto (.), 42, 44, 256 parâmetros com valor padrão,
Passport SDK, 780 ponto de interrogação (?), 782 50
ponto-e-vírgula (;), 256 parênteses ( ), 50
Passport, autenticação, 780, 781
pontos de salvamento, 564, 565 passando parâmetros para, 87
PassportAuthenticationModule,
pools parâmetros constantes, 88
780 parâmetros de array aberto,
password, atributo (tag conexões, 455
88, 89
<processModel>), 801 pools de thread, 349, 351
parâmetros out, 87, 88
Path, classe, 264 Pop( ), método, 221
parâmetros por referência, 87
path, atributo (seção <forms> de PopulateDdlFromFile( ), parâmetros por valor, 87
web.config), 774 método, 617 parâmetros variáveis, 87
path, atributo (tag <location>), porcentagem, sinal (%), 256 <processModel>, seção (ASP.NET
Índice 917

web.config), 800, 801, 802 PropertyBuilder, classe, 387 845


processamento de transação, Proposed DataRowVersion, 497 buffer (tag <pages>), 798
558, 559 Capacity (ArrayList collection),
AbsoluteExpiration,
classe SqlTransaction, 558 227
propriedade (classe Cache),
DataAdapters, 562 Capacity (classe StringBuilder),
818
exemplo simples, 559, 562 251
propriedades, 101, 102, 691, Caption (DataColumns), 488
leituras de transação, 563, 564
692, 694, 695 Category (controles Web), 851
método BeginTransaction( ),
558 AbsoluteExpiration (classe Cc (classe MailMessage), 637
método Commit( ), 558, 559 Cache), 818 Chars (classe StringBuilder), 251
método Rollback( ), 558, 559 AcceptRejectRule classe Cache, 817, 818
nível de isolamento, 563, 564 (ForeignKeyConstraint), 490 clientConnectedCheck (tag
pontos de salvamento, 564, AccessKey (classe WebControl), <processModel>), 801
565 626 CodeBehind (ASP.NET), 598
transações aninhadas, 565, 566 Age (classe ProcessInfo), 803 CodeBehind (tag <@ Control
ProcessExit, evento, 42 AllowDBNull (DataColumns), %>, 844
ProcessID, propriedade (classe 488 comAuthenticationLevel (tag
ProcessInfo), 803 AllowDefaultSort (classe <processModel>), 801
DataView), 501 comImpersonationLevel (tag
ProcessInfo, classe, 803, 804
AllowDelete (classe DataView), <processModel>), 801
Processing Transaction, caixa 501
de diálogo, 189 CommandText (interface
AllowEdit (classe DataView), IDbCommand), 457
processo de descoberta (Web 501 CommandTimeout (interface
Services), 696 AllowNew (classe DataView), IDbCommand), 449, 457
processos, 338, 339 501 CommandType (interface
dados do processo global, 338 allowOverride (tag <location>), IDbCommand), 457
IPC (Interprocess Communica- 795 Connection (interface
tions), 339 AlternatingItemStyle (controle IDbCommand), 457
processos de um único thread, DataList), 662 ConnectionString, 449, 450
340 Attachments (classe classe OdbcConnection, 451,
processos pesados, 339 MailMessage), 637 452
processos trabalhadores, Attributes (classe classe OleDbConnection, 451
reiniciando, 800, 801, 802 FileSystemInfo), 268 classe SqlConnection, 450,
programação orientada a AutoEventWireup (ASP.NET), 451
objetos. Ver OOP, 93 598 ConnectionString (classe
programas AutoEventWireup (tag <@ Con- BdpConnection), 580, 581
esqueleto de arquivos de trol %>), 844 ConnectionTimeout (interface
programa, 39 AutoEventWireup (tag IDbConnection), 449
módulos principais, 38 <pages>), 798 controles WinForms, 293
referências circulares de unit, AutoIncrement implementação de
42, 43 (DataColumns), 488 TypeConverter, 298, 300
sintaxe da cláusula uses, 39, 42 AutoIncrementSeed propriedades de array, 300,
units (DataColumns), 488 302, 303, 304
aliases, 46, 47 AutoIncrementStep propriedades de objeto, 296,
cabeçalhos, 40 (DataColumns), 488 297, 298, 300
exemplo MyUnit.pas, 39, 40 BackColor (classe WebControl), propriedades de tipo
units genéricas, 45 626 enumerado, 294, 295
Bcc (classe MailMessage), 637 propriedades simples, 293,
Project Group, 33
Bindable (controles Web), 851 294
Project Manager, 33 Body (classe MailMessage), 637 valores default, 303
projeto BodyEncoding (classe ControlToCompare (classe
ADO.NET, 443 MailMessage), 637 CompareValidator), 620
arquitetura de dados BodyFormat (classe ControlToValidate (classe
desconectados, 443, 444 MailMessage), 637 BaseValidator), 619
beneficiando-se de BorderColor (classe Count
tecnologias existentes, 444 WebControl), 626 interface ICollection, 219
integração XML, 444 BorderStyle (classe Count (classe Cache), 817
integrados em .NET Frame- WebControl), 626 Count (DataTable), 573
work, 444 BorderWidth (classe Count (classe DataView), 501
representação comum de da- WebControl), 626 cpuMask (tag <processModel>),
dos, 444 TagName (tag<@ Register %), 801
918 Índice

CreationTime (classe EnableKernelModeCache (tag Item


FileSystemInfo), 268 <httpRuntime>), 797 interface ICollection, 219
CreationTimeUtc (classe enableSessionState (<pages> interface IDictionary, 220
FileSystemInfo), 268 tag), 798 Item (classe Cache), 817
CssClass (classe WebControl), enableViewState (<pages> tag), Item (DataTable), 573
626 798 Item (classe DataView), 502
Current enableViewStateMac (tag Items (controle DataList), 663
interface IEnumerator, 220 <pages>), 798 Items (controle Repeater), 658
DataMember (ListControl), 648 ErrorMessage (classe Items (ListControl), 648
DataMember (controle Re- BaseValidator), 619 ItemStyle (controle DataList),
peater), 658 ExecutionTimeout (tag 663
DataSet (DataViewManager), <httpRuntime>), 797 Key (classe Cache), 818
502 Exists (classe FileSystemInfo), Keys
DataSource (controle Repeater), 268 IDictionary interface, 220
658 Extension (classe language (ASP.NET), 598
DataSource (ListControl), 648 FileSystemInfo), 268 Language (tag <@ Control %>),
DataTextField (ListControl), Font (classe WebControl), 626 844
648 FooterStyle (controle DataList), LastAccessTime (classe
DataTextFormatString 662 FileSystemInfo), 268
(ListControl), 648 ForeColor (classe WebControl), LastAccessTimeUtc (classe
DataType (DataColumns), 488 626 FileSystemInfo), 269
DataValueField (ListControl), From (classe MailMessage), 637 LastWriteTime (classe
648 FullName (classe Assembly), FileSystemInfo), 269
DataViewManager (classe 374 LastWriteTimeUtc (classe
DataView), 501 FullName (classe FileSystemInfo), 269
DataViewRowState (classe FileSystemInfo), 268 Length (classe StringBuilder),
DataView), 508 FullyQualifiedName (classe 251
DataViewSettings Module), 376 LineJoin (classe Pen), 148
(DataViewManager), 502 GlobalAssemblyCache (classe localOnly (tag <trace>), 808
Debug (ASP.NET), 598 Assembly), 374 Location (classe Assembly), 374
DefaultValue (controles Web), GridLines (controle DataList), logLevel (tag <processModel>),
851 663 801
DefaultValue (DataColumns), Headers (classe MailMessage), MarshalAsAttribute, 416
488 637 MaxCapacity (classe
definição, 94 HeaderStyle (controle DataList), StringBuilder), 251
DeleteRule 663 maxIoThreads (tag
(ForeignKeyConstraint), 491 HeaderText (classe <processModel>), 801
Dependencies (classe Cache), ValidationSummary), 625 MaxLength (DataColumns), 488
818 Height (classe WebControl), MaxRequestLength (tag
Description (controles Web), 626 <httpRuntime>), 797
851 Herda (ASP.NET), 598 maxWorkerThreads (tag
Display (classe BaseValidator), IdleTimeOut (tag <processModel>), 801
619 <httpRuntime>), 797 memoryLimit (tag
DisplayMode (classe idleTimeout (tag <processModel>), 801
ValidationSummary), 625 <processModel>), 801 minFreeLocalRequestFreeThread
DllImportAttribute, 421 Inherits (tag <@ Control %>), s (tag <httpRuntime>), 797
EditItemIndex (controle 844 minFreeThreads (tag
DataList), 662 InvokeRequired (classe Con- <httpRuntime>), 797
EditItemStyle (controle trol), 363, 364 modo (tag <sessionState>), 798
DataList), 662 IsAuthenticated (classe Name (classe FileSystemInfo),
Enable (tag <httpRuntime>), FormsIdentity), 774 269
797 IsFixedSize OnRemoveCallback (classe
enable (tag <processModel>), interface ICollection, 219220 Cache), 818
801 interface IDictionary, 219 Operator (classe
EnableClientScript (classe IsPostBack (classe Page), 608, CompareValidator), 620
BaseValidator), 619 609 Page (classe BaseValidator), 619
EnableClientScript (classe IsReadyOnly pageBaseType (tag <pages>), 798
ValidationSummary), 625 IsSynchronized pageOutput (tag <trace>), 808
Enabled (classe BaseValidator), interface ICollection, 219 Parameters (interface
619 IsValid (classe BaseValidator), IDbCommand), 457
enabled (tag <trace>), 808 619 password (tag <processModel>),
Índice 919

801 SlidingExperation (classe adicionando a controles


path (tag <location>), 795 Cache), 818 WinForms, 300, 302, 303
PeakMemoryUsed (classe smartNavigation (tag <pages>), propriedades de objeto
ProcessInfo), 803 798 adicionando a controles
ppRequestQueueLimit (tag Sort (classe DataView), 502 WinForms, 296, 297, 298, 300
<httpRuntime>), 797 Src (tag <@ Register %>), 845 propriedades de tipo enumerado
PreserveSigAttribute, 419 StartTime (classe ProcessInfo), adicionando a controles
Priority (Cache), 818 803 WinForms, 294, 295
Priority (MailMessage), 637 State (interface propriedades simples
ProcessID (classe ProcessInfo), IDbConnection), 449 adicionando a controles
803 Status (classe ProcessInfo), 803 WinForms, 293, 294
propriedades acessoras, 94 Style (classe WebControl), 626
protected, instrução, 106
propriedades de evento, 305, Subject (classe MailMessage),
306, 309, 310, 311 637 protection, atributo (seção
ReadOnly (DataColumns), 488 SyncRoot <forms> de web.config), 774
RepeatColumns (controle interface ICollection, 219 protocolos, 339
DataList), 663 TabIndex (classe WebControl), COM-Interop, 712
RepeatDirection (controle 626 CORBA (Common Object Re-
DataList), 663 Table (classe DataView), 502 quest Broker Architecture),
RepeatLayout (controle TagPrefix (tag <@ Register %>), 711
DataList), 663 845 DCOM (Distributed Component
RequestCount (classe Text (classe BaseValidator), 619 Object Model), 712
ProcessInfo), 803 timeout (tag <processModel>), HTTP (Hypertext Transfer Proto-
requestLimit (tag 801 col), 592
<processModel>), 801 To (classe MailMessage), 637 classe HTTPCookie, 607
requestLimit (tag <trace>), 808 ToolTip (classe WebControl), classe HTTPRequest, 605
requestQueueLimit (tag 626 classe HTTPResponse, 601,
<processModel>), 801 traceMode (tag <trace>), 808 604, 605
responseDeadlockInterval (tag Transaction (interface respostas, 593, 594
<processModel>), 801 IDbCommand), 457 RFC 2616, 592
solicitações, 592, 593
RowFilter (classe DataView), Type (classe
Java RMI, 711
504 CompareValidator), 620
protocolos stateless (sem
RowFilter (classe DataView), Unique (DataColumns), 488
estado), 592
502 UpdatedRowSource (classe
RPC (Remote Procedure Call),
RowStateFilter (classe SqlCommand), 555
711
DataView), 509, 510 UpdateRowSource (interface
SOAP (Simple Object Access Pro-
RowStateFilter (classe IDbCommand), 457
tocol), 8, 685, 712, 713
DataView), 502 UpdateRule
comparado com codificação
SelectCommand (classe (ForeignKeyConstraint), 491
binária, 766, 767, 768
BdpDataAdapter), 583, 584 useFullyQualifiedRedirectUrl
especificação, 743
SelectedIndex (controle (tag <httpRuntime>), 797
estrutura de package, 740, 741
DataList), 663 userControlBaseType (tag
exceções, 742, 743
SelectedIndex (ListControl), <pages>), 798
respostas, 741, 742
648 username (tag
solicitações, 741
SelectedItem (controle <processModel>), 801
TCP (Transmission Control Pro-
DataList), 663 validateRequest (tag <pages>),
tocol), 764, 765
SelectedItem (ListControl), 648 798
UDDI (Universal Description,
SelectedItemStyle (controle Value (classe Cache), 818
Discovery, and Integration),
DataList), 663 Values
8, 686
SelectedValue (ListControl), interface IDictionary, 220
WSDL (Web Service Description
648 ValueToCompare (classe
Language), 686
SeparatorStyle (controle CompareValidator), 620, 621
XML-RPC, 711, 712
DataList), 663 versionHeader (tag
ShowMessageBox (classe <httpRuntime>), 797 provedores. Ver provedores de
ValidationSummary), 625 webGarden (tag dados (data providers), 447,
ShowSummary (classe <processModel>), 801 448
ValidationSummary), 625 Width (classe WebControl), provedores gerenciados. Ver
ShutdownReason (classe 626 provedores de dados (data pro-
ProcessInfo), 803 propriedades acessoras, 94 viders), 447
shutdownTimeout (tag propriedades de array ProvidePropertyAttribute, classe,
<processModel>), 801
920 Índice

324, 325 Rammer, Ingo, 768 instalação do Mono, 187, 188,


proxies, 724 RangeValidator, classe, 623, 624 189
proxies reais, 724 packages, 187
rastreando, 804
proxies transparentes, 724 mensagens, 738, 739 red-carpet-{version}.{arch}.rpm,
proxies, criando, 759, 761, 762, no nível da aplicação, 807, 808 package, 187
764 no nível da página, 804, 806, redirecionamento
arquivos DPR de cliente, 85, 807 redirecionamento de erro
176, 267, 302, 343, 366, 367, rcd, comando, 188 (ASP.NET), 799, 800
381, 481, 540, 547, 548, 549, redirecionando navegadores,
rcd-{version}.{arch}.rpm, pack-
569, 645, 702, 746, 750, 763, 605
age, 187
764 Redirect( ), método, 630
arquivos DPR de servidor, 760, RCWs (Runtime Callable Wrap-
RedirectFormLoginPage( ),
761 pers), 402
método, 775, 776
classe TRemotingHelper, 70, Read( ), método, 274, 468, 469 Reference Types, 17
85, 92, 96, 126, 127, 132, Read, acesso a arquivo, 273 ReferenceEquals( ), método, 246
173, 195, 277, 292, 363, 473, ReadBlock( ), método, 274 referência, passando parâmetros
474, 489, 551, 553, 561, 563, por, 87
ReadChars( ), método, 276
606, 659, 759, 761, 762 referenciando
ReadCommitted, nível de assemblies, 134
método
isolamento, 564
GetRemoteObjectRef( ), 759, referências
760 ReaderWriterLock, classe, 357, aplicação BankExample, 726,
358 727
public, instrução, 106
ReadLine( ), função, 554 circulares de unit, 42, 43, 92, 93
published, instrução, 106
ReadLine( ), método, 274 referências de classe, 100, 101
Pulse( ), método, 357
ReadOnly FileAttribute, 268 ReflectAssembly( ), método, 374,
Push( ), método, 221
ReadOnly( ), método, 228 376
PwideChar, tipo, 76
ReadOnly, propriedade Reflection (API), 372
(DataColumns), 488 assemblies, 372, 374, 375
Q ReadToEnd( ), método, 274 listagem de código de
exemplo, 373
QueryPerformanceCounter, 419 ReadUncommitted, nível de
método
QueryPerformanceFrequency, isolamento, 564 GetCallingAssembly( ), 374
419 ReadWrite, acesso a arquivo, método
Queue, coleção, 223 273 GetCustomAttributes( ),
construtor, 224 ReBind( ), método, 647 374
exemplo, 223, 226 recolhendo blocos de código, 30 método GetModules( ), 374
método
métodos, 221, 224 recortando GetReferencedAssemblies( ),
QueueUserWorkItem( ), strings, 160 374
método, 349 regiões, 160, 161 método GetTypes( ), 374
Rectangle, classe, 152, 153 método ReflectAssembly( ),
recuando 374
R propriedade FullName, 374
blocos de código, 32
RadioButtonList, controle, 655, propriedade
recursos
656, 657 GlobalAssemblyCache, 374
liberando
vinculando a arrays, 648, 653, propriedade Location, 374
método Finalize( ), 211, 212,
655, 656 invocação de membro
213, 214
vinculando a tabelas de banco listagem de código de
método Free( ), 211
de dados, 653, 656, 657 exemplo InvMemb, 383
questões de desempenho,
RaiseLastOSError( ) método, 427 listagem de código de
216
exemplo InvProject, 380,
RaiseLastOSError( ), método, gerenciados, 206
381
427 não-gerenciados, 206
método
RaiseLastWin32Error( ), recursos não-gerenciados, 206 CreateInstanceFrom( ), 381
método, 427 liberando método GetConstructor( ),
RaisePostBackEvent( ), método, método Finalize( ), 211, 212, 383
857 213, 214 método GetEvent( ), 383
RaisePostDataChangedEvent( ), método Free( ), 211 método GetField( ), 383
questões de desempenho, método GetMethod( ), 382,
método, 860
216
raízes, 207 383
Red Carpet, 187 método GetProperty( ), 383
Índice 921

método InvokeMember( ), blies), 407 eventos, 658


381, 382 RegistrationServices, classe, 409 exemplo, 658
método InvokeType( ), 381 registros, 51, 69, 70 propriedades, 658
método WriteMessage( ), 381 registros de variantes, 77 saída, 230, 661
metadados, 374 templates, 657, 658
RegularExpressionValidator,
módulos, 375, 376, 377 RepeatLayout, propriedade
classe, 621, 623
MSIL, emitindo por reflection, (controle DataList), 663
386, 387 reiniciando
processos trabalhadores, 800, Replace( ), método, 246, 249, 251
aplicação de exemplo, 388,
801, 802 representação comum de dados,
390
reintroduce, instrução, 100 444
classe AssemblyBuilder, 387
classe ConstructorBuilder, reintroduzindo nomes de RequestCount, propriedade
387 método, 100 (classe ProcessInfo), 803
classe ReleaseComObject( ), método, requestLimit, atributo (tag
CustomAttributeBuilder, 405 <processModel>), 801
387 requestLimit, atributo (tag
ReleaseMutex( ), método, 356
classe EnumBuilder, 387 <trace>), 808
Remote Procedure Call (RPC),
classe EventBuilder, 387
711 requestQueueLimit, atributo (tag
classe FieldBuilder, 387
RemoteAt( ), método, 486 <processModel>), 801
classe ILGenerator, 388
RemotingConfiguration, classe, RequiredFieldValidator, classe,
classe LocalBuilder, 387
719, 720 619, 620
classe MethodBuilder, 387
Remove( ), método, 219, 220,
classe ModuleBuilder, 387 requires, instrução, 127
249, 251, 486, 818, 837
classe ParameterBuilder, 387 requireSSL, atributo (seção
RemoveAll( ), método, 837
classe PropertyBuilder, 387 <forms> de web.config), 774
RemoveAt( ), método, 219, 486,
classe TypeBuilder, 387
520 Reset( ), método, 220, 360
namespace
RemoveEmployeeRow( ), ResetAbort( ), método, 368
System.Reflection.Emit,
método, 573
386, 387 ResizeRedraw ControlStyle, valor
removendo. Ver excluindo, 486
processo de emissão, 388 do tipo enumerado, 336
RemoveRange( ), método, 228
tipos, 377, 378 resolução
RemoveTableNameRow( )
utilitário Reflector, 374 imagens, 163, 164, 165
método (TableName), 577
vinculação tardia, 379 Renamed event, 281 Resourcer, 314
primeiro exemplo de assem- Render( ), método, 853 resourcestring, instrução, 80
bly, 379 RenderContents( ), método,
segundo exemplo de assem- responseDeadlockInterval,
853, 855
bly, 379, 380 renderização personalizada, 853 atributo (tag <processModel>),
Reflector, 404 método Render( ), 853 801
Reflector, utilitário, 374 método RenderContents( ), respostas (HTTP), 593, 594
853, 855 classe HTTPResponse, 601, 604,
ReflectType( ), método, 378
renderizando 605
RefParam, variável, 397 redirecionamento de
controles Web, 853
Refresh, botão (aplicação navegador, 605
método Render( ), 853
BankExample), 735 saída, filtrando, 603, 604
método RenderContents( ),
Refresh( ), método, 520 853, 855 texto, gravando em clientes,
Refresh, botão (aplicação imagens de bancos de dados, 602
BankExample), 734 666 respostas (SOAP), 741, 742
RefreshNode( ), método, 323 Renewal( ), método, 753 restrições, 489
RefreshSchema( ), método, 529 ReparsePoint FileAttribute, 268 classe ForeignKeyConstraint,
regasm.exe, 407 490, 491
Repeat( ), método, 228
classe UniqueConstraint, 489
regasm.exe, ferramenta, 409 repeat..until, loop, 84 recuperando de origens de da-
regerando exceções, 120 RepeatableRead, nível de dos, 491, 492
regiões isolamento, 564 Result, variável, 86
listagem de código de exemplo, RepeatColumns, propriedade resultados de consultas
157, 159 (controle DataList), 663 mapeando, 480, 482, 483
recortando, 160, 161 RepeatDirection, propriedade recuperando com DataAdapters,
Regions, 31 (controle DataList), 663 478
registrando Repeater, controle, 657, 658, consultas parametrizadas, 480
assemblies .NET, 409, 410 659, 660, 661 DataSets, preenchendo, 478,
PIAs (Primary Interop Assem- declaração, 659 479
922 Índice

DataTables, preenchendo, saída autorização de URL, 782, 783


479 filtrando, 603, 604 definição, 769
resultsets salvando personificação, 785, 786
múltiplos resultsets, informações de WebForm, 614, Web Services, 705, 706, 708, 709
consultando, 469, 470 615, 616 SEH (Structured Exception Han-
resultsets únicos, consultando, SAOs (Server Activated Objects), dling). Ver exceções, 114
468, 469 743 Select( ), método, 498, 499, 778
Resume( ), método, 343 SAPI (Microsoft Speech API), Select, consultas
ResumeBinding( ), método, 520 398 stored procedure SelectProduct,
retângulos eventos COM, 403 545, 546
desenhando, 21, 152, 153 fazendo download, 398 Selectable ControlStyle, valor do
Interop Assemblies, 401 tipo enumerado, 336
Retrieve, botão (aplicação
listagem de código de exemplo, SelectCommand, objeto, 475, 479
BankExample), 735
399 SelectCommand, propriedade
Retrocompatibilidade, 55
Save( ), método, 565 (classe BdpDataAdapter), 583,
Reverse( ), método, 228 584
SaveUserInfo( ), método, 614,
RFC 2616, 592 615, 616, 683, 684 SelectedIndex, propriedade
Roeder, Lutz, 289, 314, 374, 404 (controle DataList), 663
ScaleTransform( ), método, 168
Rollback( ), método, 558, 559, SelectedIndex, propriedade
SDKVARS.bat, arquivo, 402 (ListControl), 648
565
seções SelectedItem, propriedade
RollBack( ), método, 586 <appSettings> (ASP.NET (controle DataList), 663
rotacionando web.config), 809 SelectedItem, propriedade
imagens, 167, 168 <authorization> (ASP.NET (ListControl), 648
RotateFlip( ), método, 167 web.config), 796, 797 SelectedItemStyle (controle
<configuration> (ASP.NET DataGrid), 668
RotateFlipType, tipo
web.config), 809 SelectedItemStyle, propriedade
enumerado, 167, 168
(controle DataList), 663
RotateTransform( ), método, <deny> (arquivo web.config),
SelectedItemTemplate, template,
168, 170, 171 797
662
implementation (units), 41
rotinas. Ver funções, proce- SelectedValue, propriedade
interface (units), 40, 41 (ListControl), 648
dures, 50
seções de configuração SelectProduct, stored procedure,
round tripping, 408, 409 personalizadas (ASP.NET 545, 546
Round( ), função, 80 web.config), 809, 810 Self, variável, 100
RowFilter, propriedade (classe Security, valor (tipo enumerado SendEMail( ), método, 637, 683
DataView), 502,504 NotifyFilters), 281 senhas
RowState, valores, 496, 499 Seek( ), método, 273 Web Services, 705, 706
RowStateFilter, propriedade SeekOrigin, valores, 273 separador de data (/), 261
(classe DataView), 502, 509, segurança separador de hora (:), 261
510 arquitetura multicamada, 718 SeparatorStyle, propriedade
RowUpdating, evento, 529 autenticação, 770 (controle DataList), 663
RPC (Remote Procedure Call), autenticação baseada em SeparatorTemplate, template,
711 formulário, 772, 773, 775, 658
rpm, comando, 188 776, 778, 780
RTTI (Runtime Type Serializable, nível de isolamento,
autenticação Passport, 780, 564
Information), 372
781
rug-{version}.{arch}.rpm, serialização, 281, 282
package, 187 autenticação Windows, 770,
exemplo, 283, 285, 286
Run Without Debugging, 771, 772
formatadores, 283
comando (menu Run), 689 configurando, 770
gráficos de objeto, 283
Run, comandos de menu definição, 769
interface ISerializable, 282, 283
Run Without Debugging, 689 Web Services, 82, 83, 85,
atributo , 282
Running, estado de thread, 348 108, 117, 120, 190, 191,
Serialize, método, 285
211, 241, 255, 278, 280,
Runtime Callable Wrappers Server Activated Objects (SAOs),
300, 376, 460, 516, 541,
(RCWs), 402 743
542, 543, 550, 638, 640,
641, 646, 706, 708 server-activated
S autorização ativação por singleton, 722
autorização baseada em ativação single-call, 722
s, especificador de formato, 258, papéis, 783, 784, 785 client-activated, 723
260 autorização de arquivo, 781 serviços. Ver também Web Ser-
Índice 923

vices. Apache (@OutputCache), 803


P/Invoke, 419, 420, 431, 432, Mono, 203, 204 SHGetFileInfo( ), método, 323
433, 434, 435 site Web, 203 shift-left (<), operador, 58
(Invocation Platform Ser- arquivos DPR de servidor, 760,
shift-right (>), operador, 58
vice), 418 761
Web Services, 7, 685 clusters, 716 ShowMessageBox, propriedade
acesso a, 4 configuração de .NET (classe ValidationSummary),
atributo , 691, 692, 694, 695 Remoting, 757, 758 625
benefícios da comunicação, Session State Server, 832, 833 ShowSummary, propriedade
8, 9 SQL Server (classe ValidationSummary),
capacidade de reutilização, armazenando dados de 625
10 sessão em, 833, 834 ShutdownReason, propriedade
clientes, 9, 10 autenticação baseada em (classe ProcessInfo), 803
consumindo, 695, 696, 698, formulário, 778, 779, 780 shutdownTimeout, atributo (tag
700, 701, 702, 703, 704 Web Server Survey, 203 <processModel>), 801
criando, 686, 687, 688, 689, XSP Simple Object Access Protocol
691 configuração, 197 (SOAP), 685, 712, 713
definição, 6, 7 parâmetros runtime, 197, comparado com codificação
exemplo, 687 198 binária, 766, 767, 768
invocando, 691 <sessionState>, seção (ASP.NET especificação, 743
invocando assincronamente, web.config), 798 estrutura de package, 740, 741
704, 705 Session State Server, 832, 833 exceções, 742, 743
neutralidade de linguagem, respostas, 741, 742
Session, objeto, 831
10 solicitações, 741
retornando dados a partir de, session, variáveis, 633
Session_End, evento, 834, 835 Simple Object Access Protocol.
692, 693
Ver SOAP, 8
segurança, 705, 706, 708, Session_Start, evento, 834, 835
709 SimpleStatusBars, controle, 324
sessões sem cookies, 832
servidores, 10 classe ProvidePropertyAttribute,
set of, instrução, 71 324, 325
SOAP (Simple Object Access
Set( ), método, 360 interface IExtenderProvider, 325,
Protocol), 8, 685
UDDI (Universal Descrip- set, propriedades, 295 326
tion, Discovery, and Inte- SetClip( ), método, 160 listagem de código, 326, 328
gration), 8, 686 SetDefault, valor (propriedade SimpleUserControl, 840, 841, 842
WSDL (Web Service Descrip- DeleteRule), 491 arquivo .ascx, 843
tion Language), 8, 686 SetDefault, valor (propriedade arquivo .aspx
XML (Extensible Markup UpdateRule), 491 BasicUserControlPage, 844,
Language), 8, 685 845
SetEntryPoint( ), método, 390
Windows Services declaração, 842, 843
SetLastError, método, 426 tag <@ Control %>, 844
definição, 6
SetLength( ), função, 68, 69 tag <@ Register %>, 845
servidor, controles, 610, 611
controles de HTML, 611 SetMode( ), método, 523 simulação de múltiplos
controles de servidor Web, 611 SetNull, valor (propriedade formulários, 633, 635
controles de usuário, 611 DeleteRule), 491 sinal de adição (+), 30
controles de validação, 611 SetNull, valor (propriedade sinal de subtração (-), 30
BaseValidator, 501, 618, 619 UpdateRule), 491 sincronização
CompareValidator, 620 SetRange( ), método, 228 acesso a dados de estado, 837
CustomValidator, 624 SetResolution( ), método, 163, single-call, ativação de objeto,
RangeValidator, 623 165 722
RegularExpressionValidator,
SetRowNum( ), método, 647 singleton, ativação de objeto, 722
621 singleton, eventos, 102
RequiredFieldValidator, 619, SetSomeProp( ), método, 309
sintaxe de atributo
620 SetStateInfo( ), método, 237
personalizado, 421, 422, 423
ValidationSummary, 625 SetStyle( ), método, 335 sistema de coordenadas
controles vinculados a dados, SetWindowText( ), método, 423 (Windows), 140, 142
611 SetWindowTextW( ), método, sistemas coordenados
propriedades fortemente 423 coordenadas globais, 173, 174
tipificadas WebControl, 300, sites Web
Shared, atributo (@
571, 572, 573, 625, 627 Apache, 203
OutputCache), 813
servidores, 10 Borland, 24
Shared, configuração
924 Índice

Delphi Guru, 24 Tlbimp.exe, 406 GetDataAdapterInsert-


especificação de SOAP, 743 Type Library to Assembly Con- Command( ), 541
FAQ do CORBA (Common Ob- verter, 402 método
ject Request Broker Architec- solicitações (HTTP), 592, 593 GetDataAdapterUpdate-
ture), 711 classe HTTPRequest, 605 Command( ), 541, 542
FAQ do Mono Project, 184 exemplo, 605 SQL Server, 448
FxCop, 17 solicitações (SOAP), 741 armazenando dados de sessão
Mono, 205 em, 833, 834
SolidBrush, classe, 144
MSDN (Microsoft Developers arquivo de web.config, 834
Network), 4 Some_Func( ), método, 420
banco de dados de estado SQL
MSDN Online, 768 SomeObject, classe, 296 Server, 833, 834
.NET Reflector, 289 SomeProc( ), função, 90 autenticação baseada em
Open Group, 711 Sort property (classe DataView), formulário, 778, 779, 780
Passport SDK, 780 502 conexões
projeto Mono, 28 Sort( ), método, 228 abrindo e fechando, 452, 453
Resourcer, 314 eventos, 453, 454, 455
SortCommand, evento, 678
RFC 2616, 592 strings de conexão, 450, 451
utilitário FxCop, 294 Spacing, barra de ferramentas,
27 SQLCommand, classe, 531, 532,
utilitário Reflector, 374 534, 535, 536, 537, 539
W3C (World Wide Consor- SparseFile FileAttribute, 268
instrução DELETE, 537, 538, 539
tium), 8 Speech API. Ver SAPI, 398 instrução INSERT, 533, 534
Ximian, 187 splines instrução UPDATE, 535, 536,
XML-RPC, 711, 712 splines cardinais, 148, 149 537
Size, valor (tipo enumerado splines de Bezier, 149, 151, 152 listagem de código de exemplo,
NotifyFilters), 281 splines cardinais, 148, 149 531, 532
SizeOf( ), função, 61 Split( ), função, 617 método SubmitAddedRow( ),
SlidingExperation, propriedade Split( ), método, 249 534, 535
(classe Cache), 818 método SubmitUpdates( ), 532,
SponsorAndLease, projeto, 753,
slidingExpiration, atributo 533
754
(seção <forms> de SqlCommand, objeto
sponsors, 723, 724
web.config), 774 criando, 459
sprites
Smart Clients, 10 SQLCommandBuilder, classe,
declarando, 175
smartNavigation, atributo (tag 528, 529, 530, 531
SQL (Standard Query Language) limitações, 529
<pages>), 798 Ver também consultas, 544 salvando atualizações com, 530
SmtpMail, classe, 637 classe SQLCommand, 531, 532,
SqlConnection, objeto
sn.exe, utilitário, 136 534, 535, 536, 537, 539
propriedade ConnectionString,
SOAP (Simple Object Access instrução DELETE, 537, 538,
450, 451
Protocol), 8, 685 539
instrução INSERT, 533, 534 SQLDataAdapter, classe, 475,
SoapHttpClientProtocol, classe, 539, 540, 541, 543, 544
701 instrução UPDATE, 535, 536,
exemplo do bloco principal, 539
537
sobrecarregando método GetDataAdapterDelete-
listagem de código de
funções, 50 Command( ), 543, 544
exemplo, 531, 532
métodos, 99 método GetDataAdapterInsert-
método SubmitAddedRow( ),
operadores, 109 Command( ), 541
534, 535
sobrescrevendo método GetDataAdapterUpdate-
método SubmitUpdates( ),
Command( ), 541, 542
destrutores, 312, 313 532, 533
stored procedure, 544
métodos, 99 classe SQLCommandBuilder,
528, 529, 530, 531 SQLDataReader, classe, 468, 469
softwares
.NET Reflector, 289 limitações, 529 SqlDbType, tipo enumerado, 464
Red Carpet, 187 salvando atualizações com, SqlParameter, classe, 462, 464
instalação do Mono, 187, 530 SQLServer, modo (estado de
188, 189 classe SQLDataAdapter, 539, sessão), 798
packages, 187 540, 541, 543, 544 SqlTransaction, classe, 558
Reflector, 404 exemplo do bloco principal, Ver também processamento
regasm.exe, 407, 409 539 de transação, 558
Resourcer, 314 método
Src, atributo (tag <@ Register
sn.exe, 136 GetDataAdapterDelete-
%>), 845
tcpTrace, 738, 739 Command( ), 543, 544
método ss, especificador de formato, 260
Índice 925

STA, estado de apartment, 349 Stream, classe, 264 especificadores de formato de


Stack, coleção, 220 StreamReaders, 264 data/hora personalizados,
construtor, 221 260, 261
streams, 271
exemplo, 221, 223 especificadores de formato de
acesso assíncrono ao stream,
métodos, 221 tipo enumerado, 261, 262
276, 278, 279
especificadores de formato
StandardClick ControlStyle, BinaryReaders, 264, 275, 276
numérico, 254, 255, 256, 257
valor do tipo enumerado, 336 BinaryWriters, 264, 275, 276
especificadores personalizados
StandardDoubleClick BufferedStreams, 264
de formato numérico, 256,
ControlStyle, valor do tipo FileStreams, 264, 271
257
enumerado, 336 criando, 271, 272, 274, 275
listagem de código de
StartFigure( ), método, 157 dados binários, 275, 276
exemplo, 255
gravando em, 271, 272, 275
StartTime, propriedade (classe tabela de, 254
lendo, 274, 275
ProcessInfo), 803 formatando, 252, 253, 254
valores de FileAccess, 273
State, parâmetro (método imutabilidade, 244, 245, 246
BeginRead( )), 279 valores de FileMode, 272
inserindo, 249
State, propriedade (interface valores de SeekOrigin, 273
localizando comprimento de,
IDbConnection), 449 MemoryStreams, 264
250
StateChange, evento, 453, 455 NetworkStreams, 264
preenchendo, 250
stateless (sem estado), Stream, 264
recortando, 160
protocolos, 592 StreamReaders, 264
removendo caracteres a partir
StateServer, modo (estado de StreamWriters, 264
de, 249
sessão), 798 StringReaders, 264
strings de consulta, 593
Status, propriedade (classe StringWriters, 264
ProcessInfo), 803 string de recursos, 80
TextReaders, 264
stdcall, convenção de chamada, strings terminadas por caractere
TextWriters, 264
436 nulo, 76, 77
streams de arquivo binário, 275, substituindo, 249
Step1_Click( ), handler de
276
evento, 180 StringWriters, 264
Step1_DrawSaucerTrans( ), StreamWriters, 264
Structured Exception Handling.
handler de evento, 180 strict private, instrução, 106 Ver exceções, 114
Step1_Erase( ), handler de strict protected, instrução, 106 Style, classe, 629
evento, 180
string de recursos, 80 Style, propriedade (classe
Step2_Click( ), handler de
evento, 180 StringBuilder, classe, 250 WebControl), 626
Step2_DrawSaucerTrans( ), exemplo, 252 su, comando, 188
handler de evento, 180 métodos, 251
Subject, propriedade (classe
Step3_DrawSaucerPos( ), propriedades, 251
MailMessage), 637
handler de evento, 182 StringReaders, 264
Stopped, estado de thread, 348 SubmitAddedRow( ), método,
strings, 243, 244 534, 535
StopRequested, estado de aparando, 250
thread, 348 SubmitDeletedRow( ), função,
classe StringBuilder (strings
stored procedures 538
mutáveis), 250
DeleteProduct, 551 SubmitUpdates( ), método, 532,
exemplo, 252
executando, 464, 465, 466 533
métodos, 251
InsertProduct, 547 substituindo
propriedades, 251
origens de dados, atualizando, strings, 249
comparando, 246, 247, 248
544, 545, 546, 547, 548, 549,
concatenando, 248, 249 SubString( ), método, 508
550, 551
convertendo em letras subtração (-), operador, 57, 72,
exemplo SqlDataAdapter,
maiúsculas, 250 506
544
convertendo em letras Succ( ), função, 59
método
minúsculas, 250
GetDataAdapterDeleteCo SupportsTransparentBackColor
copiando, 250
mmand( ), 550, 551 ControlStyle, valor do tipo
data/hora, especificador de
método enumerado, 336
formatos, 259
GetDataAdapterInsertCom SuppressFinalize( ), método, 213,
dividindo, 249, 250
mand( ), 546 214
especificadores de formato de
método data/hora, 257, 258, 260, Suspend( ), método, 343
GetDataAdapterUpdateCo 261 SuspendBinding( ), método, 520
mmand( ), 548 listagem de código de
SelectProduct, 545, 546 Suspended, estado de thread, 348
exemplo, 258
UpdateProduct, 549, 550 SuspendRequested, estado de
tabela de, 257, 258
926 Índice

thread, 348 IEnumerator, 218, 219, 220 classe Monitor, 356, 357
SWF (System.Window.Forms) IHashCodeProvider, 218 classe Mutex, 356
Mono, 204, 205 IList, 218, 219 classe ReaderWriterLock, 357,
switch, instrução, 82 System.ComponentMode, 358
namespace, 18 classe
SynchronizationLockException,
System.Configuration, SynchronizationLockExceptio
classe, 371
namespace, 18 n, 371
synchronized( ), método, 361 classe Thread, 342, 345, 347
Syncronize( ), método, 224 System.Data, namespace, 18,
classe ThreadAbortException,
449
Syncronized( ), método, 221, 368
228, 231 System.DirectoryServices, classe
namespace, 18 ThreadInterruptedException,
SyncRoot, propriedade
interface ICollection, 219 System.Drawing, namespace, 370
18, 19, 21, 139 classe ThreadPool, 349, 350, 351
SyncRoot, propriedade, 362
System.EnterpriseServices, classe ThreadPriority, 347, 348
SysErrorMessage( ) método, 427
namespace, 19 classe ThreadState, 348
SysErrorMessage( ), método, 426 classe ThreadStateException, 371
System.GC, classe, 210, 211
System FileAttribute, 268 classe Timer, 351, 352
System.GC.MaxGeneration, 211
System, namespace, 18 classe WaitHandle, 355, 356
System.Globalization,
System.CodeDom, namespace, namespace, 248 System.Timers, namespace, 19
387 System.Web, namespace, 19
System.IO, namespace, 19, 263,
System.CodeDOM, namespace, 264 System.Window.Forms (SWF)
18 Mono, 204, 205
System.Management,
System.Collections, namespace, namespace, 19 System.Windows.Forms,
18, 217 namespace, 19, 21
System.Messaging, namespace,
coleção ArrayList, 227 System.XML, namespace, 19
19
construtores, 221, 225, 227
exemplo, 230 System.Net, namespace, 19
métodos, 227, 228 System.Object, classe, 96 T
propriedades, 227 System.Reflection, namespace,
t, especificador de formato, 257,
coleção HashTable, 230, 231 19
260
construtores, 231 System.Reflection.Emit,
T, especificador de formato, 257
exemplo, 225, 228, 232, 233 namespace, 386, 387
métodos, 231 Tab Order (Designer), 27
System.Resources, namespace,
coleção Queue, 223 tabelas
19
construtor, 224 DataTableCollection, 484, 485
Sys-
exemplo, 223, 226 mapeando
tem.Runtime.CompilerServic
métodos, 221, 224 coleção TableMappings, 476,
es, namespace, 19
coleção Stack, 220 482
construtor, 221 Sys- navegando a partir de páginas
exemplo, 221, 223 tem.Runtime.InteropServices, Web, 644
métodos, 221 namespace, 19 vinculando CheckBoxLists a,
coleções fortemente tipificadas, System.Runtime.Remoting, 649, 650
234 namespace, 19, 719 vinculando DropDownLists a,
aplicação de exemplo, 237, classe ChannelServices, 720 651, 652
238 classe RemotingConfiguration, vinculando ListBoxes a, 654
descendentes de 719, 720 vinculando RadioButtonLists a,
CollectionBase, 234, 237 System.Runtime.Serialization, 653, 656, 657
dicionários fortemente namespace, 19 VMT (Virtual Method Table), 98
tipificados, 238 tabelas (DataTables), 447, 484,
System.Security, namespace, 19
aplicação de exemplo, 241, 485, 487
System.ServiceProcess,
242 adicionando a DataSets, 486
namespace, 19
descendentes de chave primária
DictionaryBase, 238, 241 System.Text, namespace, 19
definindo, 489
interfaces System.Threading, namespace,
colunas
ICollection, 218, 219 19, 342
definição, 487, 488
IComparer, 218 classe ApartmentState, 348, 349
propriedades, 488
IDictionary, 218, 220 classe AutoResetEvent, 360
removendo, 488
IDictionaryEnumerator, 218 classe Interlocked, 358, 359
verificando existência de, 488
IEnumerable, 218 classe ManualResetEvent, 359
Constraint, 447
Índice 927

DataColumns, 447 atalhos pelo teclado, 30 delegates


DataRelation, 447 TemplateColumn, tipo de definição, 344, 352
DataRelations, 492, 494 coluna, 668 executando assincronamente,
DataRows, 447, 495 templates 352, 354
adicionando, 495 AlternatingItemTemplate, 658 ThreadStart, 345, 346
cancelando alterações em, EditItemTemplate, 662 WaitCallback, 349
496 FooterTemplate, 658 estados de apartment, 348, 349
excluindo, 496 HeaderTemplate, 657 estados de thread, 338, 348
modificando, 495 ItemTemplat, 658 eventos, 359
valores de RowState, 496, .NET Remoting, 737 classe AutoResetEvent, 360
499 SelectedItemTemplate, 662 classe ManualResetEvent, 359
valores RowState, 497, 499 SeparatorTemplate, 658 exceções, 368
DataTableCollection, 447 classe
tempo-limite padrão da sessão,
EmployeeDataTable, 571 SynchronizationLockExcep-
831 tion, 371
indexando, 487
métodos, 573, 574 Temporary FileAttribute, 268 classe ThreadAbortException,
pesquisando com método tempos-limite 368, 370
Find( ), 497, 498, 499 tempo-limite padrão da sessão, classe
pesquisando com método Se- 831 ThreadInterruptedExcep-
tion, 370
lect( ), 498 terminando loops, 85
classe ThreadStateException,
propriedades, 573, 574 testando 371
removendo a partir de controles WinForms, 313 garbage collection, 371
DataSets, 486 Text, propriedade (classe mecanismos bloqueadores, 355
restrições, 489 BaseValidator), 619 classe Interlocked, 358, 359
classe ForeignKeyConstraint,
texto classe Monitor, 356, 357
490, 491
gravando em clientes, 602 classe Mutex, 356
classe UniqueConstraint, 489
streams de arquivo de texto classe ReaderWriterLock, 357,
recuperando de origens de
criando, 271, 272, 275 358
dados, 491, 492
gravando em, 271, 272, 275 classe WaitHandle, 355, 356
TabIndex, propriedade (classe lendo, 274, 275 métodos de controle
WebControl), 626 BeginInvoke( ), 364
TextReaders, 264
Table Mappings, caixa de CreateGraphics( ), 365, 368
TextureBrush, classe, 144
diálogo, 567 EndInvoke( ), 364, 365
TextWriters, 264
Table, propriedade (classe Invoke( ), 363
DataView), 502 Thread, classe, 342, 345, 347 propriedade InvokeRequired,
ThreadAbortException, classe, 363, 364
TableMappings, coleção, 476,
368, 370 namespace System.Threading,
482 ThreadInterruptedException,
342
TAccount, classe, 729 classe, 370
pools, 349, 351
TagName, atributo (tag <@ Reg- ThreadMePlease( ), método, 345
prioridades, 347, 348
ister %>), 845 ThreadPool, classe, 349, 350,
351 threads leves, 340
TagPrefix, atributo (tag <@ Reg- threads lógicos, 340, 341
ThreadPriority, classe, 347, 348
ister %>), 845 threads produtores, 359
threads
tags AppDomains, 341, 342 timers, 351, 352
<@ Control %>, 844 armazenamento de thread lo- ThreadsStart, delegate, 345, 346
<@ Register %>, 845 cal, 360, 361 ThreadState, classe, 348
form, 593 classes/métodos thread-safe, ThreadState, classe Exception,
target, parâmetro (método 361 371
InvokeMember( )), 382 método synchronized( ), 361
threadvar, palavra-chave, 360
TClientDataSet, componente, propriedade IsSynchronized,
timeout, atributo (seção <forms>
446 362
de web.config), 774
TCP (Transmission Control Pro- propriedade SyncRoot, 362
comunicações entre processos timeout, atributo (tag
tocol), 764, 765
Win32, 361 <processModel>), 801
tcpTrace, utilitário, 738, 739
criando com métodos de Timer, classe, 351, 352
TCriticalSection, classe, 213,
instância, 344 Timer1_Tick( ), handler de
215
criando com métodos estáticos, evento, 177
TDataSetProvider, componente, 345, 347 Timer1_Timer( ), handler de
446 declarando, 342, 343 evento, 180
teclado definição, 340
928 Índice

tipo de bloco HTML, 855, 856 código inseguro, 73, 74 caractere nulo, 76, 77
tipo de string. Ver strings, 243 coerção de tipo, 79, 80 substituindo, 249
comparação de, 60, 61 tipos blittable, 415
tipo enumerado
conjuntos, 71 tipos non-blittable, 415
valores do tipo enumerado
atribuindo valores a, 71 Value Types, 17
FileAttributes, 267, 268
declarando, 71 Variant, 61, 63
tipo enumerado, especificadores interseção, 73 coerção de tipo, 64
de formato, 261, 262 membro, 72 expressões, 64, 65, 66
SequentialAccess, tipo operador de adição (+), 72 Null, 66
enumerado, 473 operador de subtração (-), 72 typecasting, 63
tipos, 59 operador in, 72 Unassigned, 66
aliases, 78, 79 união, 72 WideChar, 61
aninhando, 108 CTS (Common Type System), tipos definidos pelo usuário
AnsiChar, 61 16, 17 aliases, 78, 79
arrays, 67, 68, 69, eliminando a ambigüidade, 42 arrays, 67
blittable/non-blittable, 415 PAnsiChar, 76 arrays dinâmicos, 68, 69
boxing/unboxing, 59, 60 PChar, 76 declarando, 67
Char, 61 ponteiros, 74, 75, 76 iterando por, 67
código inseguro, 73, 74 alocação de memória, 75 multidimensionais, 67
coerção de tipo, 79, 80 declarando, 74 código inseguro, 73, 74
comparação de, 60, 61 desreferenciando, 75 conjuntos, 71
conjuntos, 71, 72, 73 ponteiros nulos, 76 atribuindo valores a, 71
CTS (Common Type System), ponteiros tipificados, 74 declarando, 71
16, 17 PWideChar, 76 interseção, 73
eliminando a ambigüidade, 42 Reference Types, 17 membro, 72
PAnsiChar, 76 reflection, 377, 378 operador de adição (+), 72
PChar, 76 registros, 51, 69, 70 operador de subtração (-), 72
ponteiros, 74, 75, 76 registros de variantes, 77 operador in, 72
alocação de memória, 75 strings, 243, 244 união, 72
declarando, 74 aparando, 250 ponteiros, 74, 75, 76
desreferenciando, 75 classe StringBuilder, 250, 251 alocação de memória, 75
ponteiros nulos, 76 comparando, 246, 247, 248 declarando, 74
ponteiros tipificados, 74 concatenando, 248, 249 desreferenciando, 75
PWideChar, 76 convertendo em letras ponteiros nulos, 76
Reference Types, 17 maiúsculas, 250 ponteiros tipificados, 74
reflection, 377, 378 convertendo em letras registros, 51, 69, 70
registros, 51, 69, 70 minúsculas, 250 registros de variantes, 77
registros de variantes, 77 copiando, 250 strings
strings, 243, 244 data/hora, especificador de string de recursos, 80
Value Types, 17 formatos, 259 strings terminadas por
Variant, 61, 63 dividindo, 249, 250 caractere nulo, 76, 77
coerção de tipo, 63, 64 especificadores de formato tipos enumerados
expressões, 64, 65, 66 de data/hora, 257, 258, CharSet, 422
Null, 66 260, 261 CombineMode, 160
Unassigned, 66 especificadores de formato ControlStyles, 335, 336
WideChar, 61 de tipo enumerado, 261, DataRowEnumeration, 496
tipos de caractere, 61 262 LineCap, 145
tipos de dados, 59 especificadores de formato NotifyFilters, 281
aliases, 78, 79 numérico, 254, 255, 256, OleDbType, 464
aninhando, 108 257 RotateFlipType, 167, 168
AnsiChar, 61 formatando, 252, 253, 254 SequentialAccess, 473
arrays, 67 imutabilidade, 244, 245, 246 SqlDbType, 464
arrays dinâmicos, 68, 69 inserindo, 249 TSpriteDirection, 176
declarando, 67 localizando comprimento UnmanagedType, 416
iterando por, 67 de, 250 VarEnum, 417
multidimensionais, 67 preenchendo, 250 Tlbimp.exe, ferramenta, 402, 406
boxing recortando, 160 TmyStringWriter, classe, 40
(empacotamento)/unboxing removendo caracteres a TMyType, 42
(desempacotamento), 59, 60 partir de, 249 TMyType, classe, 385
Char, 61 string de recursos, 80 TNewUserInfoControl, 150, 235,
strings terminadas por 236, 237, 280, 299, 383, 385,
Índice 929

388, 389, 518, 519, 524, 525, tratamento de erro Unchanged RowState, 496
545, 615, 666, 680, 681, 682, COM Interop, 406, 418 Unchanged, valor
683, 699, 700, 707, 854, 862, Win32 API, 426 (DataViewRowState), 508
863, 864, 865, 866, 867, 868,
869 tratamento de evento união
To, propriedade (classe vinculação de dados, 517, 519, conjuntos, 72
MailMessage), 637 520 Unicode, valor (tipo enumerado
ToArray( ), método, 221, 224, trechos de código, 29 CharSet), 422
228 TRemotingHelper, classe, 70, unindo
TObject, classe, 290 85, 92, 96, 126, 127, 132, 173, linhas, 147, 148
To-Do, lista, 36 195, 277, 292, 363, 473, 474,
tolerância a falhas UninstallPersistSqlState.sql, 834
489, 551, 553, 561, 563, 606,
arquitetura multicamada, 716, Unique, propriedade
659, 759, 761, 762
717 (DataColumns), 488
Trim( ), método, 250
ToLower( ), método, 250 UniqueConstraint, classe, 489
TrimEnd( ), método, 250
Tool, paleta, 29 unit MonoFuncs, 193
TrimStart( ), método, 250
ToolTip, propriedade (classe unit, palavra-chave, 40
TrimToSize( ), método, 224,
WebControl), 626 units, 90, 91
228, 230
tortas, desenhando, 154, 155 aliases, 46, 47
Trunc( ), função, 80 cabeçalhos, 40
ToString( ), método, 251, 269
Truncate file mode, 272 exemplo MyUnit.pas, 39, 40
ToUpper( ), método, 250
try..finally, bloco, 115, 116, 453 instrução finalization, 91
TPostbackInputWebControl, 75, instrução implementation, 91
TryEnter( ), método, 357
100, 177, 229, 232, 233, 234, instrução initialization, 91
252, 284, 285, 304, 307, 308, TSimpleFactory, classe, 744,
747, 748 instrução interface, 91
309, 316, 317, 318, 319, 320, instrução uses, 92
321, 322, 327, 331, 332, 333, TSimpleServer, classe, 746, 747
MonoFuncs, 193
334, 335, 345, 346, 378, 453, TspriteDirection, tipo referências circulares de unit, 42,
461, 471, 490, 493, 571, 573, enumerado, 176 43, 92, 93
575, 604, 606, 660, 665, 729, TStateInfoCollection, 234, 238 seção de finalização, 41
730, 731, 819, 857, 858, 859, TstateInfoDictionary, classe, seção de implementação, 41
860, 861 238 seção de inicialização, 41
<trace>, seção (ASP.NET TStateInfoDictionary, classe, seção de interface, 40, 41
web.config), 807, 808 242 sintaxe da cláusula uses, 39, 42
traceMode, atributo (tag tt, especificador de formato, 260 units genéricas, 45
<trace>), 808 Universal Description, Discovery,
TWebService1, classe, 688
transações and Integration. Ver UDDI
TWINFORM.Reflectassembly( ),
classe BdpTransaction, 586 Unknown, estado de apartment,
método, 374
definição, 528 349
type, instrução, 108
transações de processamento. Unlock( ), método, 837
Ver processamento de Type, instrução, 66
UnmanagedType, tipo
transação, 558 Type, propriedade (classe
enumerado, 416
Transaction, classe, 446 CompareValidator), 620
$UNSAFECODE EM, diretiva de
Transaction, propriedade (inter- TypeBuilder, classe, 387
compilador, 73
face IDbCommand), 457 TypeConverter, classe, 298
$UNSAFECODE ON, diretiva, 134
Transact-SQL TypeLibConverter, classe, 412
unsafe, instrução, 73
instrução DELETE, 537 TypeLibrarytoAssemblyConvert
Unspecified, nível de isolamento,
instrução UPDATE, 536 er, ferramenta, 402
564
Transact-SQL, consultas. Ver
Unstarted, estado de thread,
consultas, 555
U 348
TransEx, aplicação, 559, 562
u, especificador de formato, 258 Update( ), método, 529, 562
Transfer( ), método, 631, 632
U, especificador de formato, 258 Update, comando, 676, 677
Transfer, botão (aplicação
UDDI (Universal Description, Update, consultas
BankExample), 736
Discovery, and Integration), classe SqlCommand, 535, 536,
transformações, 168, 171, 172 537
8, 686
TranslateTransform( ), método, método
Unassigned, variantes, 66
168, 170, 171, 173 GetDataAdapterUpdateComm
unboxing (desempacotamento),
Transmission Control Protocol and( ), 541
59, 60
(TCP), 764, 765 stored procedure UpdateProduct,
930 Índice

549 620, 621 inicialização, 53


UpdateCommand, evento, 663 classe CustomValidator, 624, IntPtr, 426
UpdateCommand, objeto, 476 625 PATH, 402
classe RangeValidator, 623, RefParam, 397
UpdatedRowSource,
624 Result, 86
propriedade (classe
classe Self, 100
SqlCommand), 555
RegularExpressionValida- variáveis de ambiente (PATH),
UpdateProduct, stored proce- tor, 621, 623 402
dure, 549, 550 classe variáveis de interface, 113, 114
UpdateRowSource, propriedade RequiredFieldValidator, variáveis de sessão, 632, 633
(interface IDbCommand), 457 619, 620 variáveis globais
UpdateRule, propriedade classe ValidationSummary, inicialização, 53
(ForeignKeyConstraint), 491 625 VaryByControl, atributo
UpperCase( ), método, 39 validação do lado do cliente (@OutputCache), 813
URL, autorização de, 782, 783 (WebForm), 618 VaryByControl, configuração
validação do lado do (@OutputCache), 803
useFullyQualifiedRedirectUrl,
servidor (WebForm), 618
atributo (tag <httpRuntime>), VaryByCustom, atributo (@
797 Validate( ), método, 619 OutputCache), 813, 815, 816
<user>, seção (arquivo validateRequest, atributo (tag VaryByCustom, configuração
web.config), 776 <pages>), 798 (@OutputCache), 803
UserControl, classe, 290, 843 ValidationSummary, classe, 625 VaryByHeader, atributo (@
userControlBaseType, atributo valores OutputCache), 813, 815
(tag <pages>), 798 atribuindo a conjuntos, 71 VaryByHeader, configuração
default (controles WinForms), (@OutputCache), 803
UserLoginValid( ), método, 775,
303 VaryByParam, atributo
776, 777, 778, 779, 780
passando parâmetros por, 87 (@OutputCache), 813, 814, 815
UserMouse ControlStyle, valor RowState, 497, 499 VaryByParam, configuração
do tipo enumerado, 336 únicos, recuperando de bancos (@OutputCache), 803
username, atributo (tag de dados, 459, 460 varying by, parâmetros
<processModel>), 801 ValParam, constante, 397 (armazenamento em cache de
UserPaint ControlStyle, valor do Value, propriedade (classe página), 814
tipo enumerado, 336 VCL, 429
Cache), 818
Users, caixa de diálogo, 770 VCL Forms, 28
Value Types, 17 VCL for .NET, 5, 6
uses, cláusula, 39, 42 Values, propriedade VerifyRenderingInServerForm( ),
uses, instrução, 92, 191 interface ICollection, 220 método, 861
uso de letras maiúsculas e ValueToCompare, propriedade VerifyType( ), método, 237
minúsculas, 52 (classe CompareValidator), versionHeader, atributo (tag
utilitários. Ver softwares, 187 620, 621 <httpRuntime>), 797
ViewState, 828, 829, 830
var, instrução, 87
adicionando valores ao state
V VAR, instrução, 53 bag, 830
var, palavra-chave, 39 desativando na página, 830
v, 617, 618
VarEnum, tipo enumerado, 417 desativando para controles, 830
validação, controles, 611
variando VIEWSTATE, campo, 600
BaseValidator, 501, 618, 619
cabeçalhos (cache de página), vinculação de dados, 514, 642
CompareValidator, 620
815 a arrays
exemplo de, 620, 621
por parâmetros (cache de controle CheckBoxList, 649
propriedades, 620
página), 813, 815 controle DropDownList, 650,
CustomValidator, 624
por strings personalizadas 651
RangeValidator, 623
RegularExpressionValidator, (cache de página), 815, 816 controle ListBoxes, 653
621 Variant, tipos, 61, 63 controle RadioButtonList,
RequiredFieldValidator, 619, coerção de tipo, 63, 64 648, 653, 655, 656
620 expressões, 64, 65, 66 a tabelas de banco de dados
ValidationSummary, 625 Null, 66 controle CheckBoxList, 649,
validando Unassigned, 66 650
WebForms, 618 Variant, variável, 393 controle DropDownList, 651,
652
classe BaseValidator, 618, variáveis controle ListBoxes, 654
619 declarando, 51, 53 controle RadioButtonList,
classe CompareValidator, EmptyParam, 397 653, 656, 657
Índice 931

analisando dados interface IDataErrorInfo, 514 W


sintaticamente, 523, 525 interface IEditableObject, 514
classe BindingContext, 515 interface IList, 514 W3C (World Wide Consortium),
classe BindingManagerBase, listas, 516, 517 8
515 mestre/detalhe, 526, 527 Wait( ), método, 357, 371
classe CurrencyManager, 515 modificação de dados, 520, WaitAll( ), método, 356, 360
controle CheckBoxList 521, 523 WaitAny( ), método, 356, 360
propriedades, 648 navegação, 517, 519, 520
WaitCallback, delegate, 349
vinculando a arrays, 649 tratamento de evento, 517,
vinculando a tabelas de WaitHandle, classe, 355, 356
519, 520
banco de dados, 649, vinculação complexa, 515 WaitOne( ), método, 356, 360,
650 705
vinculação de dados complexa
controle DataGrid, 667, 668 WaitSleepJoin, estado de thread,
ASP.NET, 647
adicionando itens, 677 348
vinculação de dados simples
classificando, 678
ASP.NET, 643, 644, 647 Web Service Description Lan-
editando, 671, 672, 673, 675,
vinculação de valor simples, guage. Ver WSDL.
676, 677
517 Web Services, 7, 685
paginação, 72, 239, 240, 241,
vinculação de valor único, 517 acesso a, 4
343, 369, 370, 384, 668,
vinculação simples, 515 atributo , 691, 692, 694, 695
669, 670, 671
tipos de coluna, 667, 668 vinculação de dados complexa, benefícios da comunicação, 8, 9
controle DataList, 662, 663, 515 capacidade de reutilização, 10
664, 665, 666, 667 ASP.NET, 647 clientes, 9, 10
declaração, 556, 663, 664 vinculação de dados de valor Smart Clients, 10
eventos, 662, 663 único, 517 consumindo, 695
exemplo, 664 vinculação de dados simples, classes proxy, 696, 698, 700,
propriedades, 662, 663 515 701, 703, 704
renderização de imagem, 666 ASP.NET, 643, 644, 647 por meio do diálogo Add Web
templates, 662 Reference, 702
vinculação de valor simples
controle DropDownList, 650, processo de descoberta, 696
único, 517
651, 652 through Add Web Reference
vinculação inicial com COM,
vinculando a arrays, 650, dialog, 703, 704
400
651 criando, 686, 687, 688, 689, 691
vinculação tardia, 379 definição, 6, 7
vinculando a tabelas de
primeiro exemplo de assembly, exemplo, 687
banco de dados, 651, 652
379 invocando, 691
controle ListBoxes, 653, 654
segundo exemplo de assembly, invocando assincronamente,
vinculando a arrays, 653
379, 380 704, 705
vinculando a tabelas de
banco de dados, 654 vinculando. Ver vinculação de neutralidade de linguagem, 10
controle RadioButtonList, 655, dados, 514 retornando dados a partir de,
656, 657 vírgula (,), 256 692, 693
vinculando a arrays, 648, Virtual Directory Properties, segurança, 705, 706, 708, 709
653, 655, 656 caixa de diálogo, 789 servidores, 10
vinculando a tabelas de SOAP (Simple Object Access Pro-
Virtual Method Table (VMT), 98
banco de dados, 653, 656, tocol), 8, 685
Visual Component Library. Ver
657 UDDI (Universal Description,
VCL for .NET, 5
controle Repeater, 657, 658, Discovery, and Integration),
visualizações. Ver DataViews. 8, 686
659, 660, 661
declaração, 659 visualizador de imagens WSDL (Web Service Description
eventos, 658 baseado em miniaturas, 639, Language), 8, 686
exemplo, 658 640, 641 XML (Extensible Markup Lan-
propriedades, 658 visualizando guage), 8, 685
saída, 230, 661 conteúdo de assembly, 121, Web, aplicações. Ver aplicações,
templates, 657, 658 123 595
controles de lista vinculados a dependências de assembly, 121 web.config, arquivo, 792, 793,
dados, 647 informações de arquivo, 270, 795
DataViews, 503, 504 271 esquema de arquivo, 793, 795
expressões de vinculação, informações de diretório, 268, seção <appSettings>, 809
avaliando, 660 269 seção <autenticação>, 773, 796
formato de dados, 523, 525 VMT (Virtual Method Table), 98 seção <authorization>, 796, 797
interface IBindingList, 514 seção <credentials>, 776, 777
932 Índice

seção <customErrors>, 799 630 questões de desempenho, 430,


seção <forms>, 773, 774 POST, 629, 630 431, 432, 433, 434, 435
seção <httpRuntime>, 797 Server.Transfer( ), 631, 632 sintaxe de atributo
seção <location>, 795 variáveis de sessão, 632, 633 personalizado, 421, 422,
seção <pages>, 798 ordem de processamento de 423
seção <processModel>, 800, evento, 616 sintaxe Delphi tradicional,
801, 802 420, 421
painéis, 633, 635
seção <sessionState>, 798 tipos de parâmetro, 423, 424,
respostas de correio eletrônico,
seção <trace>, 807, 808 425, 426
enviando, 636, 637, 638
seções de configuração tratamento de erro, 426
personalizadas, 809, 810 salvando informações a partir
rotinas .NET em código Win32,
de, 614, 615, 616
WebControl, classe 435, 436
simulação de múltiplos
propriedades fortemente declarações de importação,
formulários, 633, 635
tipificadas, 300, 571, 572, 440
separação de
573, 625, 627 empacotamento (marshaling),
projeto/programação, 601
WebForms, 28, 595, 611 437, 438, 439, 440
validação, 618
adicionando controles, 595 sintaxe Delphi tradicional,
classe BaseValidator, 618,
adicionando controles 436, 437
619
dinamicamente, 639, 640, tipos de parâmetro, 437, 438,
classe CompareValidator,
641 439, 440, 441
620, 621
arquivos, carregando clientes, Win32Check( ), método, 427, 428
classe CustomValidator, 624
635, 636 Windows Forms. Ver WinForms,
campo VIEWSTATE, 600 classe RangeValidator, 623,
624, 625 controles.
code-behind, 600, 601
comunicação baseada em classe Windows Services, 6
evento, 599, 600 RegularExpressionValidato Windows, autenticação, 770,
controles de lista, r, 621, 623 771, 772
pré-preenchendo, 616, 617, classe autenticação Basic, 772
618 RequiredFieldValidator, autenticação Digest, 772
handler de evento OnInit, 619, 620 autenticação NTLM, 770, 772
617, 618 classe ValidationSummary, configurando, 770, 772
método 625 Windows, sistema de
PopulateDdlFromFile( ), validação do lado do cliente,
coordenadas, 140, 142
617 618
validação do lado do WinForms, controles
controles de servidor, 610, 611
servidor, 618 AlarmClock, 307
controles de HTML, 611
visualizador de imagens classes ancestrais, 289, 290
controles de servidor Web,
baseado em miniaturas, 639, comportamento em tempo de
611
640, 641 projeto, 313
controles de usuário, 611
controles de validação, 501, eventos, 304
webGarden, atributo (tag
611, 618, 619, 620, 621, criando, 307
<processModel>), 801
623, 624, 625 definição, 305
Welcome, página, 25
controles vinculados a handlers de evento, 305
while, loop, 84 métodos de despacho de
dados, 611
propriedades fortemente WideChar, tipo, 61 evento, 305, 306
tipificadas WebControl, Width, propriedade (classe propriedades de evento, 303,
300, 571, 572, 573, 625, WebControl), 626 305, 306, 309, 310, 311
627 Win32, aplicações exemplo ExplorerViewer, 314
criando, 595, 613, 614 Ver também FillListView( ), método, 323
definição, 5 interoperabilidade, 391 listagem de código, 315
estrutura de página, 596, 597, códigos de erro, 426, 427, 428 método ActivateFile( ), 323
598, 599 comunicações entre processos, método ExtractIcon( ), 323
eventos Load, processando, 613 361 método FillTreeView( ), 323
eventos postback, 608, 609 exportações de DLL Win32 no método GetDirectories( ), 323
formatando, 625 código .NET, 419, 420 método RefreshNode( ), 323
handlers de evento, 595 códigos de erro HResult, 428, método SHGetFileInfo( ), 323
herança de página, 601 429, 430 exemplo PlayingCard, 328
imagens, 638, 639 códigos de erro Win32, 426, declaração de classe, 329, 330
layout de página, 612 427, 428 listagem de código, 331
manutenção de estado, 600 empacotamento (marshal- método InitComp( ), 335
navegando entre, 629 ing), 423, 424, 425, 4264, método OnPaint( ), 337
HttpResponse.Redirect( ), 39, 441 método SetStyle( ), 335
Índice 933

valores do tipo enumerado automação com vinculação autenticação, 777, 778


ControlStyles, 335, 336 tardia, 395 XML (Extensible Markup Lan-
exemplo SimpleStatusBars, 324 objeto Automation, guage), 8, 685
classe instanciando, 394 arquivos de configuração .NET
ProvidePropertyAttribute, Worker Processes (ASP.NET), Remoting
324, 325 595 configuração de cliente, 758,
interface IExtenderProvider, World Wide Consortium (W3C), 759
325, 326 8 configuração de servidor, 757,
listagem de código, 326, 328 758
Write, acesso a arquivo, 273
ícones de componente, 314 estrutura de, 755, 757
implementação de Write( ), método, 273, 275, 347,
integração ADO.NET, 444
TypeConverter, 298, 300 602, 806, 850
.NET Remoting configuração
métodos, 311 WriteLine( ), método, 244, 273, arquivos
construtores, 312 347, 390 client configuração, 759
destrutores, sobrescrevendo, writeln, procedure, 347 XML-RPC, 711, 712
312, 313 WriteMessage( ), método, 381 Xor (binário), operador, 58
interdependências, 311 WriteSomething( ), método, 385
métodos private, 311 XSP
.wsdl, arquivos, 697 configuração, 197
métodos protected, 311
métodos public, 312 WSDL (Web Service Description parâmetros runtime, 197
métodos published, 312 Language), 8, 686 diretório-raiz da aplicação,
métodos strict private, 311 197, 198
métodos strict protected, 311 diretório-raiz Web, 197
X porta e endereço, 198
passos para escrever
componentes, 289 x, especificador de formato, .xsd, arquivos (DataSets
propriedades, 293 254, 255, 258, 262 fortemente tipificados), 568
propriedades de array, 300, X, especificador de formato, xsp, package, 189
302, 303, 304 254, 255, 258, 262
propriedades de objeto, 296, XCOPY
297, 298, 300 distribuição, 791, 792
Y
propriedades de tipo opção /E, 791 y, especificador de formato, 261
enumerado, 294, 295 opção /H, 792 yy, especificador de formato, 261
propriedades simples, 293, opção /I, 792 yyyy, especificador de formato,
294 opção /K, 792 261
valores default, 303 opção /R ,792
quando utilizar, 288, 289 opção /Y, 792
testando, 313 Ximian Red Carpet, 187 Z
units de componente, 290, instalação do Mono, 187, 189 z, especificador de formato, 261
291, 293 packages, 187
zz, especificador de formato, 261
Word Ximian, site Web, 187 zzz, especificador de formato,
automação
.xml, arquivos 261
PÁGINA EM BRANCO
PÁGINA EM BRANCO
PÁGINA EM BRANCO

Você também pode gostar