Delphi 5 - Guia do Desenvolvedor_capitulos do CD

Transcrição

Delphi 5 - Guia do Desenvolvedor_capitulos do CD
Documento de padrões
de codificação
CAPÍTULO
6
NE STE CAP ÍT UL O
l
Introdução
l
Regras gerais de formatação sobre o código-fonte
l
Object Pascal
l
Arquivos
l
Formulários e módulos de dados
l
Pacotes
l
Componentes
O texto completo deste capítulo aparece no CD-ROM
que acompanha este livro.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 1 — 2ª PROVA
Introdução
Este documento descreve os padrões de codificação para a programação em Delphi, conforme usados no
Guia do Programador Delphi 5. Em geral, o documento segue as orientações de formatação constantemente “não-pronunciadas”, usadas pela Borland International com algumas poucas exceções. A finalidade de incluir este documento no Guia do Programador Delphi 5 é apresentar um método pelo qual as
equipes de desenvolvimento possam impor um estilo coerente para a codificação realizada. A intenção é
fazer isso de modo que cada programador em uma equipe possa entender o código sendo escrito pelos
outros programadores. Isso é feito tornando-se o código mais legível através da coerência.
Este documento de maneira alguma inclui tudo o que poderia existir em um padrão de codificação.
No entanto, ele contém detalhes suficientes para que você possa começar. Fique à vontade para usar e
modificar esses padrões de acordo com as suas necessidades. No entanto, não recomendamos que você se
desvie muito dos padrões utilizados pelo pessoal de desenvolvimento da Borland. Recomendamos isso
porque, à medida que você traz novos programadores para a sua equipe, os padrões com que eles provavelmente estarão mais acostumados são os da Borland. Como a maioria dos documentos de padrões de
codificação, este documento será modificado conforme a necessidade. Portanto, você encontrará a versão mais atualizada on-line, em www.xapware.com/ddg.
Este documento não aborda padrões de interface com o usuário. Esse é um tópico separado, porém
igualmente importante. Muitos livros de terceiros e documentação da própria Microsoft aborda tais orientações, e por isso decidimos não replicar essas informações, mas sim indicarmos a Microsoft Developers Network e outras fontes onde essa informação se encontra à sua disposição.
Regras gerais de formatação sobre o código-fonte
Endentação
A endentação será de dois espaços por nível. Não salve caracteres de tabulação nos arquivos-fonte. O
motivo para isso é que os caracteres de tabulação são expandidos para diferentes larguras, conforme as
diferentes configurações dos usuários e de acordo com os utilitários de gerenciamento de código-fonte
(impressão, arquivamento, controle de versão etc.).
Você pode desativar o salvamento de caracteres de tabulação desmarcando as caixas de seleção Use
Tab Character (usar caracter de tabulação) e Optimal Fill (preenchimento ideal) na página General da caixa de diálogo Editor Properties (propriedades do editor), acessada por meio de Tools, Editor Options.
Margens
As margens serão definidas em 80 caracteres. Em geral, o código-fonte não deve exceder esse limite, com
a exceção para terminar uma palavra. No entanto, essa orientação é um tanto flexível. Sempre que possível, as instruções que excedem uma linha devem ser quebradas após uma vírgula ou um operador. Quando uma instrução é quebrada, ela deve ser recuada em dois caracteres à direita do início da linha da instrução original.
Par begin..end
A instrução begin aparece na sua própria linha. Por exemplo, a primeira linha a seguir está incorreta; a segunda linha está correta:
for I := 0 to 10 do begin // Errado, begin na mesma linha de for
for I := 0 to 10 do
// Correto, begin em uma linha separada
begin
Uma exceção a essa regra é quando a instrução begin aparece como parte de uma cláusula else. Veja
um
exemplo:
2
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
if alguma instrução = then
begin
…
end
else begin
AlgumaOutraInstrução;
end;
A instrução end sempre aparece em sua própria linha.
Quando a instrução begin não fizer parte de uma cláusula else, a instrução end correspondente sempre será recuada para corresponder à sua parte begin.
Object Pascal
Parênteses
Nunca deverá haver espaço em branco entre um parêntese inicial e o caractere seguinte. Da mesma forma, nunca deverá haver espaço em branco entre um parêntese final e o caractere anterior. O exemplo a
seguir ilustra o espaçamento incorreto e correto com relação aos parênteses:
CallProc( AParameter );
CallProc(AParameter);
// incorreto
// correto
Nunca inclua parênteses desnecessários em uma instrução. Os parênteses só devem ser usados onde
for necessário para gerar o significado desejado no código-fonte. Os exemplos a seguir ilustram o uso incorreto e correto:
if (I = 42) then
// incorreto – parênteses desnecessários
if (I = 42) or (J = 42) then // correto – parênteses necessários
Palavras reservadas e palavras-chave
As palavras reservadas e as palavras-chave da linguagem Object Pascal sempre deverão estar completamente em minúsculas.
Procedimentos e funções (rotinas)
Nomeação/formatação
Os nomes de rotinas sempre deverão começar com uma letra maiúscula e utilizar maiúsculas nas palavras
intermediárias, para melhorar a legibilidade. A seguir vemos um exemplo de um nome de procedimento
formatado incorretamente:
procedure estenomederotinaestáformatadoincorretamente;
Agora veja um exemplo de um nome de rotina com iniciais maiúsculas apropriadas:
procedure EsteNomeDeRotinaEstáMuitoMaisLegível;
As rotinas deverão receber nomes significativos para o seu conteúdo. As rotinas que causam uma
ação terão como prefixo o verbo da ação. Veja um exemplo:
procedure FormatarDiscoRígido;
As rotinas que definem valores de parâmetros de entrada deverão ter como prefixo a palavra set
(definir):
procedure SetNomeUsuário;
As rotinas que recuperam um valor deverão ter como prefixo a palavra get (obter):
function GetNomeUsuário: string;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
3
Parâmetros formais
Formatação
Onde for possível, os parâmetros formais do mesmo tipo deverão ser combinados em uma instrução:
procedure Foo(Param1, Param2, Param3: Integer; Param4: string);
Nomeação
Todos os nomes de parâmetros formais deverão ser indicativos de sua finalidade, e normalmente serão
baseados no nome do identificador que foi passado para a rotina. Quando for apropriado, os nomes de
parâmetro deverão ter como prefixo o caracter A:
procedure AlgumProced(AnomeUsuário: string; AidadeUsuário: integer);
O prefixo A é uma convenção usada para retirar a ambigüidade quando o nome do parâmetro for
igual ao nome de uma propriedade ou campo na classe.
Ordenação de parâmetros
A ordenação de parâmetros formais a seguir enfatiza o aproveitamento das convenções de chamada de
registro.
Os parâmetros mais usados (por quem chama) deverão estar nos primeiros lugares. Os parâmetros
menos usados deverão ser listados depois, na ordem da esquerda para a direita.
As listas de entrada deverão vir antes das listas de saída, na ordem da esquerda para a direita.
Coloque os parâmetros mais genéricos antes dos parâmetros mais específicos, na ordem da esquerda para a direita. Por exemplo: AlgumProced(Aplaneta, Acontinente, Apaís, Aestado, Acidade).
Existem exceções à regra de ordenação, como no caso dos manipuladores de evento, onde um parâmetro chamado Sender do tipo TObject normalmente é passado como primeiro parâmetro.
Parâmetros constantes
Quando parâmetros do tipo de registro, array, ShortString ou interface não são modificados por uma rotina, os parâmetros formais para essa rotina deverão marcar o parâmetro como const. Isso garante que o
compilador gerará código para passar esses parâmetros não modificados da maneira mais eficiente.
Os parâmetros dos outros tipos podem opcionalmente ser marcados como const se não forem modificados por uma rotina. Embora isso não tenha efeito sobre a eficiência, oferece mais informações sobre
o uso do parâmetro para quem chamou a rotina.
Colisões de nomes
Ao usar duas unidades que contêm rotinas do mesmo nome, a rotina que reside na unidade que aparece
por último na cláusula uses será chamada se você chamar essa rotina. Para evitar essas ambigüidades dependentes da cláusula uses, sempre use como prefixo dessas chamadas de método o nome da unidade desejada. Veja dois exemplos:
SysUtils.FindClose(SR);
e
Windows.FindClose(Handle);
Variáveis
Nomeação e formatação de variáveis
As variáveis deverão ter nomes que indicam sua finalidade.
Variáveis de controle de loop geralmente recebem um único caracter, como I, J ou K. Também é
4 aceitável usar um nome mais significativo, como ÍndiceUsuário.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Os nomes de variáveis Booleanas deverão ser descritivos o suficiente para que o significado dos valores True e False seja claro.
Variáveis locais
As variáveis locais usadas dentro dos procedimentos seguem as mesmas convenções de uso e nomeação
de todas as outras variáveis. As variáveis temporárias deverão ter nomes apropriados.
Quando for necessário, a inicialização de variáveis locais ocorrerá imediatamente na entrada da rotina. As variáveis AnsiString locais são inicializadas automaticamente com uma string vazia, variáveis locais do tipo interface e dispinterface são inicializadas automaticamente como nil, e variáveis locais do
tipo Variant e OleVariant são inicializadas automaticamente como Unassigned.
Uso de variáveis globais
O uso de variáveis globais é desencorajado. No entanto, elas podem ser usadas quando for necessário.
Quando isso acontecer, você deverá manter as variáveis globais dentro do contexto em que são usadas.
Por exemplo, uma variável global poderá ser global apenas dentro do escopo da seção de implementação
de uma única unidade.
Os dados globais a serem usados por diversas unidades deverão ser movidos para uma unidade comum, usada por todas.
Os dados globais podem ser inicializadas com um valor diretamente na seção var. Lembre-se de que
todos os dados globais são automaticamente inicializados com zero; portanto, não inicialize variáveis
globais como valores “vazios”, como 0, nil, ’’, Unassigned e assim por diante. Um motivo para isso é que
os dados globais inicializados com zero não ocupam espaço no arquivo EXE. Dados inicializados com
zero são armazenados em um segmento de dados virtual alocado apenas na memória quando a aplicação
é inicializada. Dados globais inicializados com valor diferente de zero ocupam espaço no arquivo EXE
do disco.
Tipos
Convenção para uso de maiúsculas
Os nomes de tipo que são palavras reservadas ficarão completamente em minúsculas. Os tipos da API do
Win32 geralmente aparecem totalmente em maiúsculas, e você deverá seguir a convenção para um nome
de tipo em particular, mostrado na unidade Windows.pas ou outra unidade da API. Para outros nomes de
variáveis, a primeira letra deverá ser maiúscula e o restante deverá usar maiúsculas no início de cada palavra intermediária. Veja alguns exemplos:
var
MyString: string;
WindowHandle: HWND;
I: Integer;
// palavra reservada
// tipo da API do Win32
// identificador de tipo introduzido na unidade System
Tipos de ponto flutuante
O uso do ripo Real é desencorajado, pois só existia por compatibilidade com o código mais antigo do
Pascal. Embora agora ele seja o mesmo que Double, esse fato poderá ser confuso para outros programadores. Use Double para as necessidades gerais de ponto flutuante. Além disso, as instruções e os barramentos do processador são otimizados para trabalhar com Double, sendo também o formato de dados
padrão definido pelo IEEE. Use Extended apenas quando for necessária uma faixa maior do que a oferecida por Double. Extended é um tipo específico da Intel, e não é aceito em Java. Use Single apenas quando o tamanho físico dos bytes da variável de ponto flutuante for significativo (como ao usar DLLs de
outras linguagens).
5
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tipos enumerados
Os nomes para tipos enumerados deverão ser significativos para a finalidade da enumeração. O nome do
tipo deve ter como prefixo o caracter T para anotá-lo como uma declaração de tipo. A lista de identificadores do tipo enumerado deverá conter um prefixo de dois ou três caracteres minúsculos, que o relacione com o nome do tipo enumerado original. Veja um exemplo:
TTipoMúsica = (stRock, stClassical, stCountry, stAlternative, stHeavyMetal, stGospel);
As instâncias de variável de um tipo enumerado receberão o mesmo nome do tipo, sem o prefixo T
(TipoMúsica), a menos que haja um motivo para dar à variável um nome mais específico, como em TipoMúsicaFavorita1, TipoMúsicaFavorita2 etc.
Tipos Variant e OleVariant
O uso dos tipos Variant e OleVariant é desencorajado em geral, mas esses tipos são necessários à programação quando os tipos de dados são conhecidos apenas em runtime, como normalmente acontece no desenvolvimento COM e de banco de dados. Use OleVariant para a programação baseada em COM, como
controles Automation e ActiveX, e use Variant para a programação que não seja COM. O motivo é que
uma Variant pode armazenar de modo eficiente as strings nativas do Delphi (como uma string var), mas
OleVariant converte todas as strings em strings OLE (strings WideChar) e não são contadas como referência;
em vez disso, elas são sempre copiadas.
Tipos estruturados
Tipos de array
Os nomes para os tipos de array devem ser indicativos da finalidade do array. O nome do tipo deverá ter
como prefixo o caracter T. Se for declarado um ponteiro para o tipo de array, ele deverá ter como prefixo
o caracter P e deverá ser declarado antes da declaração de tipo. Veja um exemplo:
type
PArrayCiclo = ^TArrayCiclo;
TArrayCiclo = array[1..100] of integer;
Quando for prático, as instâncias de variáveis do tipo array deverão receber o mesmo nome do tipo
sem o prefixo T.
Tipos de registro
Um tipo de registro deverá receber um nome indicativo de sua finalidade. A declaração de tipo deverá ter
como prefixo o caracter T. Se for declarado um ponteiro para o tipo de registro, ele deverá ter como prefixo o caracter P e deverá ser declarado antes da declaração de tipo. A declaração de tipo para cada elemento poderá ser alinhada opcionalmente em uma coluna à direita. Veja um exemplo:
type
PFuncionário = ^Tfuncionário;
TFuncionário = record
NomeFunc: string
SalárioFunc: Double;
end;
6
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Instruções
Instruções if
O caso mais provável de ser executado em uma instrução if/then/else deverá ser colocado na cláusula
then, com os casos menos prováveis residindo na(s) cláusula(s) else.
Tente evitar o encadeamento de instruções if e use instruções case sempre que possível.
Não aninhe instruções if com mais de cinco níveis de profundidade. Crie um método mais claro
para o código.
Não use parênteses desnecessários em uma instrução if.
Se várias condições estiverem sendo testadas em uma instrução if, as condições deverão ser organizadas da esquerda para a direita na ordem de intensidade de cálculos, da menor para a maior. Isso permite que o seu código aproveite a lógica de avaliação Boolean de curto circuito embutida no compilador.
Por exemplo, se a Condição1 for mais rápida do que a Condição2, e a Condição2 for mais rápida do que a Condição3, então a instrução if deverá ser construída da seguinte forma:
if Condição1 and Condição2 and Condição3 then
Instruções case
Tópicos gerais
Os casos individuais em uma instrução case deverão ser ordenados pela constante de caso, seja numérica
ou alfabeticamente.
As instruções de ações de cada caso deverão ser simples e geralmente não deverão exceder quatro
ou cinco linhas de código. Se as ações forem mais complexas, o código deverá ser colocado em um procedimento ou função separada.
A cláusula else de uma instrução case deverá ser usada apenas para padrões legítimos ou para detectar erros.
Formatação
As instruções case seguem as mesmas regras de formatação das outras construções com relação às convenções de endentação e nomeação.
Instruções while
O uso do procedimento Exit para sair de um loop while é desencorajado; sempre que possível, você deverá sair do loop usando apenas a condição do loop.
Todo o código de inicialização de um loop while deverá ocorrer diretamente antes da entrada do
loop [whileTN] e não deverá ser separado por outras instruções não relacionadas.
Qualquer trabalho de encerramento deverá ser feito imediatamente após o loop.
Instruções for
As instruções for deverão ser usadas no lugar das instruções while quando o código tiver de ser executado
por um número conhecido de incrementos.
Instruções repeat
As instruções repeat são semelhantes aos loops while e deverão seguir as mesmas orientações gerais.
7
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Instruções with
Tópicos gerais
A instrução with deverá ser usada com reservas e com considerável atenção. Evite o uso demasiado de instruções with e cuidado com o uso de vários objetos, registros etc. na instrução with. Por exemplo:
with Registro1, Registro2 do
pode confundir o programador e facilmente levar a bugs difíceis de se detectar.
Formatação
As instruções with seguem as mesmas regras de formatação em relação às convenções de nomeação e endentação, descritas anteriormente neste documento.
Tratamento estruturado de exceções
Tópicos gerais
O tratamento de exceções deverá ser usado abundantemente para correção de erro e proteção de recursos.
Isso significa que, em todos os casos em que são alocados recursos, um try..finally deverá ser usado para
garantir a desalocação correta do recurso. A exceção a essa regra envolve casos nos quais os recursos são
alocados/liberados na inicialização/finalização de uma unidade ou do construtor/destruidor de um objeto.
Uso de try..finally
Sempre que possível, cada alocação deverá ser combinada com uma construção try..finally. Por exemplo, o código a seguir poderia levar a possíveis bugs:
AlgumaClasse1 := TAlgumaClasse.Create;
AlgumaClasse2 := TAlgumaClasse.Create;
try
{ realiza algum código }
finally
AlgumaClasse1.Free;
AlgumaClasse2.Free;
end;
Um método mais seguro para a alocação anterior seria este:
AlgumaClasse1 := TAlgumaClasse.Create
try
AlgumaClasse2 := TAlgumaClasse.Create;
try
{ realiza algum código }
finally
AlgumaClasse2.Free;
end;
finally
AlgumaClasse1.Free;
end;
Uso de try..except
Use try..except apenas quando você quiser realizar alguma tarefa se surgir alguma exceção. Em geral,
você
não deverá usar try..except simplesmente para mostrar uma mensagem de erro na tela, pois isso será
8
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
feito automaticamente no contexto de uma aplicação pelo objeto Application. Se você quiser chamar o
tratamento padrão de exceção depois de ter realizado alguma tarefa na cláusula except, use raise para levantar a exceção novamente para o próximo manipulador.
Uso de try..except..else
O uso da cláusula else com try..except é desencorajado, pois bloqueará todas as exceções, até mesmo
aquelas para as quais você pode não estar preparado.
Classes
Nomeação/formatação
Os nomes de tipo para as classes deverão indicar claramente a finalidade da classe. O nome do tipo deverá ter o prefixo T para indicá-lo como uma definição de tipo. Veja um exemplo:
type
TCliente = class(TObject)
Os nomes de instância para as classes geralmente combinarão com o nome do tipo da classe, mas
sem o prefixo T:
var
Cliente: TCliente;
NOTA
Consulte a seção “Padrões de nomeação de tipo de componente” para obter mais informações sobre a nomeação de componentes.
Campos
Nomeação/formatação
Os nomes de campo de classe seguem as mesmas convenções de nomeação dos identificadores de variável, exceto por terem a indicação F como prefixo, indicando que são nomes de campo (field).
Visibilidade
Todos os campos deverão ser privados. Os campos que são acessíveis fora do escopo da classe deverão se
tornar acessíveis através do uso de uma propriedade.
Métodos
Nomeação/formatação
Os nomes de métodos seguem as mesmas convenções de nomeação descritas para procedimentos e funções neste documento.
Uso de métodos estáticos
Use métodos estáticos quando você não quiser que um método seja modificado por classes descendentes.
Uso de métodos virtuais/dinâmicos
Use métodos virtuais quando quiser que um método seja modificado por classes descendentes. Os métodos dinâmicos só deverão ser usados em classes das quais haverá muitos descendentes (diretos ou indire- 9
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
tos). Por exemplo, ao trabalhar com uma classe que contenha um método pouco modificado e 100 classes descendentes, você deverá tornar o método dinâmico, a fim de reduzir o uso de memória pelas 100
classes descendentes.
Uso de métodos abstratos
Não use métodos abstratos sobre classes das quais serão criadas instâncias. Use métodos abstratos apenas
sobre classes básicas que nunca serão criadas.
Métodos de acesso a propriedades
Todos os métodos de acesso deverão aparecer nas seções private ou protected da definição de classe.
As convenções de nomeação para os métodos de acesso a propriedades seguem as mesmas regras
dos procedimentos e funções. O método de acesso de leitura (método leitor) deverá ter como prefixo a
palavra Get.
O método de acesso de escrita (método escritor) deverá ter como prefixo a palavra Set. O parâmetro para o método escritor deverá ter o nome Value, e seu tipo deverá ser o da propriedade que ele representa. Veja um exemplo:
TAlgumaClasse = class(TObject)
private
FAlgumCampo: Integer;
protected
function GetAlgumCampo: Integer;
procedure SetAlgumCampo( Valor: Integer);
public
property AlgumCampo: Integer read GetAlgumCampo write SetAlgumCampo;
end;
Propriedades
Nomeação/formatação
As propriedades que servem como acessos para campos privados terão nomes iguais aos campos que elas
representam, sem o indicador F.
Os nomes de propriedade deverão ser substantivos, e não verbos. As propriedades representam dados; os métodos representam ações.
Os nomes de propriedade de array deverão estar no plural. Os nomes de propriedade normais deverão estar no singular.
Uso de métodos de acesso
Embora não seja obrigatório, encoraja-se o uso de no mínimo um método de acesso de escrita para as
propriedades que representam um campo privado.
Arquivos
Arquivos de projeto
Nomeação
Os arquivos de projeto deverão ter nomes descritivos. Por exemplo, o Delphi 5 Developer’s Guide Bug
Manager recebeu o nome de projeto DDGBugs.dpr. Um programa de informações do sistema poderá ter um
10 nome como InfoSis.dpr.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Arquivos de formulário
Nomeação
Um arquivo de formulário deverá ter um nome descritivo da finalidade do formulário, finalizado com os
caracteres Frm. Por exemplo, o formulário Sobre teria o nome de arquivo SobreFrm.dpr, e um formulário
Principal teria o nome de arquivo PrincipalFrm.dpr.
Arquivos de módulo de dados
Nomeação
Um módulo de dados deverá receber um nome que descreva a finalidade desse módulo. O nome deverá
ser finalizado com os caracteres DM. Por exemplo, um módulo de dados Clientes terá o nome de arquivo
ClientesDM.dfm.
Arquivos remotos de módulo de dados
Nomeação
Um módulo de dados remoto deverá receber um nome que descreva a finalidade do módulo de dados remoto. O nome deverá ser finalizado com os caracteres RDM. Por exemplo, um módulo de dados remoto
Clientes teria o nome de arquivo ClientesRDM.dfm.
Arquivos de unidade
Estrutura geral da unidade
Nome da unidade
Os arquivos de unidade receberão nomes descritivos. Por exemplo, a unidade contendo o formulário
principal de uma aplicação poderia ser chamada MainFrm.pas.
A cláusula uses
Uma cláusula uses na seção interface deverá conter apenas unidades exigidas pelo código na seção interRemova quaisquer nomes de unidade desnecessários, que possam ter sido inseridos automaticamente pelo Delphi.
Uma cláusula uses na seção implementation deverá conter apenas as unidades exigidas pelo código na
seção implementation. Remova quaisquer nomes de unidade desnecessários.
face.
A seção interface
A seção interface deverá conter declarações apenas para os tipos, variáveis, declarações de encaminhamento de procedimento/função e outros que precisarem ser acessíveis a unidades externas. Caso contrário, essas declarações deverão entrar na seção implementation.
A seção implementation
A seção implementation deverá conter quaisquer declarações de tipos, variáveis, procedimentos/funções e
outros que sejam privados à unidade que a contém.
11
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
A seção inicialization
Não inclua código demorado na seção inicialization de uma unidade. Isso fará com que a aplicação pareça ser lenta logo na partida.
A seção finalization
Não se esqueça de desalocar quaisquer itens que tenham sido alocados na seção inicialization.
Unidades de formulário
Nomeação
Um arquivo de unidade para um formulário deverá receber o mesmo nome do seu arquivo de formulário
correspondente. Por exemplo, um formulário Sobre teria o nome de unidade SobreFrm.pas, e um formulário Principal teria o nome de arquivo de unidade PrincipalFrm.pas.
Unidades de módulo de dados
Nomeação
Os arquivos de unidade ou módulos de dados deverão receber os mesmos nomes de seus arquivos de formulário correspondentes. Por exemplo, uma unidade de módulo de dados Clientes teria o nome de unidade ClientesDM.pas.
Unidades de uso geral
Nomeação
Uma unidade de uso geral receberá um nome indicativo da finalidade da unidade. Por exemplo, uma unidade de utilitários receberia o nome BugUtilitários.pas, e uma unidade contendo variáveis globais receberia o nome ClienteGlobais.pas.
Lembre-se de que os nomes de unidade precisam ser exclusivos em todos os pacotes usados por um
projeto. Nomes de unidade genéricos ou comuns não são recomendados.
Unidades de componentes
Nomeação
As unidades de componentes deverão ser incluídas em um diretório separado para distingui-las como
unidades definindo componentes ou conjuntos de componentes. Elas nunca deverão ser colocadas no
mesmo diretório do projeto. O nome da unidade deverá indicar o seu conteúdo.
NOTA
Consulte a seção “Componentes definidos pelo usuário” para obter mais informações sobre padrões de
nomeação de componentes.
Cabeçalhos de arquivo
O uso de cabeçalhos de arquivo informativos é encorajado para todos os arquivos de código-fonte, arquivos de projeto, unidades etc. Um cabeçalho de arquivo apropriado deverá incluir a seguinte informação:
{
Copyright © ANO by AUTORES
12 }
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Formulários e módulos de dados
Formulários
Padrão de nomeação de tipo de formulário
Os tipos de formulário deverão ter nomes que descrevam a finalidade do formulário. A definição de tipo
deverá ter o prefixo T, e um nome descritivo deverá aparecer após o prefixo. Finalmente, From deverá ser
inserido no final do nome descritivo. Por exemplo, o nome de tipo para um formulário Sobre seria:
TSobreForm = class(TForm)
A definição de um formulário principal seria:
TPrincialForm – class(TForm)
O formulário de entrada de clientes teria um nome como:
TEntradaClientesForm = class(TForm)
Padrão de nomeação de instância de formulário
As instâncias de formulário deverão ter o mesmo nome de seus tipos correspondentes, porém sem o prefixo T. Por exemplo, para os tipos de formulário anteriores, os nomes de instância são os seguintes:
Nome do tipo
Nome da instância
TSobreForm
SobreForm
TPrincipalForm
PrincipalForm
TEntradaClientesForm
EntradaClientesForm
Criação automática de formulários
Somente o formulário principal deverá ser criado automaticamente, a menos que haja um bom motivo
para se fazer de outra forma. Todos os outros formulários deverão ser removidos da lista Autocreate na
caixa de diálogo Project Options (opções do projeto). Veja mais informações na próxima seção.
Funções de instanciação de formulário modal
Todas as unidades de formulário deverão conter uma função de instanciação de formulário que cria,
configura e exibe o formulário de forma modal, além de liberar o formulário. Essa função deverá retornar o resultado modal retornado pelo formulário. Os parâmetros passados a essa função deverão seguir o
padrão de passagem de parâmetros, especificado neste documento. Essa funcionalidade deverá estar encapsulada dessa maneira para facilitar a reutilização e a manutenção do código.
A variável de formulário deverá ser removida da unidade e declarada localmente na função de instanciação de formulário. (Observe que isso exige que o formulário seja removido da lista Autocreate na
caixa de diálogo Project Options. Ver “Criação automática de formulários”, anteriormente neste documento.)
Por exemplo, a unidade a seguir ilustra tal função para um formulário GetUserData:
unit UserDataFrm;
interface
13
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TUserDataForm = class(TForm)
edtUserName: Tedit;
edtUserID: Tedit;
private
{ declarações privadas }
public
{ declarações públicas }
end;
function GetuserData(var aUserName: String; var aUserID: Integer): Word;
implementation
{$R *.DFM}
function GetUserData(var aUserName: String; var aUserID: Integer): Word;
var
UserDataForm: TUserDataForm;
begin
UserDataForm := TUserDataForm.Create(Application);
try
UserDataForm.Caption := ‘Getting User Data’;
Result := UserDataForm.ShowModal;
if ( Result = mrOK ) then begin
aUserName := UserDataForm.edtUserName.Text;
aUserID
:= StrToInt(UserDataForm.edtUserID.Text);
end;
finally
UserDataForm.Free;
end;
end;
end.
Módulos de dados
Padrão de nomeação de módulo de dados
Um tipo DataModule receberá um nome descritivo da finalidade do módulo de dados. A definição de tipo terá
um prefixo T, e um nome descritivo deverá acompanhar o prefixo. Finalmente, o nome será finalizado com
a palavra DataModule. Por exemplo, o nome do tipo para um módulo de dados Cliente seria algo assim:
TMóduloDadosCliente = class(TDataModule)
De modo semelhante, o módulo de dados de Pedidos poderia ter o seguinte nome:
TMóduloDadosPedidos = class(TDataModule)
Padrão de nomeação de instância do módulo de dados
As instâncias do módulo de dados terão nomes iguais aos seus tipos correspondentes, porém sem o prefi14 xo T. Por exemplo, para os tipos de formulário anteriores, os nomes de instância são os seguintes:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Nome do tipo
Nome da instância
TMóduloDadosCliente
MóduloDadosCliente
TMóduloDadosPedidos
MóduloDadosPedidos
Pacotes
Uso de pacotes de runtime ou projeto
Os pacotes de runtime terão apenas as unidades/componentes exigidos por outros componentes nesse
pacote. Outras unidades contendo editores de propriedade/componente e outro código apenas de projeto deverá ser colocado em um pacote de projeto. Unidades de registro também deverão ser colocadas em
um pacote de projeto.
Padrões de nomeação de arquivo
Os pacotes terão nomes de acordo com os seguintes modelos:
l
iiilibvv.dpk
(pacote de projeto)
l
iiistdvv.dpk
(pacote de runtime)
Aqui, os caracteres iii significam um prefixo de identificação de três caracteres. Esse prefixo poderá ser usado para identificar uma empresa, pessoa ou qualquer outra entidade de identificação.
Os caracteres vv significam uma versão para o pacote correspondente à versão do Delphi para a qual
o pacote é visado.
Observe que o nome do pacote contém lib ou std para representá-lo como um pacote de runtime ou
em tempo de projeto.
Em casos em que existem pacotes tem tempo de projeto e runtime, os arquivos terão nomes semelhantes. Por exemplo, os pacotes para o Guia do Programador Delphi 5 têm os seguintes nomes:
l
DdgLib50.dpk
(pacote de projeto)
l
DdgStd50.dpk
(pacote de runtime)
Componentes
Componentes definidos pelo usuário
Padrões de nomeação de tipo de componente
Os componentes terão nomes semelhantes às classes definidas na seção “Classes”, com a exceção de que
os componentes recebem um prefixo de identificação de três caracteres. Esse prefixo poderá ser usado
para identificar uma empresa, pessoa ou qualquer outra entidade. Por exemplo, um componente de
clock escrito para o Guia do Programador Delphi 5 seria definido da seguinte forma:
TddgClock = class(TComponent)
Observe que o prefixo de três caracteres está em letras minúsculas.
Unidades de componentes
As unidades de componentes deverão conter apenas um componente principal. Um componente principal é qualquer componente que apareça na Component Palette (palheta de componentes). Quaisquer
componentes/objetos auxiliares também poderão residir na mesma unidade do componente principal. 15
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Uso de unidades de registro
O procedimento de registro para os componentes deverá ser removido da unidade de componentes e colocado em uma unidade separada. Essa unidade de registro será usada para registrar quaisquer componentes, editores de propriedade, editores de componentes, experts e assim por diante.
O registro de componentes deverá ser feito apenas nos pacotes de projeto; portanto, a unidade de
registro deverá estar contida no pacote de projeto e não no pacote de runtime.
Sugere-se que as unidades de registro tenham o seguinte nome:
XxxReg.pas
Aqui, Xxx é um prefixo de três caracteres usado para identificar uma empresa, pessoa ou qualquer
outra entidade. Por exemplo, a unidade de registro para os componentes do Guia do Programador Delphi 5 se chamariam DdgReg.pas.
Convenções de nomeação de instância de componente
Todos os componentes deverão ter nomes descritivos. Nenhum componente ficará sem os nomes padrão
atribuídos a eles pelo Delphi. Os componentes serão nomeados por meio de uma variação da convenção
de nomeação húngara. De acordo com esse padrão, o nome do componente consistirá em duas partes:
um prefixo de tipo de componente e um nome qualificador.
Prefixos de tipo de componente
O prefixo de tipo de componente é um conjunto de letras minúsculas que representam o tipo de componente. Por exemplo, a seguir vemos prefixos de tipo de componente válidos para os componentes especificados.
TButton
TEdit
TSpeedButton
TListBox
btn
edt
spdbtn
lstbx
Como vimos, o prefixo de tipo de componente é criado modificando-se o nome do tipo de componente (por exemplo, TButton, TEdit) para um prefixo. As regras a seguir ilustram como definir um prefixo de tipo de componente.
1.
2.
3.
4.
Remova quaisquer prefixos “T” do tipo de nome de componente. Por exemplo, “TButton” torna-se “Button”.
Remova quaisquer vogais do nome formado na etapa 1, exceto quando a vogal inicia o nome. Por
exemplo, “Button” torna-se “bttn” e “Edit” torna-se “edt”.
Suprima consoantes duplas. Por exemplo, “bttn” torna-se “btn”.
Se ocorrer um conflito de nome, comece incluindo vogais ao prefixo para um dos componentes.
Por exemplo, se um novo componente “TBatton” for incluído, ele entrará em conflito com “TButton”. portanto, o prefixo para “TBatton” poderia ser “batn”.
Nome de qualificador de componente
O nome do qualificador de componente deverá descrever a finalidade do componente. Por exemplo, um
componente TButton com a finalidade de fechar um formulário teria o nome “btnFechar”. Um componente TEdit usado para editar o nome de uma pessoa teria o nome “edtNome”.
16
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Controles ActiveX
com Delphi
CAPÍTULO
7
NE STE CAP ÍT UL O
l
O que é um controle ActiveX?
l
Quando um controle ActiveXdeve ser utilizado
l
Incluindo um controle ActiveX na Component
Palette
l
O wrapper de componentes do Delphi
l
Usando controles ActiveX em suas aplicações
l
Distribuindo aplicações equipadas com controle
ActiveX
l
Registro do controle ActiveX
l
BlackJack: um exemplo de aplicação OCX
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 2 — 2ª PROVA
O Delphi oferece a grande vantagem de integrar com facilidade os controles ActiveX padrão da indústria
(anteriormente conhecidos como controles OCX ou OLE) em suas aplicações. Ao contrário dos próprios
componentes personalizados do Delphi, os controles ActiveX são projetados para serem independentes
de qualquer ferramenta de desenvolvimento em particular. Isso significa que você pode contar com muitos fornecedores para obter uma grande variedade de soluções ActiveX que abrem um grande leque de
recursos e funcionalidade.
O suporte para controle ActiveX no Delphi de 32 bits funciona de modo semelhante ao suporte
para VBX no Delphi 1 de 16 bits. Você seleciona uma opção para incluir novos controles ActiveX a partir do menu principal do IDE do Delphi ou do editor de pacotes, e o Delphi cria um wrapper do Object
Pascal para o controle ActiveX, que é então compilado em um pacote e incluído na Component Palette
do Delphi. Estando lá, o controle ActiveX é integrado de modo transparente à Component Palette, junto
com os seus outros componentes da VCL e ActiveX. A partir desse ponto, você está a apenas um clique e
um arrasto da inclusão do controle ActiveX em qualquer uma de suas aplicações. Este capítulo discute a
integração de controles ActiveX no Delphi, o uso de um controle ActiveX na sua aplicação e a distribuição de aplicações equipadas com ActiveX.
NOTA
O Delphi 1 foi a última versão do Delphi a dar suporte para controles VBX (Visual Basic Extension). Se você tiver um projeto do Delphi 1 que se baseie em um ou mais controles VBX, verifique com os fornecedores de VBX
para saber se eles fornecem uma solução ActiveX compatível para usar em suas aplicações Delphi de 32 bits.
O que é um controle ActiveX?
Controles ActiveX são controles personalizados para aplicações de 16 bits e 32 bits do Windows, que
tiram proveito das tecnologias OLE e ActiveX baseadas em COM. Ao contrário dos controles VBX,
que são projetados para o Visual Basic de 16 bits (e, portanto, compartilham as limitações do Visual
Basic), os controles ActiveX foram projetados desde o início visando à independência de aplicativo.
Resumidamente, você pode pensar nos controles ActiveX como uma mistura da tecnologia VBX de fácil utilização com o padrão ActiveX aberto. Para fins deste capítulo, você pode pensar no OLE e no
ActiveX como a mesma coisa. Se você estiver procurando maiores distinções entre esses termos, dê
uma olhada no Capítulo 23.
Debaixo da superfície, um controle ActiveX é na realidade um servidor ActiveX que, em um pacote, pode oferecer todo o poder do ActiveX – incluindo todas as funções e serviços do OLE, edição visual,
arrastar e soltar e OLE Automation. Assim como todos os servidores ActiveX, os controles ActiveX são
registrados no Registro do Sistema. Os controles ActiveX podem ser desenvolvidos através de diversos
produtos, incluindo Delphi, Borland C++Builder, Visual C++ e Visual Basic.
A Microsoft está promovendo ativamente os controles ActiveX como o meio escolhido para os controles personalizados independentes do aplicativo; a Microsoft afirmou que a tecnologia VBX não será
diretamente aceita nos sistemas operacionais Win32 em diante. Por esse motivo, os programadores deverão utilizar controles ActiveX em vez de controles VBX quando desenvolverem aplicações de 32 bits.
NOTA
Para obter uma descrição mais completa sobre a tecnologia de controle ActiveX, consulte o Capítulo 25.
Quando um controle ActiveX deve ser utilizado
18
Geralmente existem dois motivos para você usar um controle ActiveX em vez de um controle nativo do
Delphi. O primeiro deles é que nenhum componente do Delphi disponível atende à sua necessidade em
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
particular. Como o mercado de controle ActiveX é maior do que o de controles da VCL, você provavelmente encontrará uma maior variedade de controles de “peso industrial” e com todos os recursos, como
processadores de textos, browsers da World Wide Web e planilhas, sob a forma de controles ActiveX. O
segundo motivo para você usar um controle ActiveX em vez de um controle nativo do Delphi é se você
desenvolve em várias linguagens de programação e deseja aproveitar seu trabalho com determinado controle ou controles entre as várias plataformas de desenvolvimento.
Embora os controles ActiveX sejam integrados de modo transparente no IDE do Delphi, lembre-se
de algumas desvantagens inerentes ao uso de controles ActiveX nas suas aplicações. O aspecto mais óbvio é que, embora os componentes do Delphi sejam montados diretamente no executável de uma aplicação, os controles ActiveX geralmente exigem um ou mais arquivos de runtime adicionais, que precisam
ser distribuídos junto com o executável. Outro problema é que os controles ActiveX se comunicam com
as aplicações por meio da camada COM, enquanto os componentes do Delphi se comunicam diretamente com as aplicações e outros componentes. Isso significa que um componente do Delphi bem escrito
funciona melhor do que um controle ActiveX bem escrito. Uma desvantagem mais sutil dos controles
ActiveX é que eles são uma solução do tipo “denominador comum”, de modo que não explorarão todos
os recursos da ferramenta de desenvolvimento em que são utilizados.
Inclusão de um controle ActiveX na Component Palette
A primeira etapa no uso de um controle ActiveX em particular na sua aplicação do Delphi é a inclusão
desse controle na Component Palette do IDE do Delphi. Isso coloca um ícone para o controle ActiveX na
Component Palette, entre seus outros controles do Delphi e ActiveX. Depois de incluir um controle
ActiveX qualquer na Component Palette, você poderá colocá-lo em qualquer formulário e usá-lo como
faria com qualquer outro controle do Delphi.
Para incluir um controle ActiveX na Component Palette, siga estas etapas:
1. Escolha Component, Import ActiveX Control (importar controle ActiveX) a partir do menu principal. A caixa de diálogo Import ActiveX (importar ActiveX) aparece (ver Figura 7.1).
FIGURA 7.1
2.
3.
A caixa de diálogo Import ActiveX.
A caixa de diálogo Import ActiveX é dividida em duas partes: a parte superior contém uma caixa de
listagem com controles ActiveX registrados e oferece botões Add (adicionar) e Remove (remover)
que lhe permitem registrar ou retirar os controles do registro. A parte inferior da caixa de diálogo
lhe permite especificar parâmetros para a criação de um componente e unidade do Delphi que encapsule o controle.
Se o nome do controle ActiveX que você deseja usar estiver listado na parte superior da caixa de
diálogo, prossiga para a etapa 4. Caso contrário, dê um clique no botão Add para registrar um novo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
19
4.
controle com o sistema. Um clique no botão Add faz surgir a caixa de diálogo Register OLE Control
(registrar controle OLE; ver Figura 7.2). Selecione o nome do arquivo OCX ou DLL que representa
o controle ActiveX que você deseja incluir no sistema e dê um clique no botão Open. Isso registra o
controle ActiveX selecionado com o Registro do Sistema e fecha a caixa de diálogo Register OLE
Control.
Na parte superior da caixa de diálogo Import ActiveX, selecione o nome do controle ActiveX que
você deseja incluir na Component Palette. A parte inferior da caixa de diálogo contém controles de
edição para o nome do diretório da unidade, a página da palheta e o caminho de consulta, além de
um controle memo que lista as classes contidas dentro do arquivo OCX. O nome do caminho mostrado na caixa de edição Unit Dir Name (nome do diretório da unidade) é o caminho do componente de wrapper do Delphi criado para realizar a interface com o controle ActiveX. O nome do arquivo usa como padrão o nome do arquivo OCX (com uma extensão pas); o caminho usa como padrão
o subdiretório \Delphi5\Imports. Embora o padrão seja indicado para uso, você pode editar o caminho do diretório como desejar.
FIGURA 7.2
5.
6.
7.
8.
A caixa de diálogo Register OLE Control.
O controle de edição Palette Page (página da palheta) na caixa de diálogo Import ActiveX contém o
nome da página na Component Palette onde você deseja que esse controle resida. O padrão é a página ActiveX. Você pode escolher outra página existente; como alternativa, se você criar um nome
novo, uma página correspondente será criada na Component Palette.
O controle de memo Class Names (nomes de classe) na caixa de diálogo Import ActiveX contém os
nomes dos novos objetos criados nesse controle. Você normalmente deverá deixar esses nomes definidos com o padrão, a menos que tenha um motivo específico para fazer de outra forma. Por
exemplo, um motivo seria se o nome da classe padrão entrar em conflito com outro componente já
instalado no IDE.
Nesse ponto, você poderá dar um clique no botão Install (instalar) ou Create Unit (criar unidade) da
caixa de diálogo Import ActiveX. O botão Create Unit gerará o código-fonte da unidade para o
wrapper do componente de controle ActiveX. O botão Install gerará o código do wrapper e depois
chamará a caixa de diálogo Install, que lhe permite escolher um pacote em que o componente poderá ser instalado (ver Figura 7.3).
Na caixa de diálogo Install, você poderá incluir o controle em um pacote existente ou criar um novo
pacote que será instalado na Component Palette. Dê um clique em OK nessa caixa de diálogo e o
componente será instalado na palheta.
Agora o seu controle ActiveX está na Component Palette e pronto para cumprir seu papel.
20
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
A caixa de diálogo Install.
FIGURA 7.3
O wrapper de componentes do Delphi
Agora é uma boa ocasião para examinarmos o wrapper do Object Pascal criado para encapsular o controle ActiveX. Isso poderá ajudar a esclarecer como funciona o suporte ao ActiveX no Delphi, de modo que
você entenda os recursos e as limitações inerentes aos controles ActiveX. A Listagem 7.1 mostra a unidade Card_TLB.pas gerada pelo Delphi; essa unidade encapsula o controle ActiveX AxCard.ocx.
NOTA
AxCard.ocx é um controle ActiveX desenvolvido no Capítulo 25.
Listagem 7.1 A unidade do wrapper de componentes do Delphi para AxCard.ocx.
unit AxCard_TLB;
//
//
//
//
//
//
//
//
//
//
//
******************************************************************* //
AVISO
–––Os tipos declarados neste arquivo foram gerados a partir de dados
lidos em uma biblioteca de tipos. Se essa biblioteca de tipos for
novamente importada, explícita ou indiretamente (por meio de outra
biblioteca de tipos referindo-se a esta biblioteca de tips), ou se o
comando ‘Refresh’ do Type Library Editor for ativado enquanto edita
a biblioteca de tipos, o conteúdo deste arquivo será gerado novamente
e todas as modificações manuais serão perdidas.
****************************************************************** //
// PASTLWTR : $Revision:
1.88 $
// Arquivo gerado em 24/8/99 9:24:19 AM pela Type Library descrita a seguir
//
//
//
//
//
//
//
//
//
//
//
//
//
********************************************************************//
NOTA:
Os itens guardados por $IFDEF_LIVE_SERVER_AT_DESIGN_TIME são usados
pelas propriedades que retornam objetos que podem exigir criação
explícita por meio de uma chamada de função antes de qualquer acesso
através da propriedade. Esses itens foram desativados para evitar o
uso acidental dentro do Object Inspector.
Você poderá ativá-los definindo LIVE_SERVER_AT_DESIGN_TIME ou
removendo-os seletivamente a partir de blocos $IFDEF. No entanto,
tais itens ainda precisam ser criados programaticamente por meio de
um método da CoClass apropriada antes que possam ser usados.
****************************************************************** //
Type Lib: C:\work\d5dg\code\Ch25\AxCard\AxCard.tlb (1)
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
21
Listagem 7.1 Continuação
// IID\LCID: {7B33D940-0A2C-11D2-AE5C-04640BC10000}\0
// Helpfile:
// DepndLst:
// (1) v2.0 stdole, (C:\WINDOWS\SYSTEM\STDOLE2.TLB)
// (2) v4.0 StdVCL, (C:\WINDOWS\SYSTEM\STDVCL40.DLL)
// ****************************************************************** //
{$TYPEDADDRESS OFF} // Unidade precisa ser compilada sem ponteiros
// com tipo verificado.
interface
uses Windows, ActiveX, Classes, Graphics, OleServer, OleCtrls, StdVCL;
// ********************************************************************//
// GUIDS declarados na TypeLibrary. Os seguintes prefixos são usados:
// Bibliotecas de tipo
: LIBID_xxxx
// CoClasses
: CLASS_xxxx
// DISPInterfaces
: DIID_xxxx
// Interfaces não-DISP
: IID_xxxx
// ********************************************************************//
const
// TypeLibrary – versões principal e secundária
AxCardMajorVersion = 1;
AxCardMinorVersion = 0;
LIBID_AxCard: TGUID = ‘{7B33D940-0A2C-11D2-AE5C-04640BC10000}’;
IID_ICardX: TGUID = ‘{7B33D941-0A2C-11D2-AE5C-04640BC10000}’;
DIID_ICardXEvents: TGUID = ‘{7B33D943-0A2C-11D2-AE5C-04640BC10000}’;
CLASS_CardX: TGUID = ‘{7B33D945-0A2C-11D2-AE5C-04640BC10000}’;
// ********************************************************************//
// Declaração de enumerações definidas na biblioteca de tipos
// ********************************************************************//
// Constantes para enum TxDragMode
type
TxDragMode = TOleEnum;
const
dmManual = $00000000;
dmAutomatic = $00000001;
// Constantes para enum TxCardSuit
type
TxCardSuit = TOleEnum;
const
csClub = $00000000;
csDiamond = $00000001;
csHeart = $00000002;
csSpade = $00000003;
22
// Constantes para enum TxCardValue
type
TxCardValue = TOleEnum;
const
cvAce = $00000000;
cvTwo = $00000001;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.1 Continuação
cvThree = $00000002;
cvFour = $00000003;
cvFive = $00000004;
cvSix = $00000005;
cvSeven = $00000006;
cvEight = $00000007;
cvNine = $00000008;
cvTen = $00000009;
cvJack = $0000000A;
cvQueen = $0000000B;
cvKing = $0000000C;
// Constantes para enum TxMouseButton
type
TxMouseButton = TOleEnum;
const
mbLeft = $00000000;
mbRight = $00000001;
mbMiddle = $00000002;
// Constantes para enum TxAlignment
type
TxAlignment = TOleEnum;
const
taLeftJustify = $00000000;
taRightJustify = $00000001;
taCenter = $00000002;
// Constantes para enum TxBiDiMode
type
TxBiDiMode = TOleEnum;
const
bdLeftToRight = $00000000;
bdRightToLeft = $00000001;
bdRightToLeftNoAlign = $00000002;
bdRightToLeftReadingOnly = $00000003;
type
// *******************************************************************//
// Encaminha declaração de tipos definidos na TypeLibrary
// *******************************************************************//
ICardX = interface;
ICardXDisp = dispinterface;
ICardXEvents = dispinterface;
//
//
//
//
*******************************************************************//
Declaração de CoClasses definidas na Type Library
(NOTA: Aqui mapeamos cada CoClass para sua interface padrão)
*******************************************************************//
CardX = ICardX;
// *******************************************************************//
// Interface: ICardX
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
23
Listagem 7.1 Continuação
24
// Flags:
(4416) Dual OleAutomation Dispatchable
// GUID:
{7B33D941-0A2C-11D2-AE5C-04640BC10000}
// *******************************************************************//
ICardX = interface(IDispatch)
[‘{7B33D941-0A2C-11D2-AE5C-04640BC10000}’]
function Get_BackColor: OLE_COLOR; safecall;
procedure Set_BackColor(Value: OLE_COLOR); safecall;
function Get_Color: OLE_COLOR; safecall;
procedure Set_Color(Value: OLE_COLOR); safecall;
function Get_DragCursor: Smallint; safecall;
procedure Set_DragCursor(Value: Smallint); safecall;
function Get_DragMode: TxDragMode; safecall;
procedure Set_DragMode(Value: TxDragMode); safecall;
function Get_FaceUp: WordBool; safecall;
procedure Set_FaceUp(Value: WordBool); safecall;
function Get_ParentColor: WordBool; safecall;
procedure Set_ParentColor(Value: WordBool); safecall;
function Get_Suit: TxCardSuit; safecall;
procedure Set_Suit(Value: TxCardSuit); safecall;
function Get_Value: TxCardValue; safecall;
procedure Set_Value(Value: TxCardValue); safecall;
function Get_DoubleBuffered: WordBool; safecall;
procedure Set_DoubleBuffered(Value: WordBool); safecall;
procedure FlipChildren(AllLevels: WordBool); safecall;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; safecall;
function DrawTextBiDiModeFlagsReadingOnly: Integer; safecall;
function Get_Enabled: WordBool; safecall;
procedure Set_Enabled(Value: WordBool); safecall;
function GetControlsAlignment: TxAlignment; safecall;
procedure InitiateAction; safecall;
function IsRightToLeft: WordBool; safecall;
function UseRightToLeftAlignment: WordBool; safecall;
function UseRightToLeftReading: WordBool; safecall;
function UseRightToLeftScrollBar: WordBool; safecall;
function Get_BiDiMode: TxBiDiMode; safecall;
procedure Set_BiDiMode(Value: TxBiDiMode); safecall;
function Get_Visible: WordBool; safecall;
procedure Set_Visible(Value: WordBool); safecall;
function Get_Cursor: Smallint; safecall;
procedure Set_Cursor(Value: Smallint); safecall;
function ClassNameIs(const Name: WideString): WordBool; safecall;
procedure AboutBox; safecall;
property BackColor: OLE_COLOR read Get_BackColor write Set_BackColor;
property Color: OLE_COLOR read Get_Color write Set_Color;
property DragCursor: Smallint read Get_DragCursor write
Set_DragCursor;
property DragMode: TxDragMode read Get_DragMode write Set_DragMode;
property FaceUp: WordBool read Get_FaceUp write Set_FaceUp;
property ParentColor: WordBool read Get_ParentColor write
Set_ParentColor;
property Suit: TxCardSuit read Get_Suit write Set_Suit;
property Value: TxCardValue read Get_Value write Set_Value;
property DoubleBuffered: WordBool read Get_DoubleBuffered write
Set_DoubleBuffered;
property Enabled: WordBool read Get_Enabled write Set_Enabled;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.1 Continuação
property BiDiMode: TxBiDiMode read Get_BiDiMode write Set_BiDiMode;
property Visible: WordBool read Get_Visible write Set_Visible;
property Cursor: Smallint read Get_Cursor write Set_Cursor;
end;
//
//
//
//
//
*******************************************************************//
DispIntf: ICardXDisp
Flags:
(4416) Dual OleAutomation Dispatchable
GUID:
{7B33D941-0A2C-11D2-AE5C-04640BC10000}
*******************************************************************//
ICardXDisp = dispinterface
[‘{7B33D941-0A2C-11D2-AE5C-04640BC10000}’]
property BackColor: OLE_COLOR dispid 1;
property Color: OLE_COLOR dispid -501;
property DragCursor: Smallint dispid 2;
property DragMode: TxDragMode dispid 3;
property FaceUp: WordBool dispid 4;
property ParentColor: WordBool dispid 5;
property Suit: TxCardSuit dispid 6;
property Value: TxCardValue dispid 7;
property DoubleBuffered: WordBool dispid 10;
procedure FlipChildren(AllLevels: WordBool); dispid 11;
function DrawTextBiDiModeFlags(Flags: Integer): Integer; dispid 14;
function DrawTextBiDiModeFlagsReadingOnly: Integer; dispid 15;
property Enabled: WordBool dispid -514;
function GetControlsAlignment: TxAlignment; dispid 16;
procedure InitiateAction; dispid 18;
function IsRightToLeft: WordBool; dispid 19;
function UseRightToLeftAlignment: WordBool; dispid 24;
function UseRightToLeftReading: WordBool; dispid 25;
function UseRightToLeftScrollBar: WordBool; dispid 26;
property BiDiMode: TxBiDiMode dispid 27;
property Visible: WordBool dispid 28;
property Cursor: Smallint dispid 29;
function ClassNameIs(const Name: WideString): WordBool; dispid 33;
procedure AboutBox; dispid -552;
end;
//
//
//
//
//
*******************************************************************//
DispIntf: ICardXEvents
Flags:
(4096) Dispatchable
GUID:
{7B33D943-0A2C-11D2-AE5C-04640BC10000}
*******************************************************************//
ICardXEvents = dispinterface
[‘{7B33D943-0A2C-11D2-AE5C-04640BC10000}’]
procedure OnClick; dispid 1;
procedure OnDblClick; dispid 2;
procedure OnKeyPress(var Key: Smallint); dispid 7;
end;
//
//
//
//
//
//
*******************************************************************//
Declaração de classe Proxy do controle OLE
Nome do controle
: TCardX
String de ajuda
: CardX Control
Interface padrão
: ICardX
Def. Intf. DISP?
: No
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
25
Listagem 7.1 Continuação
// Interface de evento : ICardXEvents
// TypeFlags
: (34) pode criar controle
// *******************************************************************//
TCardXOnKeyPress = procedure(Sender: TObject; var Key: Smallint) of
object;
26
TCardX = class(TOleControl)
private
FOnClick: TNotifyEvent;
FOnDblClick: TNotifyEvent;
FOnKeyPress: TCardXOnKeyPress;
FIntf: ICardX;
function GetControlInterface: ICardX;
protected
procedure CreateControl;
procedure InitControlData; override;
public
procedure FlipChildren(AllLevels: WordBool);
function DrawTextBiDiModeFlags(Flags: Integer): Integer;
function DrawTextBiDiModeFlagsReadingOnly: Integer;
function GetControlsAlignment: TxAlignment;
procedure InitiateAction;
function IsRightToLeft: WordBool;
function UseRightToLeftAlignment: WordBool;
function UseRightToLeftReading: WordBool;
function UseRightToLeftScrollBar: WordBool;
function ClassNameIs(const Name: WideString): WordBool;
procedure AboutBox;
property ControlInterface: ICardX read GetControlInterface;
property DefaultInterface: ICardX read GetControlInterface;
property DoubleBuffered: WordBool index 10 read GetWordBoolProp write
SetWordBoolProp;
property Enabled: WordBool index -514 read GetWordBoolProp write
SetWordBoolProp;
property BiDiMode: TOleEnum index 27 read GetTOleEnumProp write
SetTOleEnumProp;
property Visible: WordBool index 28 read GetWordBoolProp write
SetWordBoolProp;
published
property TabStop;
property Align;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnStartDrag;
property BackColor: TColor index 1 read GetTColorProp write
SetTColorProp stored False;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.1 Continuação
property Color: TColor index -501 read GetTColorProp write
SetTColorProp stored False;
property DragCursor: Smallint index 2 read GetSmallintProp write
SetSmallintProp stored False;
property DragMode: TOleEnum index 3 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property FaceUp: WordBool index 4 read GetWordBoolProp write
SetWordBoolProp stored False;
property ParentColor: WordBool index 5 read GetWordBoolProp write
SetWordBoolProp stored False;
property Suit: TOleEnum index 6 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property Value: TOleEnum index 7 read GetTOleEnumProp write
SetTOleEnumProp stored False;
property Cursor: Smallint index 29 read GetSmallintProp write
SetSmallintProp stored False;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
property OnDblClick: TNotifyEvent read FOnDblClick write FOnDblClick;
property OnKeyPress: TCardXOnKeyPress read FOnKeyPress write
FOnKeyPress;
end;
procedure Register;
implementation
uses ComObj;
procedure TCardX.InitControlData;
const
CEventDispIDs: array [0..2] of DWORD = (
$00000001, $00000002, $00000007);
CControlData: TControlData2 = (
ClassID: ‘{7B33D945-0A2C-11D2-AE5C-04640BC10000}’;
EventIID: ‘{7B33D943-0A2C-11D2-AE5C-04640BC10000}’;
EventCount: 3;
EventDispIDs: @CEventDispIDs;
LicenseKey: nil (*HR:$00000000*);
Flags: $00000009;
Version: 401);
begin
ControlData := @CControlData;
TControlData2(CControlData).FirstEventOfs :=
Cardinal(@@FOnClick) - Cardinal(Self);
end;
procedure TCardX.CreateControl;
procedure DoCreate;
begin
FIntf := IUnknown(OleObject) as ICardX;
end;
begin
if FIntf = nil then DoCreate;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
27
Listagem 7.1 Continuação
function TCardX.GetControlInterface: ICardX;
begin
CreateControl;
Result := FIntf;
end;
procedure TCardX.FlipChildren(AllLevels: WordBool);
begin
DefaultInterface.FlipChildren(AllLevels);
end;
function TCardX.DrawTextBiDiModeFlags(Flags: Integer): Integer;
begin
Result := DefaultInterface.DrawTextBiDiModeFlags(Flags);
end;
function TCardX.DrawTextBiDiModeFlagsReadingOnly: Integer;
begin
Result := DefaultInterface.DrawTextBiDiModeFlagsReadingOnly;
end;
function TCardX.GetControlsAlignment: TxAlignment;
begin
Result := DefaultInterface.GetControlsAlignment;
end;
procedure TCardX.InitiateAction;
begin
DefaultInterface.InitiateAction;
end;
function TCardX.IsRightToLeft: WordBool;
begin
Result := DefaultInterface.IsRightToLeft;
end;
function TCardX.UseRightToLeftAlignment: WordBool;
begin
Result := DefaultInterface.UseRightToLeftAlignment;
end;
function TCardX.UseRightToLeftReading: WordBool;
begin
Result := DefaultInterface.UseRightToLeftReading;
end;
function TCardX.UseRightToLeftScrollBar: WordBool;
begin
Result := DefaultInterface.UseRightToLeftScrollBar;
end;
28
function TCardX.ClassNameIs(const Name: WideString): WordBool;
begin
Result := DefaultInterface.ClassNameIs(Name);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.1 Continuação
procedure TCardX.AboutBox;
begin
DefaultInterface.AboutBox;
end;
procedure Register;
begin
RegisterComponents(‘ActiveX’,[TCardX]);
end;
end.
Agora que você já viu o código gerado pelo editor da biblioteca de tipos, vejamos um pouco mais a
fundo o mecanismo de importação da biblioteca de tipos.
De onde vêm os arquivos do wrapper?
A primeira coisa que você poderá notar é que o nome do arquivo termina com _TLB. Ainda mais sutilmente, você pode ter observado o fato de que existem várias referências a “library” (biblioteca) no arquivo de
código gerado. Ambos são indicações quanto à origem do arquivo de wrapper. A biblioteca de tipos de
um controle ActiveX é uma informação especial vinculada ao controle como um recurso que descreve os
diferentes elementos de um controle ActiveX. Em particular, as bibliotecas de tipos contêm informações
como as interfaces aceitas por um controle, as propriedades, os métodos e os eventos de um controle,
além dos tipos enumerados usados pelo controle. A primeira entrada no arquivo de wrapper é o GUID
da biblioteca de tipos do controle.
NOTA
As bibliotecas de tipos são usadas principalmente por qualquer tipo de objeto Automation. O Capítulo 23
contém mais informações sobre as bibliotecas de tipo e seu uso.
Enumerações
Analisando a unidade gerada de cima para baixo, imediatamente após o GUID da biblioteca de tipos estão os tipos enumerados usados pelo controle. Observe que as enumerações são declaradas como constantes simples, em vez de verdadeiros tipos enumerados. Isso é feito porque as enumerações da biblioteca
de tipos, assim como as da linguagem C, não precisam começar com zero, e os números ordinais do elemento não precisam ser contíguos. Como esse tipo de declaração não é válido em Object Pascal, as enumerações devem ser declaradas como constantes.
Interfaces de controle
Após o arquivo do wrapper, a interface principal do controle é declarada. Aqui, você encontrará todas as
propriedades e métodos do controle ActiveX. As propriedades também são declaradas novamente em
uma dispinterface, permitindo assim que o controle seja usado como uma interface dual. Os eventos são
declarados separadamente em seguida em uma dispinterface. Você certamente não precisa saber a respeito de interfaces para usar controles ActiveX nas suas aplicações. Além do mais, o trabalho com interfaces
pode ser um tópico complicado, e por isso não entraremos em muitos detalhes no momento. Você encontrará mais informações sobre interfaces em geral no Capítulo 23, e informações sobre interfaces com
controles ActiveX no Capítulo 25.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
29
Descendente de TOleControl
Logo após no arquivo da unidade vem a definição de classe para o wrapper do controle. Por default, o
nome do objeto wrapper do controle ActiveX é TXX, onde X é o nome da coclass de controle na biblioteca
de tipos. Esse objeto, como todos os wrappers de controle ActiveX, descende da classe TOleControl. TOleControl é um componente transportando a alça da janela que descende de TWinControl. TOleControl encapsula as complexidades do mapeamento da funcionamento de controles ActiveX para os componentes do
Delphi, de modo que os controles ActiveX trabalhem de modo transparente com o Delphi. TOleControl é
uma classe abstrata – o que significa que você nunca desejará criar uma instância dele, mas usá-lo como
ponto de partida para outras classes.
Os métodos
O primeiro procedimento mostrado é o procedimento InitControlData( ). Esse procedimento é introduzido no objeto TOleControl e é modificado para todos os descendentes. Ele configura os números exclusivos da classe OLE e da identificação de evento, além de outras informações específicas do controle. Em
particular, esse método torna o TOleControl ciente de importantes detalhes do controle ActiveX, como
IDs de classe, flags de controle diversos e uma chave de licença, se o controle for licenciado. Esse método
aparece na parte protected da definição de classe, pois não é útil para os usuários da classe – ele só tem significado no interior da classe.
O método InitControlInterface( ) é modificado para inicializar o campo da interface privada FIntf
com um ponteiro para a interface ICardsX do controle.
O controle ActiveX CardX expõe apenas um outro método: AboutBox( ). É comum que os controles
ActiveX contenham um método chamado AboutBox( ), que ativa uma caixa de diálogo About personalizada para o comando. Esse método é chamado através da interface vTable, usando a propriedade ControlInterface, que é lida a partir do campo FIntf.
NOTA
Além de chamadas de vTable usando a propriedade ControlInterface, os métodos Control também podem
ser chamados com Automation, usando a propriedade OleObject de TOleControl. Como você verá no Capítulo 23, normalmente é mais eficaz a chamada de métodos por meio de vTable em vez de por Automation.
As propriedades
Você pode ter notado que a classe TCardX possui dois grupos de propriedades distintos. Um grupo não
possui valores de leitura e escrita especificados. Estas são propriedades e eventos padrão de componentes
do Delphi, herdados das classes ancestrais TWinControl e TComponent. O outro grupo de propriedades possui
um índice e também métodos get e set especificados. Esse grupo de propriedades inclui as propriedades
do controle ActiveX sendo encapsuladas pelo TOleControl.
Os métodos get e set especializados para as propriedades encapsuladas oferecem a mágica que preenche a lacuna entre as propriedades do controle ActiveX e as propriedades do componente do Object
Pascal. Observe os métodos read e write que apanham e definem propriedades para cada tipo específico
(como GetBoolProp( ), SetBoolProp( ), GetStringProp( ), SetStringProp( ) etc.). Embora existam métodos get
e set para cada tipo de propriedade, todos eles operam de modo semelhante. Na verdade, o código a seguir mostra métodos get e set genéricos para TOleControl, que funcionariam dada uma propriedade do
tipo X:
function TOleControl.GetXProp(Index: Integer): X;
var
Temp: TVarData;
30 begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
GetProperty(Index, Temp);
Result := Temp.VX;
end;
procedure TOleControl.SetXProp(Index: Integer; Value: X);
var
Temp: TVarData;
begin
Temp.VType := varX;
Temp.VX := Value;
SetProperty(Index, Temp);
end;
Nesse código, o índice da propriedade (conforme indicado pela diretiva index nas propriedades do
componente TCardsCtrl) é passado implicitamente para os procedimentos. A variável do tipo X é empacotada em um registro de dados TVarData (um registro que representa uma Variant), e esses parâmetros são
passados para o método GetProperty( ) ou SetProperty( ) de TOleControl. Cada propriedade do controle
ActiveX possui um índice exclusivo que atua como identificador. Usando esse índice e a variável TVarData
Temp, GetProperty( ) e SetProperty( ) usam OLE Automation para obter e definir os valores de propriedade
dentro do controle ActiveX.
Se você já trabalhou com outros pacotes de desenvolvimento antes, gostará de saber que o Delphi
oferece acesso facilitado não apenas para as propriedades do próprio controle ActiveX, mas também
para as propriedades e os métodos normais de TWinControl. Isso permite usar um controle ActiveX como
outros controles que transportam alça no Delphi, possibilitando o uso de princípios orientados a objeto
para modificar o comportamento de um controle ActiveX, criando descendentes personalizados dos
controles ActiveX no ambiente Delphi.
Usando controles ActiveX em suas aplicações
Depois de vincular o wrapper do seu controle ActiveX à biblioteca de componentes, você já terá lutado a
maior parte da batalha. Depois que o controle ActiveX tiver sido incluído na Component Palette, seu uso
será o mesmo que o de um componente normal do Delphi. A Figura 7.4 mostra o ambiente Delphi com
um TCardX focalizado no Form Designer. Observe as propriedades do TCardX listadas no Object Inspector.
FIGURA 7.4
Trabalhando com um controle ActiveX no Delphi.
31
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Além das propriedades de um controle ActiveX sendo definidas com o Object Inspector, alguns
controles ActiveX também oferecem uma caixa de diálogo Properties que é revelada selecionando-se a
opção Properties do menu de contexto no Form Designer do Delphi. O menu de contexto, também mostrado na Figura 7.4, é revelado por meio de um clique com o botão direito sobre um determinado controle. A caixa de diálogo Properties na realidade se encontra dentro do controle ActiveX; sua aparência,
seu uso e seu conteúdo são determinados inteiramente pelo projetista do controle. A Figura 7.5 mostra a
caixa de diálogo Properties para o controle ActiveX TCardX.
FIGURA 7.5
A caixa de diálogo Properties de TCardX.
Como você pode imaginar, esse controle em particular vem equipado com propriedades que permitem especificar o naipe, o valor, a cor e a figura do fundo da carta, além das propriedades padrão que se
referem à posição, ordem de tabulação etc. A carta na Figura 7.4 possui uma propriedade Value definida
como 1 (A) e sua propriedade Suit (naipe) definida como 3 (Espadas).
Distribuindo aplicações equipadas com controle ActiveX
Quando você estiver pronto para distribuir sua aplicação equipada com controle ActiveX, haverá alguns
aspectos de distribuição que você terá de se lembrar enquanto se prepara para enviar seu controle ActiveX e arquivos associados para seus clientes:
l
l
l
32
Você precisa enviar o arquivo OCX ou DLL que contém os controles ActiveX que está usando
na sua aplicação. Os arquivos OCX, sendo DLLs, não estão ligados ao executável da sua aplicação. Além disso, antes que o usuário possa usar sua aplicação, o controle ActiveX precisa ser registrado no Registro do Sistema desse usuário. O registro do controle ActiveX é discutido na
próxima seção.
Alguns controles ActiveX exigem uma ou mais DLLs externas ou outros arquivos para poderem
funcionar. Verifique a documentação dos seus controles ActiveX de terceiros para determinar se
algum arquivo adicional precisa ser distribuído com o seu controle ActiveX. Veja no Capítulo 25
mais informações sobre quais arquivos adicionais poderiam ter que ser distribuídos junto com
seus controles escritos em Delphi.
Muitos controles ActiveX vêm com um arquivo de licença que é obrigatório se você quiser usar o
controle durante o projeto. Esse arquivo vem do fornecedor do controle ActiveX, e impede que
seus usuários finais criem aplicações com os controles ActiveX que você entrega junto com suas
aplicações. Você não precisa remeter esses arquivos LIC com a sua aplicação, a menos que os
usuários de sua aplicação precisem usar os controle licenciados em uma ferramenta de desenvolvimento e você tenha a licença apropriada para redistribuí-los.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Registro do controle ActiveX
Antes que o controle ActiveX possa ser usado em qualquer sistema (incluindo o dos clientes que executam suas aplicações), ele precisa ser registrado no Registro do Sistema. Normalmente, isso é feito por
meio do aplicativo RegSvr32.exe, que vem com a maioria das versões do Windows. Como alternativa, você
pode usar o utilitário de registro da linha de comandos, TRegSvr.exe, encontrado no diretório bin do Delphi. Ocasionalmente, você pode querer registrar o controle de modo mais transparente, para que sua
aplicação tenha um toque de integração. Felizmente, não é difícil integrar o registro do controle ActiveX
(e a remoção do registro) em sua aplicação. A Inprise oferece o código-fonte do utilitário TRegSvr como
um exemplo de aplicação, e essa é uma demonstração excelente de como registrar servidores ActiveX e
bibliotecas de tipos.
BlackJack: um exemplo de aplicação OCX
A melhor maneira de demonstrar como usar um controle ActiveX em uma aplicação é mostrando como
escrever uma aplicação útil que incorpore um controle ActiveX. Este exemplo utiliza o controle ActiveX
TCardX; que maneira melhor de demonstrar um controle de carta do que criar um jogo de vinte-e-um? Por
questão de argumento, considere que todos os programadores jogam alto e que não precisam receber as
regras do jogo (você não sabia que este livro tinha comédia, sabia?).Desse modo, você pode se concentrar
na tarefa de programação em mãos.
Como você pode imaginar, a maior parte do código para essa aplicação lida com a lógica do jogo de
vinte-e-um. Todo o código é fornecido nas listagens mais adiante neste capítulo; no momento, a discussão fica em torno das partes de código individuais que lidam diretamente com o gerenciamento e a manipulação dos controles ActiveX. O nome desse projeto é BJ; para que você tenha uma idéia de onde vem o
código, a Figura 7.6 mostra um jogo de DDG BlackJack em andamento.
FIGURA 7.6
Jogando vinte-e-um.
O baralho de cartas
Antes de escrever o jogo em si, você primeiro precisa escrever um objeto que encapsule um baralho de
cartas do jogo. Ao contrário de um baralho de cartas real (em que as cartas são apanhadas do topo de um
baralho misturado), esse objeto de baralho de cartas contém um conjunto não misturado e utiliza números pseudo-aleatórios para escolher uma carta aleatória do baralho em seqüência. Isso é possível porque
cada carta tem noção de que ela foi usada ou não. Isso também simplifica bastante o procedimento de
embaralhar as cartas, pois tudo o que o objeto precisa fazer é definir cada uma das cartas como não usada. O código para a unidade PlayCard.pas, que contém o objeto TCardDeck, encontra-se na Listagem 7.2.
33
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.2 A unidade PlayCard.pas.
unit PlayCard;
interface
uses SysUtils, Cards;
type
ECardError = class(Exception);
TPlayingCard = record
Face: TCardValue;
Suit: TCardSuit;
end;
// exceção de carta genérica
// representa uma carta
// valor da face da carta
// valor do naipe da carta
{ um array de 52 cartas representando um baralho }
TCardArray = array[1..52] of TPlayingCard;
{ Objeto que representa um baralho de 52 cartas DIFERENTES. }
{ Este é um baralho misturado de 52 cartas, e o objeto
}
{ registra até que ponto no baralho o usuário apanhou
}
{ a carta. }
TCardDeck = class
private
FCardArray: TCardArray;
FTop: integer;
procedure InitCards;
function GetCount: integer;
public
property Count: integer read GetCount;
constructor Create; virtual;
procedure Shuffle;
function Draw: TPlayingCard;
end;
{ GetCardValue retorna o valor numérico de qualquer carta }
function GetCardValue(C: TPlayingCard): Integer;
implementation
function GetCardValue(C: TPlayingCard): Integer;
{ retorna o valor numérico de uma carta }
begin
Result := Ord(C.Face) + 1;
if Result > 10 then Result := 10;
end;
34
procedure TCardDeck.InitCards;
{ inicializa o baralho atribuindo uma combinação exclusiva de
{ valor/naipe a cada carta. }
var
i: integer;
AFace: TCardValue;
ASuit: TCardSuit;
begin
AFace := cvAce;
// começa com Ás
ASuit := csClub;
// começa com paus
}
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.2 Continuação
for i := 1 to 52 do
begin
FCardArray[i].Face := AFace;
FCardArray[i].Suit := ASuit;
if (i mod 4 = 0) and (i <<|>> 52) then
inc(AFace);
if ASuit <<|>> High(TCardSuit) then
inc(ASuit)
else
ASuit := Low(TCardSuit);
end;
end;
// para cada carta do baralho...
//
//
//
//
//
atribui face
atribui naipe
para cada quatro cartas...
incrementa a face
sempre incrementa o naipe
constructor TCardDeck.Create;
{ construtor do objeto TCardDeck. }
begin
inherited Create;
InitCards;
Shuffle;
end;
function TCardDeck.GetCount: integer;
{ Retorna contagem de cartas não usadas }
begin
Result := 52 - FTop;
end;
procedure TCardDeck.Shuffle;
{ Mistura novamente as cartas e define carta
var
i: integer;
RandCard: TPlayingCard;
RandNum: integer;
begin
for i := 1 to 52 do
begin
RandNum := Random(51) + 1;
//
RandCard := FCardArray[RandNum];
//
FCardArray[RandNum] := FCardArray[i]; //
FCardArray[i] := RandCard;
end;
FTop := 0;
end;
de cima como 0. }
escolhe número aleatório
troca próxima carta por
carta aleatória no baralho
function TCardDeck.Draw: TPlayingCard;
{ Escolhe a próxima carta do baralho. }
begin
inc(FTop);
if FTop = 53 then
raise ECardError.Create(‘Deck is empty’);
Result := FCardArray[FTop];
end;
initialization
Randomize;
end.
// semente do gerador de núm. aleatório
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
35
O jogo
A interação do jogo de vinte-e-um DDG BlackJack com o objeto TCardX ocorre principalmente em três
procedimentos. Um procedimento, chamado Hit( ), é chamado quando o jogador escolhe aceitar outra
carta. Outro procedimento, DealerHit( ), é chamado quando o carteador deseja outra carta. Finalmente,
o procedimento FreeCards( ) é chamado para dispor de todas as cartas na tela para preparar-se para outra
mão a ser distribuída.
O procedimento Hit( ) funciona com o controle ActiveX TCardX exclusivamente de duas maneiras.
Primeiro, ele cria todos os controles dinamicamente, em vez de usar controles retirados da Component
Palette. Além disso, ele nunca usa uma variável de instância do tipo TCardX – em vez disso, ele aproveita
uma construção with..do para criar e suar o objeto em uma única etapa. O código a seguir mostra o procedimento Hit( ):
procedure TMainForm.Hit;
{ Vez do jogador }
begin
CurCard := CardDeck.Draw;
// apanha carta
with TCardX.Create(Self) do
// cria controle OCX da carta
begin
Left := NextPlayerPos;
// define posição
Top := PYPos;
Suit := Ord(CurCard.Suit);
// define naipe
Value := Ord(CurCard.Face);
// define face
Parent := Self;
// atribui pai
Inc(NextPlayerPos, Width div 2);
// acompanha posição
Update;
// mostra carta
end;
DblBtn.Enabled := False;
// desativa double down
if CurCard.Face = cvAce then PAceFlag := True; // flag de Ás
Inc(PlayerTotal, GetCardValue(CurCard));
// acumula total
PlayLbl.Caption := IntToStr(PlayerTotal);
// engana
if PlayerTotal > 21 then
// verifica estouro
begin
ShowMessage(‘Busted!’);
ShowFirstCard;
ShowWinner;
end;
end;
Neste procedimento, uma carta aleatória, chamada CurCard, é retirada de um objeto TCardDeck chamado CardDeck. Um controle ActiveX TCardX é então criado, e os valores de propriedade são atribuídos.
NextPlayerPos é uma variável que registra a posição no eixo-X para a próxima carta. PYPos é uma constante
que informa a posição do eixo-Y da mão do jogador. As propriedades Suit e Value recebem valores e correspondem ao naipe (Suit) e à face (Face) de CurCard. MainForm é atribuído para ser o Parent do controle, e a
variável NextPlayerPos é incrementada pela metade da largura de uma carta. Depois de tudo isso, a variável PlayerTotal é incrementada pelo valor da carta para registrar o escore do jogador.
O procedimento DealerHit( ) funciona de modo semelhante ao procedimento Hit( ). O código desse procedimento é o seguinte:
36
procedure TMainForm.DealerHit(CardVisible: Boolean);
{ Carteador tem a vez }
begin
CurCard := CardDeck.Draw;
// carteador apanha uma carta
with TCardX.Create(Self) do
// cria o controle ActiveX
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
begin
Left := NextDealerPos;
// coloca carta no formulário
Top := DYPos;
Suit := Ord(CurCard.Suit);
// atribui naipe
FaceUp := CardVisible;
Value := Ord(CurCard.Face);
// atribui face
Parent := Self;
// atribui pai para controle
Inc(NextDealerPos, Width div 2); // define onde colocar próx. carta
Update;
// mostra carta
end;
if CurCard.Face = cvAce then DAceFlag := True;
// define flag de Ás
Inc(DealerTotal, GetCardValue(CurCard));
// continua contando
DealLbl.Caption := IntToStr(DealerTotal);
// engana
if DealerTotal > 21 then
// verifica estouro
ShowMessage(‘Dealer Busted!’);
end;
Esse método aceita um parâmetro Booleano chamado CardVisible, que indica se a carta deve ser distribuída virada para cima. Isso porque as regras do vinte-e-um dizem que a primeira carta do carteador
deve permanecer virada para baixo até que o jogador tenha escolhido ficar com ela ou até que tenha fracassado. Observado essa regra, a primeira chamada para DealerHit( ) resultará em False sendo passado
em CardVisible.
O procedimento FreeCards( ) é responsável por remover todos os controles de TCardX no formulário
principal. Como a aplicação não mantém um array ou um punhado de variáveis do tipo TCardX por perto
para gerenciar as cartas na tela, esse procedimento percorre a propriedade de array Controls do formulário, procurando elementos do tipo TCardX. Quando um controle desse tipo é encontrado, seu método Free
é chamado para removê-lo da memória. O truque aqui é certificar-se de estar percorrendo o array de trás
para frente. Se você não seguir esse caminho inverso, correrá o risco de alterar a ordem dos controles
no array enquanto está atravessando o array, o que poderá causar erros. O código para o procedimento
FreeCards aparece em seguida:
procedure TMainForm.FreeCards;
{ libera todas as cartas de controle ActiveX na tela }
var
i: integer;
begin
for i := ControlCount - 1 downto 0 do // retrocede!
if Controls[i] is TCardX then
Controls[i].Free;
end;
Isso completa a explicação sobre as partes principais do código que manipula os controles ActiveX.
A listagem completa de Main.pas, a unidade principal dessa aplicação, aparece na Listagem 7.3.
Listagem 7.3 A unidade Main.pas para o projeto BJ.
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
OleCtrls, AxCard_TLB, Cards, PlayCard, StdCtrls, ExtCtrls, Menus;
37
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.3 Continuação
38
type
TMainForm = class(TForm)
Panel1: TPanel;
MainMenu1: TMainMenu;
Play1: TMenuItem;
Deal1: TMenuItem;
Hit1: TMenuItem;
Hold1: TMenuItem;
DoubleDown1: TMenuItem;
N1: TMenuItem;
Close1: TMenuItem;
Help1: TMenuItem;
About1: TMenuItem;
Panel2: TPanel;
Label3: TLabel;
CashLabel: TLabel;
BetLabel: TLabel;
HitBtn: TButton;
DealBtn: TButton;
HoldBtn: TButton;
ExitBtn: TButton;
BetEdit: TEdit;
DblBtn: TButton;
CheatPanel: TPanel;
DealLbl: TLabel;
PlayLbl: TLabel;
Label4: TLabel;
Label6: TLabel;
Cheat1: TMenuItem;
N2: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure About1Click(Sender: TObject);
procedure Cheat1Click(Sender: TObject);
procedure ExitBtnClick(Sender: TObject);
procedure DblBtnClick(Sender: TObject);
procedure DealBtnClick(Sender: TObject);
procedure HitBtnClick(Sender: TObject);
procedure HoldBtnClick(Sender: TObject);
private
CardDeck: TCardDeck;
CurCard: TPlayingCard;
NextPlayerPos: integer;
NextDealerPos: integer;
PlayerTotal: integer;
DealerTotal: integer;
PAceFlag: Boolean;
DAceFlag: Boolean;
PBJFlag: Boolean;
DBJFlag: Boolean;
DDFlag: Boolean;
Procedure Deal;
procedure DealerHit(CardVisible: Boolean);
procedure DoubleDown;
procedure EnableMoves(Enable: Boolean);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.3 Continuação
procedure FreeCards;
procedure Hit;
procedure Hold;
procedure ShowFirstCard;
procedure ShowWinner;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
uses AboutU;
const
PYPos = 175;
DYPos = 10;
// iniciando pos y para cartas do jogador
// o mesmo para as cartas do carteador
procedure TMainForm.FormCreate(Sender: TObject);
begin
CardDeck := TCardDeck.Create;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
CardDeck.Free;
end;
procedure TMainForm.About1Click(Sender: TObject);
{ Cria e chama caixa About }
begin
with TAboutBox.Create(Self) do
try
ShowModal;
finally
Free;
end;
end;
procedure TMainForm.Cheat1Click(Sender: TObject);
begin
Cheat1.Checked := not Cheat1.Checked;
CheatPanel.Visible := Cheat1.Checked;
end;
procedure TMainForm.ExitBtnClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.DblBtnClick(Sender: TObject);
begin
DoubleDown;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
39
Listagem 7.3 Continuação
end;
procedure TMainForm.DealBtnClick(Sender: TObject);
begin
Deal;
end;
procedure TMainForm.HitBtnClick(Sender: TObject);
begin
Hit;
end;
procedure TMainForm.HoldBtnClick(Sender: TObject);
begin
Hold;
end;
40
procedure TMainForm.Deal;
{ Joga uma nova mão para carteador e jogador }
begin
FreeCards;
// remove cartas da tela
BetEdit.Enabled := False;
// desativa ctrl edição de aposta
BetLabel.Enabled := False;
// disable bet label
if CardDeck.Count < 11 then
// embaralha se < 11 cartas
begin
Panel1.Caption := ‘Reshuffling and dealing...’;
CardDeck.Shuffle;
end
else
Panel1.Caption := ‘Dealing...’;
Panel1.Show;
// mostra painel “repartindo”
Panel1.Update;
// cuida para que seja visível
NextPlayerPos := 10;
// define posição horiz. das cartas
NextDealerPos := 10;
PlayerTotal := 0;
// zera totais de cartas
DealerTotal := 0;
PAceFlag := False;
// zera flags
DAceFlag := False;
PBJFlag := False;
DBJFlag := False;
DDFlag := False;
Hit;
// vez do jogador
DealerHit(False);
// vez do carteador
Hit;
// vez do jogador
DealerHit(True);
// vez do carteador
Panel1.Hide;
// esconde painel
if (PlayerTotal = 11) and PAceFlag then
PBJFlag := True;
// verifica vinte-e-um do jogador
if (DealerTotal = 11) and DAceFlag then
DBJFlag := True;
// verifica vinte-e-um do carteador
if PBJFlag or DBJFlag then
// se houve vinte-e-um
begin
ShowFirstCard;
// vira carta do carteador
ShowMessage(‘Blackjack!’);
ShowWinner;
// determina vencedor
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.3 Continuação
end
else
EnableMoves(True);
end;
// permite jogo, mantém double down
procedure TMainForm.DealerHit(CardVisible: Boolean);
{ Carteador tem a vez }
begin
CurCard := CardDeck.Draw;
// carteador apanha uma carta
with TCardX.Create(Self) do
// cria o controle ActiveX
begin
Left := NextDealerPos;
// coloca carta no formulário
Top := DYPos;
Suit := Ord(CurCard.Suit);
// atribui naipe
FaceUp := CardVisible;
Value := Ord(CurCard.Face);
// atribui face
Parent := Self;
// atribui pai para controle
Inc(NextDealerPos, Width div 2); // define onde colocar próx. carta
Update;
// mostra carta
end;
if CurCard.Face = cvAce then DAceFlag := True;
// define flag Ás
Inc(DealerTotal, GetCardValue(CurCard));
// continua contando
DealLbl.Caption := IntToStr(DealerTotal);
// engana
if DealerTotal > 21 then
// verifica estouro do carteador
ShowMessage(‘Dealer Busted!’);
end;
procedure TMainForm.DoubleDown;
{ Chamada para double down na mão distribuída }
begin
DDFlag := True;
// define flag double down para ajustar aposta
Hit;
// apanha uma carta
Hold;
// deixa o carteador apanhar suas cartas
end;
procedure TMainForm.EnableMoves(Enable: Boolean);
{ Ativa/desativa botões/itens de menu de movimentos }
begin
HitBtn.Enabled := Enable;
// Pressiona botão
HoldBtn.Enabled := Enable;
// Segura botão
DblBtn.Enabled := Enable;
// Botão double down
Hit1.Enabled := Enable;
// Alcança item do menu
Hold1.Enabled := Enable;
// Mantém item do menu
DoubleDown1.Enabled := Enable;
// Item de menu double down
end;
procedure TMainForm.FreeCards;
{ Libera todas as cartas de controle ActiveX na tela }
var
i: integer;
begin
for i := ControlCount - 1 downto 0 do // conta para trás!
if Controls[i] is TCardX then
Controls[i].Free;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
41
Listagem 7.3 Continuação
procedure TMainForm.Hit;
{ Vez do jogador }
begin
CurCard := CardDeck.Draw;
// apanha carta
with TCardX.Create(Self) do
// cria controle de carta
begin
Left := NextPlayerPos;
// define posição
Top := PYPos;
Suit := Ord(CurCard.Suit);
// define naipe
Value := Ord(CurCard.Face);
// define valor
Parent := Self;
// atribui pai
Inc(NextPlayerPos, Width div 2); // verifica posição
Update;
// apresenta carta
end;
DblBtn.Enabled := False;
// desativa double down
if CurCard.Face = cvAce then PAceFlag := True; // define flag de Ás
Inc(PlayerTotal, GetCardValue(CurCard));
// total acumulado
PlayLbl.Caption := IntToStr(PlayerTotal);
// engana
if PlayerTotal > 21 then
// verifica estouro
begin
ShowMessage(‘Busted!’);
ShowFirstCard;
ShowWinner;
end;
end;
procedure TMainForm.Hold;
{ Jogador segura. O procedimento permite que o carteador dê as cartas. }
begin
EnableMoves(False);
ShowFirstCard;
// mostra carta do carteador
if PlayerTotal <= 21 then
// se jogador não tiver estourado...
begin
if DAceFlag then
// se o carteador tem um Ás...
begin
{ Carteador precisa atingir 17 }
while (DealerTotal <= 7) or ((DealerTotal >= 11) and (DealerTotal < 17)) do
DealerHit(True);
end
else
// se não houver Ás, continua jogando até chegar a 17
while DealerTotal < 17 do DealerHit(True);
end;
ShowWinner;
// Determina vencedor
end;
42
procedure TMainForm.ShowFirstCard;
var
i: integer;
begin
// cuida para que todas as cartas estejam viradas para cima
for i := 0 to ControlCount - 1 do
if Controls[i] is TCardX then
begin
TCardX(Controls[i]).FaceUp := True;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 7.3 Continuação
TCardX(Controls[i]).Update;
end;
end;
procedure TMainForm.ShowWinner;
{ Determina mão vencedora }
var
S: string;
begin
if DAceFlag then
// se carteador tem um Ás...
begin
if DealerTotal + 10 <= 21 then
// descobre melhor mão
inc(DealerTotal, 10);
end;
if PACeFlag then
// se jogador tem um Ás...
begin
if PlayerTotal + 10 <= 21 then
// descobre melhor mão
inc(PlayerTotal, 10);
end;
if DealerTotal > 21 then
// zera o escore se estourou
DealerTotal := 0;
if PlayerTotal > 21 then
PlayerTotal := 0;
if PlayerTotal > DealerTotal then
// se jogador ganha...
begin
S := ‘You win!’;
if DDFlag then
// paga 2:1 para double down
CashLabel.Caption := IntToStr(StrToInt(CashLabel.Caption) +
StrToInt(BetEdit.Text) * 2)
else
// paga 1:1 normalmente
CashLabel.Caption := IntToStr(StrToInt(CashLabel.Caption) +
StrToInt(BetEdit.Text));
if PBJFlag then
// paga 1.5:1 se fez vinte-e-um
CashLabel.Caption := IntToStr(StrToInt(CashLabel.Caption) +
StrToInt(BetEdit.Text) div 2)
end
else if DealerTotal > PlayerTotal then
// se carteador ganha...
begin
S := ‘Dealer wins!’;
if DDFlag then
// perde 2x em double down
CashLabel.Caption := IntToStr(StrToInt(CashLabel.Caption) StrToInt(BetEdit.Text) * 2)
else
// perda normal
CashLabel.Caption := IntToStr(StrToInt(CashLabel.Caption) StrToInt(BetEdit.Text));
end
else
S := ‘Push!’;
// empate, ninguém ganha
if MessageDlg(S + #13#10’Do you want to play again with the same bet?’,
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
Deal;
BetEdit.Enabled := True;
// permite mudar a aposta
BetLabel.Enabled := True;
end;
end.
43
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Chamando um método do controle ActiveX
Na Listagem 7.3, você pode ter notado que o formulário principal contém um método que cria e apresenta uma caixa de diálogo About. A Figura 7.7 mostra a aparência dessa caixa de diálogo About quando
ela é chamada.
FIGURA 7.7
Caixa de diálogo About do jogo de vinte-e-um.
Essa caixa de diálogo About é especial porque contém um botão que, quando selecionado, mostra
uma caixa About para o controle ActiveX CardX, chamando seu método AboutBox( ). A caixa About para o
controle CardX aparece na Figura 7.8.
A seguir vemos o código que realiza essa tarefa. Olhando mais adiante, a mesma técnica usada para
fazer chamadas de vTable aos servidores OLE Automation no Capítulo 23 também é usada aqui.
procedure TAboutBox.CardBtnClick(Sender: Tobject);
begin
Card.AboutBox;
end;
FIGURA 7.8
A caixa de diálogo About do controle ActiveX Cards.
44
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Resumo
Depois de ler este capítulo, você deverá entender todos os aspectos importantes do uso de controles ActiveX no ambiente Delphi. Você aprendeu sobre a integração de um controle ActiveX no Delphi, como
funciona o wrapper de controle ActiveX do Object Pascal, como distribuir uma aplicação equipada com
controle ActiveX, como registrar um controle ActiveX e como incorporar controles ActiveX em uma
aplicação. Devido à sua presença no mercado, os controles ActiveX normalmente podem oferecer uma
explosão de produtividade imediata. No entanto, visto que os controles ActiveX possuem algumas desvantagens, lembre-se também de procurar os componentes nativos da VCL quando estiver adquirindo
controles.
45
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Programação gráfica
com GDI e fontes
CAPÍTULO
8
NE STE CAP ÍT UL O
l
Representação de figuras no Delphi: TImage
l
Como salvar imagens
l
Uso de propriedades de TCanvas
l
Uso de métodos de TCanvas
l
Sistemas de coordenadas e modos de
mapeamento
l
Criação de um programa de pintura
l
Animação com programação gráfica
l
Fontes avançadas
l
Projeto de exemplo de criação de fonte
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 3 — 2ª PROVA
Nos capítulos anteriores, você trabalhou com uma propriedade chamada Canvas. Canvas possui um
nome apropriado, pois você pode pensar em uma janela como uma tela em branco de um artista, na qual
vários objetos do Windows são pintados. Cada botão, janela, cursor etc. é nada mais do que uma coleção
de pixels em que as cores foram definidas para lhe dar alguma aparência útil. De fato, você pode pensar
em cada janela individual como uma superfície separada em que seus componentes separados são pintados. Para levar essa analogia um pouco mais adiante, imagine que você seja um artista que necessite de
várias ferramentas para realizar sua tarefa. Você precisa de uma palheta na qual poderá escolher diferentes cores. Provavelmente usará também diferentes estilos de pincéis, ferramentas de desenho e técnicas
especiais do artista. O Win32 utiliza ferramentas e técnicas semelhantes – no sentido de programação –
para pintar os diversos objetos com os quais os usuários interagem. Essas ferramentas estão disponíveis
através da Graphics Device Interface (interface de dispositivo gráfico), mais conhecida como GDI.
O Win32 utiliza a GDI para pintar ou desenhar as imagens que você vê na tela do seu computador.
Antes do Delphi, na programação tradicional do Windows, os programadores trabalhavam diretamente
com as funções e ferramentas da GDI. Agora, o objeto TCanvas encapsula e simplifica o uso dessas funções,
ferramentas e técnicas. Este capítulo o ensina a usar TCanvas para realizar funções gráficas úteis. Você também verá como pode criar projetos de programação avançados com o Delphi 5 e a GDI do Win32. Ilustramos isso criando um programa de pintura e um programa de animação.
Representação de figuras no Delphi: TImage
O componente TImage representa uma imagem gráfica que pode ser exibida em qualquer lugar de um formulário e está disponível a partir da Component Palette (palheta de componentes) do Delphi 5. Com
TImage, você pode carregar e exibir um arquivo de bitmap (.bmp), um Windows metafile de 16 bits (.wmf),
um metafile avançado de 32 bits (.emf), um arquivo de ícone (.ico), um arquivo JPEG (.jpg, .jpeg) ou outros formatos de arquivo manipulados pelas classes adicionais de TGraphic. Os dados de imagem na realidade são armazenados pela propriedade Picture de TImage, que é do tipo TPicture.
Imagens gráficas: bitmaps, metafiles e ícones
Bitmaps
Os bitmaps do Win32 são informações binárias organizadas em um padrão de bits que representa
uma imagem gráfica. Mais especificamente, esses bits armazenam itens de informação de cor chamados pixels. Existem dois tipos de bitmaps: bitmaps dependes de dispositivo (DDB) e bitmaps independentes de dispositivo (DIB).
Como programador do Win32, você provavelmente não estará lidando muito com DDBs, pois
esse formato foi mantido simplesmente por questão de compatibilidade. Bitmaps dependentes de dispositivo, como o nome indica, dependem do dispositivo em que são criados. Os bitmaps nesse formato, quando salvos, não armazenam informações referentes à palheta de cores que utilizam e nem armazenam informações referentes à sua resolução.
Ao contrário, os bitmaps independentes de dispositivo (DIBs) armazenam informações que lhes
permitem ser exibidos em qualquer dispositivo sem alterar radicalmente sua aparência.
Na memória, tanto DDBs quanto DIBs são representados pelas mesmas estruturas, em sua maior
parte. Uma diferença fundamental é que os DDBs utilizam a palheta fornecida pelo sistema, enquanto
os DIBs oferecem sua própria palheta. Para avançar um pouco mais nessa explicação, DDBs são simplesmente armazenamento nativo, tratados pelas rotinas do driver de vídeo e pelo hardware de vídeo.
DIBs são formatos de pixel padronizados, tratados pelas rotinas genéricas da GDI e armazenadas na
memória global. Algumas placas de vídeo usam formatos de pixel de DIB como armazenamento nativo, e com isso você consegue DDB=DIB. Em geral, DIB oferece mais flexibilidade e simplicidade, às
vezes com um pequeno prejuízo no desempenho. DDBs sempre são mais rápidos, mas não tão convenientes.
47
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Metafiles
Ao contrário dos bitmaps, metafiles são imagens gráficas baseadas em vetor. Metafiles são arquivos
em que são armazenadas uma série de rotinas da GDI, permitindo que você salve chamadas de função da GDI em disco para poder reapresentar a imagem mais tarde. Isso também permite compartilhar suas rotinas de desenho com outros programas sem ter de chamar a função específica da GDI em
cada programa. Outras vantagens dos metafiles são que eles podem ser dimensionados para dimensões arbitrárias e ainda reterem suas linhas e arcos suaves – os bitmaps não conseguem fazer isso tão
bem. Na verdade, esse é um dos motivos por que o mecanismo de impressão do Win32 ter sido criado
em torno do armazenamento avançado de matafile para os trabalhos de impressão.
Existem dois formatos de metafile: metafiles padrão, normalmente armazenados em um arquivo
com uma extensão wmf, e metafiles avançados, normalmente armazenados em um arquivo com uma
extensão emf.
Metafiles padrão são reminiscências do sistema Win16. Metafiles avançados são mais poderosos
e precisos. Use EMFs se você estiver produzindo metafiles para suas próprias aplicações. Se você estiver exportando seus metafiles para programas mais antigos, que não possam utilizar o formato avançado, use os WMFs de 16 bits. No entanto, saiba que, voltando para os WMFs de 16 bits, você também
perderá vários primitivos da GDI que os EMFs aceitam, mas não os WMFs. A classe TMetafile do Delphi
5 conhece os dois tipos de metafiles.
Ícones
Ícones são recursos do Win32 que na realidade são armazenados em um arquivo de ícones com uma
extensão ico. Eles também podem residir em um arquivo de recursos (.res). Existem dois tamanhos típicos de ícones no Windows: ícones grandes, que possuem 32x32 pixels, e ícones pequenos, que possuem 16x16 pixels. Todas as aplicações do Windows usam os dois tamanhos de ícone. Os ícones pequenos aparecem no canto superior esquerdo da janela principal da aplicação e também no controle
List view (modo de lista) do Windows. Esse controle aparece na página Win32 da Component Palette.
Os ícones são compostos de dois bitmaps. Um deles, denominado imagem, é a imagem real do
ícone, conforme exibida na tela. O outro bitmap, denominado máscara, possibilita a obtenção do efeito de transparência quando o ícone é apresentado. Os ícones são usados para diversas finalidades.
Por exemplo, os ícones aparecem na barra de tarefas de uma aplicação e nas caixas de mensagem
onde os ícones de ponto de interrogação, ponto de exclamação ou sinal de parar são usados para
chamar a atenção.
TPicture é uma classe contêiner para a classe abstrata TGraphic. Uma classe contêiner significa que
TPicture pode conter uma referência e exibir um TBitmap, TMetafile, TIcon ou qualquer outro tipo de TGraphic, sem realmente se importar com quem é quem. Você usa as propriedades e os métodos de TImage.Picture para carregar arquivos de imagem em um componente TImage. Por exemplo, use a instrução a seguir:
MyImage.Picture.LoadFromFile(‘NomeArquivo.bmp’);
Use uma instrução semelhante para carregar arquivos de ícone ou metafiles. Por exemplo, o código
a seguir carrega um metafile do Win32:
MyImage.Picture.LoadFromFile(‘NomeArquivo.emf’);
Este código carrega um arquivo de ícone do Win32:
MyImage.Picture.LoadFromFile(‘NomeArquivo.ico’);
No Delphi 5, TPicture pode agora carregar imagens JPEG usando a mesma técnica para carregar bitmaps:
48
MyImage.Picture.LoadFromFile(‘NomeArquivo.jpeg’);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Como salvar imagens
Para salvar uma imagem, use o método SaveToFile:
MyImage.Picture.SaveToFile(‘NomeArquivo.bmp’);
A classe TBitmap encapsula o bitmap e a palheta do Win32, e oferece os métodos para carregar, armazenar, exibir, salvar e copiar as imagens de bitmap. TBitmap também controla a realização de palheta automaticamente. Isso significa que a tarefa cansativa de gerenciar bitmaps foi bastante simplificada com a
classe TBitmap do Delphi 5, o que permite focalizar o uso do bitmap e evita que você tenha de se preocupar
com todos os detalhes internos da implementação.
NOTA
TBitmap não é o único objeto que gerencia a realização da palheta. Componentes como TImage, TMetafile
e cada um dos outros descendentes de TGraphic também realizam palhetas de seus bitmaps sob pedido. Se
você criar componentes que contenham um objeto TBitmap que pode ter imagens de 256 cores, terá de
substituir o método GetPalette( ) do seu componente para retornar a palheta de cores do bitmap.
Para criar uma instância de uma classe TBitmap e carregar um arquivo de bitmap, por exemplo, você
usa os seguintes comandos:
MyBitmap := Tbitmap.Create;
MyBitmap.LoadFromFile(‘MyBMP.BMP’);
ATENÇÃO
Outro método para carregar bitmaps para uma aplicação é carregá-lo a partir de um arquivo de recursos.
Discutiremos sobre esse método mais adiante.
Para copiar um bitmap para outro, você usa o método TBitmap.Assign( ), como neste exemplo:
Bitmap1.Assign(Bitmap2);
Você também pode copiar uma parte de um bitmap a partir de uma instância de TBitmap para outra
instância de TBitmap, ou ainda para a tela do formulário, usando o método CopyRect( ):
var
R1: Trect;
begin
with R1 do
begin
Top := 0;
Left := 0;
Right := BitMap2.Width div 2;
Bottom := BitMap2.Height div 2;
end;
Bitmap1.Canvas.CopyRect(ClientRect, BitMap2.Canvas, R1);
end;
49
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
No código anterior, você primeiro calcula os valores apropriados em um registro TRect e depois usar
o método TCanvas.CopyRect( ) para copiar uma parte do bitmap. Um TRect é definido da seguinte forma:
TRect = record
case Integer of
0: (Left, Top, Right, Bottom: Integer);
1: (TopLeft, BottomRight: Tpoint);
end;
Essa técnica será usada no programa de pintura, mais adiante neste capítulo. CopyRect( ) estica automaticamente a parte copiada da tela de origem para preencher o retângulo de destino.
ATENÇÃO
Você precisa estar ciente de uma diferença significativa no consumo de recursos para copiar bitmaps nos
dois exemplos anteriores. A técnica CopyRect( ) duplica o uso da memória, pois existem duas cópias separadas da imagem na memória. A técnica Assign( ) não custa nada, pois os dois objetos de bitmap compartilham uma referência à mesma imagem na memória. Se você modificar um dos objetos de bitmap, a
VCL imitará a imagem usando um esquema de copiar-ao-escrever.
Outro método que você pode usar para copiar o bitmap inteiro para a tela do formulário, de modo
que encurte ou expanda para caber dentro dos limites da tela, é o método StretchDraw( ). Veja um exemplo:
Canvas.StretchDraw(R1, MyBitmap);
Discutiremos os métodos de TCanvas mais adiante neste capítulo.
Uso de propriedades de TCanvas
Classes de nível mais alto, como descendentes de TForm e TGraphicControl, possuem uma propriedade Canvas. Essa tela (canvas) serve como superfície de pintura para os outros componentes do seu formulário.
As ferramentas que Canvas utiliza para realizar o desenho são canetas (pens), pincéis (brushes) e fontes
(fonts).
Usando canetas
Nesta seção, primeiro explicaremos como usar as propriedades de TPen e depois mostraremos algum código em um projeto de exemplo que utiliza essas propriedades.
As canetas permitem desenhar linhas na tela e são acessadas por meio da propriedade Canvas.Pen.
Você pode mudar o modo como as linhas são desenhadas modificando as propriedades da caneta: Color,
Width, Style e Mode.
A propriedade Color especifica a cor de uma caneta. O Delphi 5 oferece constantes de cor predefinidas, que correspondem a várias cores comuns. Por exemplo, as constantes clRed e clYellow correspondem
às cores vermelha e amarela. O Delphi 5 também define constantes para representarem as cores de elementos de tela do sistema Win32, como clActiveCaption e clHighlightText, que correspondem aos títulos
ativos e ao texto destacado do Win32. A linha a seguir atribui a cor azul à caneta da tela:
canvas.Pen.color := clblue;
Esta linha mostra como atribuir uma cor aleatória à propriedade Pen de Canvas:
50
Pen.Color := Tcolor(RGB(Random(255), Random(255), Random(25t5)));
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
RGB( ) e Tcolor
O Win32 representa as cores como inteiros longos em que os três bytes inferiores significa um nível
de intensidade de cor vermelha, verde e azul. A combinação dos três valores compõe uma cor
Win32 válida. A função RGB(R, G, B) apanha três parâmetros para os níveis de intensidade vermelho,
verde e azul. Existem 255 valores possíveis para cada nível de intensidade e aproximadamente 16
milhões de cores podem ser retornadas a partir da função RGB( ). RGB(0, 0, 0), por exemplo, retorna
o valor de cor do preto, enquanto RGB(255, 255, 255) retorna o valor de cor para o branco. RGB(255, 0,
0), RGB(0, 255, 0) e RGB(0, 0, 255) retornam os valores de cor para vermelho, verde e azul, respectivamente. Variando os valores passados para RGB( ), você pode obter uma cor em qualquer local do espectro de cores.
TColor é específico da VCL, e refere-se à constante definida na unidade Graphics.pas. Essas constantes são mapeadas para uma cor com correspondência mais próxima na palheta do sistema ou para
uma cor definida no Painel de Controle do Windows. Por exemplo, clBlue é mapeado para a cor azul,
enquanto clButtonFace é mapeado para a cor especificada para as faces de botões. Além dos três bytes
para representar a cor, o byte de mais alta ordem de TColor especifica como uma cor será correspondida. Portanto, se o byte de mais alta ordem for $00, a cor representada será a combinação mais próxima
na palheta atualmente observada. Finalmente, uma cor de $02 combina com a cor mais próxima na
palheta lógica do contexto de dispositivo atual. Você encontrará informações adicionais no arquivo de
ajuda do Delphi, sob “TColor type” (tipo Tcolor).
DICA
Use a função ColorToRGB( ) para converter as cores do sistema do Win32, como clWindow, para uma cor
RGB válida. A função é descrita na ajuda on-line do Delphi 5.
A caneta também pode desenhar linhas com diferentes estilos de desenho, conforme especificado pela sua propriedade Style. A Tabela 8.1 mostra os diferentes estilos que você pode definir para
Pen.Style.
Tabela 8.1 Estilos de caneta
Estilo
Desenha
psClear
Uma linha invisível
psDash
Uma linha composta de uma série de traços
psDashDot
Uma linha composta de traços e pontos alternados
psDashDotDot
Uma linha composta de uma série de combinações traço-ponto-ponto
psDot
Uma linha composta de uma série de pontos
psInsideFrame
Uma linha dentro de um frame de formas fechadas que especificam um retângulo delimitador
psSolid
Uma linha sólida
A linha a seguir mostra como você mudaria o estilo de desenho da caneta:
Canvas.Pen.Style := psDashDot;
A Figura 8.1 mostra como os diferentes estilos de caneta aparecem quando desenhados na tela do
formulário. Uma coisa a observar é que as cores “intermediárias” nas linhas pontilhadas vêm da cor do
pincel. Se você quiser criar uma linha pontilhada preta cortando um quadrado vermelho, teria que defiEDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
51
nir a Canvas.Brush.Color como clRed. A definição da cor da caneta e do pincel é o modo como você desenharia, por exemplo, uma linha tracejada vermelha e azul sobre um quadrado branco.
FIGURA 8.1
Diferentes estilos de caneta.
Diferentes estilos de caneta
A propriedade Pen.Width permite especificar a largura, em pixels, que a caneta utiliza para desenhar. Quando essa propriedade está definida como uma largura maior, a caneta desenha com linhas
mais grossas.
O estilo de linha pontilhado aplica-se apenas a canetas com uma espessura igual a 1. A definição da
espessura da caneta como 2 desenhará uma linha sólida. Isso vem da GDI de 16 bits, que o Win32 simula
apenas por compatibilidade. O Windows 95/98 não cria linhas pontilhadas grossas, mas o Windows
NT/2000 pode fazer isso se você usar apenas o conjunto estendido de recursos da GDI.
Três fatores determinam o modo como o Win32 desenha pixels ou linhas na superfície de uma tela:
a cor da caneta, a cor da superfície ou de destino e a operação de bits que o Win32 realiza sobre valores
de duas cores. Essa operação é conhecida como operação de rastreio (ou ROP). A propriedade Pen.Mode
especifica a ROP a ser usada para uma determinada tela. Dezesseis modos são predefinidos no Win32,
como vemos na Tabela 8.2.
Tabela 8.2 Modos de caneta do Win32 na cor de origem (O) e destino (D) de Pen.Color
52
Modo
Cor do pixel resultante
Operação Booleana
pmBlack
Sempre preto
0
pmWhite
Sempre branco
1
pmNOP
Inalterado
D
pmNOT
Inverte cor D
not D
pmCopy
Cor especificada por O
O
pmNotCopy
Inverso de O
not O
pmMergePenNot
Combinação de O e inverso de D
O or not D
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela 8.2 Continuação
Modo
Cor do pixel resultante
Operação Booleana
pmMaskPenNot
Combinação de cores comuns a O e inverso de D
O and not D
pmMergeNotPen
Combinação de D e inverso de O
not O or D
pmMaskNotPen
Combinação de cores comuns a D e inverso de O
not S and D
pmMerge
Combinação de O e D
S or D
pmNotMerge
Inverso da operação pmMerge sobre O e D
not (S or D)
pmMask
Combinação de cores comuns a O e D
S and D
pmNotMask
Inverso da operação pmMask sobre O e D
not (S and D)
pmNor
Combinação de cores em O ou D, mas não ambos
S XOR D
pmNotXor
Inverso da operação pmXOR sobre S e D
not (S XOR D)
Pen.mode é pmCopy por default. Isso significa que a caneta desenha com a cor especificada por sua propriedade Color. Suponha que você queira desenhar linhas pretas sobre um fundo branco. Se uma linha
cruzar sobre uma linha desenhada anteriormente, ela deve desenhar em branco ao invés de preto.
Uma forma de se fazer isso seria verificar a cor da área na qual você irá desenhar – se for branca, defina
pen.Color como preto; se for preta, defina pen.Color como branco. Embora esse método funcione, ele seria desajeitado e lento. Um método melhor seria definir Pen.Color como clBlack e Pen.Mode como pmNot. Isso resultaria
na caneta desenhando o inverso da operação de mesclagem da cor da caneta e da superfície. A Figura 8.2
mostra o resultado dessa operação quando se desenha com uma caneta preta de uma forma riscada.
FIGURA 8.2
A saída de uma operação pmNotMerge.
A Listagem 8.1 é um exemplo do projeto do CD que ilustra o código resultante nas Figuras 8.1 e
8.2. Você encontrará essa demonstração no CD que acompanha este livro.
Listagem 8.1 Ilustração das operações de Pen
unit MainFrm;
interface
uses
53
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.1 Continuação
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
Menus, StdCtrls, Buttons, ExtCtrls;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiPens: TMenuItem;
mmiStyles: TMenuItem;
mmiPenColors: TMenuItem;
mmiPenMode: TMenuItem;
procedure mmiStylesClick(Sender: TObject);
procedure mmiPenColorsClick(Sender: TObject);
procedure mmiPenModeClick(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
procedure ClearCanvas;
procedure SetPenDefaults;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ClearCanvas;
var
R: TRect;
begin
// Apaga o conteúdo de Canvas
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
Canvas.FillRect(ClientRect);
end;
end;
procedure TMainForm.SetPenDefaults;
begin
with Canvas.Pen do
begin
Width := 1;
Mode := pmCopy;
Style := psSolid;
Color := clBlack;
end;
end;
procedure TMainForm.mmiStylesClick(Sender: TObject);
var
yPos: integer;
54 PenStyle: TPenStyle;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.1 Continuação
begin
ClearCanvas;
// Primeiro apaga o conteúdo de Canvas
SetPenDefaults;
// yPos representa a coordenada Y
YPos := 20;
with Canvas do
begin
for PenStyle := psSolid to psInsideFrame do
begin
Pen.Color := clBlue;
Pen.Style := PenStyle;
MoveTo(100, yPos);
LineTo(ClientWidth, yPos);
inc(yPos, 20);
end;
// Escreve
TextOut(1,
TextOut(1,
TextOut(1,
TextOut(1,
TextOut(1,
TextOut(1,
TextOut(1,
end;
end;
títulos para os vários estilos de caneta
10, ‘ psSolid ‘);
30, ‘ psDash ‘);
50, ‘ psDot ‘);
70, ‘ psDashDot ‘);
90, ‘ psDashDotDot ‘);
110, ‘ psClear ‘);
130, ‘ psInsideFrame ‘);
procedure TMainForm.mmiPenColorsClick(Sender: TObject);
var
i: integer;
begin
ClearCanvas;
// Apaga o conteúdo de Canvas
SetPenDefaults;
with Canvas do
begin
for i := 1 to 100 do
begin
// Pega uma cor de caneta aletaória e desenha linha usando essa cor
Pen.Color := RGB(Random(256),Random(256), Random(256));
MoveTo(random(ClientWidth), Random(ClientHeight));
LineTo(random(ClientWidth), Random(ClientHeight));
end
end;
end;
procedure TMainForm.mmiPenModeClick(Sender: TObject);
var
x,y: integer;
begin
ClearCanvas; // Apaga o conteúdo de Canvas
SetPenDefaults;
y := 10;
canvas.Pen.Width := 20;
while y < ClientHeight do
begin
canvas.MoveTo(0, y);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
55
Listagem 8.1 Continuação
// Desenha uma linha e incrementa valor Y
canvas.LineTo(ClientWidth, y);
inc(y, 30);
end;
x := 5;
canvas.pen.Mode := pmNot;
while x < ClientWidth do
begin
Canvas.MoveTo(x, 0);
canvas.LineTo(x, ClientHeight);
inc(x, 30);
end;
end;
end.
A Listagem 8.1 mostra três exemplos de tratamento da caneta da tela. As duas funções auxiliadoras,
ClearCanvas( ) e SetPenDefaults( ), são usadas para limpar o conteúdo da tela do formulário principal e retornar as propriedades de Canvas.Pen aos seus valores default à medida que essas propriedades são modifi-
cadas por cada um dos três manipuladores de evento.
ClearCanvas( ) é uma técnica útil para apagar o conteúdo de qualquer componente contendo uma
propriedade Canvas. ClearCanvas( ) utiliza um pincel branco sólido para apagar o que estava pintado na
tela. FillRect( ) é responsável por pintar uma área retangular conforme especificado pela estrutura de
TRect, ClientRect, que é passada a ele.
O método mmiStylesClick( ) mostra como exibir os vários estilos de TPen, como mostra a Figura 8.1,
desenhando linhas horizontais no Canvas do formulário usando um estilo diferente de TPen. Tanto TCanvas.MoveTo( ) quanto TCanvas.LineTo( ) permitem desenhar linhas na tela.
O método mmiPenColorsClick( ) ilustra o desenho de linhas com uma cor diferente de TPen. Aqui você
usa a função RGB( ) para recuperar uma cor a ser atribuída a TPen.Color. Os três valores que você passa para
RGB( ) são valores aleatórios dentro do intervalo de 0 a 255. A saída desse método aparece na Figura 8.3.
Finalmente, o método mmiPenModeClick( ) ilustra como desenhar linhas usando um modo de caneta
diferente. Aqui você usa o modo pmNot para realizar as ações discutidas anteriormente, resultando na saída que aparece na Figura 8.2.
56 F I G U R A 8 . 3 Saída do método mmiPenColorsClick( ).
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Usando os pixels de TCanvas
A propriedade TCanvas.Pixels é um array bidimensional em que cada elemento representa o valor de TCode um pixel na superfície ou área de cliente do formulário. O canto superior esquerdo da superfície
de pintura do seu formulário é indicado por:
lor
Canvas.Pixels[0,0]
e o canto inferior direito é:
Canvas.Pixels[larguracliente, alturacliente];
É raro você precisar acessar pixels individuais no seu formulário. Em geral, você não deseja usar a
propriedade Pixels, pois ela é lenta. O acesso a essa propriedade usa as funções da GDI GetPixel( )/SetPixel, que a Microsoft reconheceu serem imperfeitas, e que nunca serão eficientes. Isso porque as duas
funções se baseiam nos valores RGB de 24 bits. Quando não estiverem trabalhando com contextos de
dispositivo RGB de 24 bits, essas funções precisam realizar uma ginástica intensa de combinação de cores
para converter o RGB para um formato de pixel de dispositivo. Para manipular pixels rapidamente, use a
propriedade de array TBitmap.ScanLine em seu lugar. Para buscar ou enviar um ou dois pixels de uma só
vez, o uso de Pixels pode ser indicado.
Usando pincéis
Esta seção discute as propriedades de TBrush e mostra algum código em um projeto de exemplo que utiliza essas propriedades.
Usando as propriedades de TBrush
O pincel de uma tela preenche áreas e formas desenhadas na tela. Isso difere do objeto TPen, que permite
desenhar linhas na tela. Um pincel permite preencher uma área na tela usando várias cores, estilos e padrões.
O objeto TBrush de Canvas possui três propriedades importantes que especificam como o pincel pinta
na superfície do formulário: Color, Style e Bitmap. Color especifica a cor do pincel, Style especifica o padrão
de segundo plano do pincel, e Bitmap especifica um bitmap que você pode usar para criar padrões personalizados para o segundo plano do pincel.
Oito opções de pincel são especificadas pela propriedade Style: bsSolid, bsClear, bsHorizontal,
bsVertical, bsFDiagonal, bsBDiagonal, bsCross e bsDiagCross. Por default, a cor de pincel é clWhite com um estilo bsSolid e nenhum bitmap. Você pode mudar a cor e o estilo para preencher uma área com padrões diferentes. O exemplo na próxima seção ilustra o uso de cada uma das propriedades de TBrush.
Exemplo de código de TBrush
A Listagem 8.2 mostra a unidade para um projeto que ilustra o uso das propriedades de TBrush que discutimos. Você pode carregar esse projeto a partir do CD que acompanha este livro.
Listagem 8.2 Exemplo de TBrush
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
Menus, ExtCtrls;
type
TMainForm = class(TForm)
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
57
Listagem 8.2 Continuação
mmMain: TMainMenu;
mmiBrushes: TMenuItem;
mmiPatterns: TMenuItem;
mmiBitmapPattern1: TMenuItem;
mmiBitmapPattern2: TMenuItem;
procedure mmiPatternsClick(Sender: TObject);
procedure mmiBitmapPattern1Click(Sender: TObject);
procedure mmiBitmapPattern2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FBitmap: TBitmap;
public
procedure ClearCanvas;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ClearCanvas;
var
R: TRect;
begin
// Apaga o conteúdo de Canvas
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
GetWindowRect(Handle, R);
R.TopLeft := ScreenToClient(R.TopLeft);
R.BottomRight := ScreenToClient(R.BottomRight);
FillRect(R);
end;
end;
procedure TMainForm.mmiPatternsClick(Sender: TObject);
begin
ClearCanvas;
with Canvas do
begin
// Escreve os títulos para os vários estilos de pincel
TextOut(120, 101, ‘bsSolid’);
TextOut(10, 101, ‘bsClear’);
TextOut(240, 101, ‘bsCross’);
TextOut(10, 221, ‘bsBDiagonal’);
TextOut(120, 221, ‘bsFDiagonal’);
TextOut(240, 221, ‘bsDiagCross’);
TextOut(10, 341, ‘bsHorizontal’);
TextOut(120, 341, ‘bsVertical’);
58
// Desenha um retângulo com os vários estilos de pincel
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.2 Continuação
Brush.Style := bsClear;
Rectangle(10, 10, 100, 100);
Brush.Color := clBlack;
Brush.Style := bsSolid;
Rectangle(120, 10, 220, 100);
{ Demonstra que o pincel é transparente desenhando um retângulo
colorido, sobre o qual o retângulo com estilo de pincel será
desenhado. }
Brush.Style := bsSolid;
Brush.Color := clRed;
Rectangle(230, 0, 330, 90);
Brush.Style := bsCross;
Brush.Color := clBlack;
Rectangle(240, 10, 340, 100);
Brush.Style := bsBDiagonal;
Rectangle(10, 120, 100, 220);
Brush.Style := bsFDiagonal;
Rectangle(120, 120, 220, 220);
Brush.Style := bsDiagCross;
Rectangle(240, 120, 340, 220);
Brush.Style := bsHorizontal;
Rectangle(10, 240, 100, 340);
Brush.Style := bsVertical;
Rectangle(120, 240, 220, 340);
end;
end;
procedure TMainForm.mmiBitmapPattern1Click(Sender: TObject);
begin
ClearCanvas;
// Carrega um bitmap do disco
FBitMap.LoadFromFile(‘pattern.bmp’);
Canvas.Brush.Bitmap := FBitmap;
try
{ Desenha um retângulo para cobrir a área do cliente inteira do
formulário usando o padrão de bitmap como pincel para pintura. }
Canvas.Rectangle(0, 0, ClientWidth, ClientHeight);
finally
Canvas.Brush.Bitmap := nil;
end;
end;
procedure TMainForm.mmiBitmapPattern2Click(Sender: TObject);
begin
ClearCanvas;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
59
Listagem 8.2 Continuação
// Carrega um bitmap do disco
FBitMap.LoadFromFile(‘pattern2.bmp’);
Canvas.Brush.Bitmap := FBitmap;
try
{ Desenha um retângulo para cobrir a área do cliente inteira do
formulário usando o padrão de bitmap como pincel para pintura. }
Canvas.Rectangle(0, 0, ClientWidth, ClientHeight);
finally
Canvas.Brush.Bitmap := nil;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FBitmap := TBitmap.Create;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FBitmap.Free;
end;
end.
DICA
O método ClearCanvas( ) que você usa aqui é uma rotina prática para uma unidade utilitária. Você pode
definir ClearCanvas( ) para apanhar os parâmetros TCanvas e TRect aos quais o código de apagamento
será aplicado:
procedure ClearCanvas(Acanvas: TCanvas; Arect: Trect);
begin
// Apaga as constantes da tela
with Acanvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
FillRect(ARect);
end;
end;
O método mmiPatternsClick( ) ilustra o desenho com vários padrões de TBrush. Primeiro, você desenha os títulos e depois, usando cada um dos padrões de pincel disponíveis, desenha retângulos na tela do
formulário. A Figura 8.4 mostra a saída desse método.
Os métodos mmiBitmapPattern1Click( ) e mmiBitmapPattern2Click( ) ilustram como usar um padrão
de bitmap como pincel. A propriedade TCanvas.Brush contém uma propriedade TBitmap à qual você pode
atribuir um padrão de bitmap. Esse padrão será usado para preencher a área pintada pelo pincel em vez
do padrão especificado pela propriedade TBrush.Style. Entretanto, existem algumas regras para o uso
dessa técnica. Primeiro, você terá de atribuir um objeto de bitmap válido à propriedade. Em segundo lugar, você precisa atribuir nil à propriedade Brush.Bitmap quando tiver terminado de usá-la, pois o pincel
não assume a propriedade do objeto de bitmap quando você atribui um bitmap a ele. As Figuras 8.5 e 8.6
60 mostram a saída de mmiBitmapPattern1Click( ) e mmiBitmapPattern2Click( ), respectivamente.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.4
Padrões de pincel.
NOTA
O Windows limita o tamanho dos bitmaps de padrão do pincel a 8x8 pixels, e eles precisam ser bitmaps dependentes de dispositivo, e não bitmaps independentes de dispositivo. O Windows rejeitará os bitmaps de
padrão de pincel maiores do que 8x8; o NT aceitará algo maior, mas só usará a parte superior esquerda de
8x8 pixels.
FIGURA 8.5
Saída de mmiBitmapPattern1Click( ).
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
61
FIGURA 8.6
Saída de mmiBitmapPattern2Click( ).
DICA
O uso de um padrão de bitmap para preencher uma área da tela não somente se aplica às formas da tela,
mas também a qualquer componente que contenha uma propriedade Canvas. Basta acessar os métodos
e/ou propriedades da propriedade Canvas do componente, ao invés das propriedades do formulário. Por
exemplo, veja como realizar o desenho de um padrão em um componente TImage:
Image1.Canvas.Brush.Bitmap := AlgumBitmap;
try
Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);a
finally
Image1.Canvas.Brush.Bitmap := nil;
end;
Usando fontes
A propriedade Canvas.Font permite desenhar texto usando qualquer uma das fontes disponíveis do
Win32. Você pode mudar a aparência do texto escrito na tela modificando a propriedade Color, Name,
Size, Height ou Style da fonte.
Você pode atribuir qualquer uma das cores predefinidas do Delphi 5 a Font.Color. O código a seguir,
por exemplo, atribui a cor vermelha à fonte da tela:
Canvas.Font.Color := clRed;
A propriedade Name especifica o nome da fonte da janela. Por exemplo, a linha de código a seguir
atribui diferentes nomes de tipo à fonte de Canvas:
Canvas.Font.Name := ‘New Times Roman’;
Canvas.Font.Size especifica o tamanho da fonte em pontos.
Canvas.Font.Style é um conjunto composto de um estilo ou uma
62
na Tabela 8.3.
combinação dos estilos mostrados
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela 8.3 Estilos de fonte
Valor
Estilo
fsBold
Negrito
fsItalic
Itálico
fsUnderline
Sublinhado
fsStrikeOut
Uma linha horizontal atravessando a fonte, dando-lhe uma aparência de tachado
Para combinar dois estilos, use a sintaxe para combinar diversos valores de conjunto:
Canvas.Font.Style := [fsBold, fsItalic];
Você pode usar fsFontDialog para obter uma fonte do Win32 e atribuir essa fonte à propriedade TMe-
mo.Font:
if FontDialog1.Execute then
Memo1.Font.Assign(FontDialog1.Font);
O mesmo poderá ser feito para atribuir a fonte selecionada na TFontDialog à fonte de Canvas:
Canvas.Font.Assign(FontDialog1.Font);
ATENÇÃO
Não se esqueça de usar o método Assign( ) ao copiar variáveis de instância de TBitMap, TBrush, TIcon,
TMetaFile, TPan e TPicture. Uma instrução como
MyBrush1 := MyBrush2
pode parecer válida, mas realiza uma cópia de ponteiro direta, de modo que as duas instâncias apontam
para o mesmo objeto de pincel, o que pode resultar em um furo no heap. Usando Assign( ), você garante
que recursos anteriores serão liberados.
Isso não é assim quando se atribui entre duas propriedades de TFont. Portanto, uma instrução como
Form1.Font := Form2.Font
é uma instrução válida porque TForm.Font é uma propriedade cujo método de escrita chama internamente
Assign( ) para copiar os dados do objeto indicado do formulário. No entanto, tenha cuidado: isso só é válido quando se atribui propriedades TFont, e não variáveis TFont. Via de regra, sempre use Assign( ).
Além disso, você pode definir atributos individuais da fonte selecionada em TFontDialog à fonte de
Canvas:
Canvas.Font.Name := Font.Dialog1.Font.Name;
Canvas.Font.Size := Font.Dialog1.Font.Size;
Vimos rapidamente algo sobre fontes. Porém, uma discussão mais profunda sobre fontes pode ser
encontrada no final deste capítulo.
63
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Usando a propriedade CopyMode
A propriedade TCanvas..CopyMode determina como uma tela copia uma imagem de outra tela para si mesma. Por exemplo, quando CopyMode contém o valor cmSrcCopy, isso significa que a imagem de origem será
copiada para o destino inteiramente. CmSrcInver, no entanto, faz com que os pixels das imagens de origem
e de destino sejam combinados usando o operador de bit XOR. CopyMode é usado para se conseguir diferentes efeitos ao copiar de um bitmap para outro bitmap. Um local típico onde você mudaria o valor default
de CopyMode de cmSrcCopy para outro valor é na escrita de aplicações de animação. Você aprenderá a escrever para animação mais adiante neste capítulo.
Para ver como usar a propriedade CopyMode, dê uma olhada na Figura 8.7.
A Figura 8.7 mostra um formulário contendo duas imagens, ambas tendo uma elipse desenhada.
Você seleciona um valor de CopyMode a partir do componente TComboBox, e terá vários resultados ao copiar
uma imagem para outra dando um clique no botão Copy. As Figuras 8.8 e 8.9 mostram quais seriam os
efeitos de copiar imgFromImage para imgToImage usando os modos de cópia cmdSrcAnd e cmdSrcInvert.
FIGURA 8.7
Um formulário que contém duas imagens para ilustrar CopyMode.
FIGURA 8.8
Uma operação de cópia usando o valor de modo de cópia cmdSrcAnd.
64
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Uma operação de cópia usando o valor de modo de cópia cmSrcInvert.
FIGURA 8.9
A Listagem 8.3 mostra o código-fonte para o projeto, ilustrando os vários modos de cópia. Você encontrará esse código no CD que acompanha este livro.
Listagem 8.3 Projeto ilustrando o uso de CopyMode
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
imgCopyTo: TImage;
imgCopyFrom: TImage;
cbCopyMode: TComboBox;
btnDrawImages: TButton;
btnCopy: TButton;
procedure FormShow(Sender: TObject);
procedure btnCopyClick(Sender: TObject);
procedure btnDrawImagesClick(Sender: TObject);
private
procedure DrawImages;
procedure GetCanvasRect(AImage: TImage; var ARect: TRect);
end;
var
MainForm: TMainForm;
implementation
65
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.3 Continuação
{$R *.DFM}
procedure TMainForm.GetCanvasRect(AImage: TImage; var ARect: TRect);
var
R: TRect;
R2: TRect;
begin
R := AImage.Canvas.ClipRect;
with AImage do begin
ARect.TopLeft
:= Point(0, 0);
ARect.BottomRight := Point(Width, Height);
end;
R2 := ARect;
ARect := R2;
end;
procedure TMainForm.DrawImages;
var
R: TRect;
begin
// Desenha uma elipse em img1
with imgCopyTo.Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
GetCanvasRect(imgCopyTo, R);
FillRect(R);
Brush.Color := clRed;
Ellipse(10, 10, 100, 100);
end;
// Desenha uma elipse em img2
with imgCopyFrom.Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
GetCanvasRect(imgCopyFrom, R);
FillRect(R);
Brush.Color := clBlue;
Ellipse(30, 30, 120, 120);
end;
end;
procedure TMainForm.FormShow(Sender: TObject);
begin
// Inicializa a caixa de combinação para o primeiro item
cbCopyMode.ItemIndex := 0;
DrawImages;
end;
66
procedure TMainForm.btnCopyClick(Sender: TObject);
var
cm: Longint;
CopyToRect,
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.3 Continuação
CopyFromRect: TRect;
begin
// Determina modo de cópia com base na seleção da cx. de combinação
case cbCopyMode.ItemIndex of
0: cm := cmBlackNess;
1: cm := cmDstInvert;
2: cm := cmMergeCopy;
3: cm := cmMergePaint;
4: cm := cmNotSrcCopy;
5: cm := cmNotSrcErase;
6: cm := cmPatCopy;
7: cm := cmPatInvert;
8: cm := cmPatPaint;
9: cm := cmSrcAnd;
10: cm := cmSrcCopy;
11: cm := cmSrcErase;
12: cm := cmSrcInvert;
13: cm := cmSrcPaint;
14: cm := cmWhiteness;
else
cm := cmSrcCopy;
end;
// Atribui modo selecionado à propr. CopyMode de Image1.
imgCopyTo.Canvas.CopyMode := cm;
GetCanvasRect(imgCopyTo, CopyToRect);
GetCanvasRect(imgCopyFrom, CopyFromRect);
// Agora copia Image2 para Image1 usando valor de CopyMode de Image1
imgCopyTo.Canvas.CopyRect(CopyToRect, imgCopyFrom.Canvas, CopyFromRect);
end;
procedure TMainForm.btnDrawImagesClick(Sender: TObject);
begin
DrawImages;
end;
end.
Esse projeto pinta inicialmente uma elipse nos dois componentes de TImage: imgFromImage e imgToImage.
Quando o botão Copy for acionado, imgFromImage será copiado para imgToImage1 usando o valor de CopyMode
especificado por cbCopyMode.
Outras propriedades
TCanvas possui outras propriedades que discutiremos mais ao ilustrarmos como usá-las em técnicas de co-
dificação. Esta seção discute rapidamente estas propriedades.
TCanvas.ClipRect representa uma região de desenho da tela na qual o desenho pode ser realizado.
Você pode usar ClipRect para limitar a área que pode ser desenhada por uma determinada tela.
67
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
ATENÇÃO
Inicialmente, ClipRect representa a área de desenho inteira de Canvas. Você pode ser tentado a usar a propriedade ClipRect para obter os limites de uma tela. No entanto, isso poderia causar problemas. ClipRect
nem sempre representará o tamanho total de seu componente. Ele pode ser menor do que a área de exibição de Canvas.
TCanvas.Handle oferece acesso ao contexto de dispositivo real que a instância de TCanvas encapsula. Os
contextos de dispositivo são discutidos mais adiante neste capítulo.
TCanvas.PenPos é simplesmente um local de coordenadas X,Y da caneta da tela. Você pode alterar a
posição da caneta usando os métodos de TCanvas – MoveTo( ), LineTo( ), PolyLine( ), TextOut( ) e assim
por diante.
Uso de métodos de TCanvas
A classe TCanvas encapsula muitas funções de desenho da GDI. Com os métodos de TCanvas, você pode desenhar linhas e formas, escrever texto, copiar áreas de uma tela para outra e ainda esticar uma área na tela
para preencher uma área maior.
Desenhando linhas com TCanvas
TCanvas.MoveTo( ) muda a posição de desenho de Canvas.Pen na superfície de Canvas. O código a seguir, por
exemplo, move a posição de desenho para o canto superior esquerdo da tela:
Canvas.MoveTo(0, 0);
TCanvas.LineTo( ) desenha uma linha na tela a partir de sua posição atual para a posição especificada
pelos parâmetros passados a LineTo( ). Use MoveTo( ) com LineTo( ) para desenhar linhas em qualquer lu-
gar da tela. O código a seguir desenha uma linha da posição superior esquerda da área de cliente do formulário para o canto inferior direito do formulário:
Canvas.MoveTo(0, 0;
Canvas.LineTo(ClientWidth, ClientHeight);
Você já viu como usar os métodos MoveTo( ) e LineTo( ) na seção que explica a propriedade TCanvas.Pen.
NOTA
O Delphi agora aceita texto e layouts de controle orientados da direita para a esquerda; alguns controles
(como grid) mudam o sistema de coordenadas da tela para inverter o eixo X. Portanto, se você estiver rodando o Delphi em uma versão do Windows para o Oriente Médio, MoveTo(0,0) poderá ir para o canto superior direito da janela.
Desenhando formas com TCanvas
TCanvas oferece vários métodos para desenhar formas na tela: Arc( ), Chord( ), Ellipse( ), Pie( ), Polygon( ), PolyLine( ), Rectangle( ) e RoundRect( ). Para desenhar uma elipse na área do cliente do formulário, você usaria o método Ellipse( ) de Canvas, como no código a seguir:
68 Canvas.Ellipse(0, 0, ClientWidth, ClientHeight);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Você também pode preencher uma área na tela com um padrão de pincel especificado na propriedade Canvas.Brush.Style. O código a seguir desenha uma elipse e preenche seu interior com o padrão de
pincel especificado por Canvas.Brush.Style:
Canvas.Brush.Style := bsCross;
Canvas.Ellipse(0, 0, ClientWidth, ClientHeight);
Além disso, você viu como incluir um padrão de bitmap na propriedade TCanvas.Brush.Bitmap, que é
usada para preencher uma área na tela. Você também pode usar um padrão de bitmap para preencher
formas. Vamos demonstrar isso mais adiante.
Alguns dos outros métodos de desenho de formas de Canvas utilizam parâmetros adicionais ou diferentes para descrever a forma sendo desenhada. O método PolyLine( ), por exemplo, apanha um array de
registros TPoint que especifica posições ou coordenadas de pixels na tela a serem conectadas por uma linha – do tipo ligue os pontos. Um TPoint é um registro no Delphi 5 que significa uma coordenada X,Y.
Um TPoint é definido como:
TPoint = record
X: Integer;
Y: Integer;
end;
Exemplo de código para desenhar formas
A Listagem 8.4 ilustra o uso de vários métodos de desenho de forma de TCanvas. Você poderá encontrar
este projeto no CD que acompanha este livro.
Listagem 8.4 Uma ilustração das operações de desenho de forma
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics,
Controls, Forms, Dialogs, Menus;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiShapes: TMenuItem;
mmiArc: TMenuItem;
mmiChord: TMenuItem;
mmiEllipse: TMenuItem;
mmiPie: TMenuItem;
mmiPolygon: TMenuItem;
mmiPolyline: TMenuItem;
mmiRectangle: TMenuItem;
mmiRoundRect: TMenuItem;
N1: TMenuItem;
mmiFill: TMenuItem;
mmiUseBitmapPattern: TMenuItem;
mmiPolyBezier: TMenuItem;
procedure mmiFillClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
69
Listagem 8.4 Continuação
procedure mmiArcClick(Sender: TObject);
procedure mmiChordClick(Sender: TObject);
procedure mmiEllipseClick(Sender: TObject);
procedure mmiUseBitmapPatternClick(Sender: TObject);
procedure mmiPieClick(Sender: TObject);
procedure mmiPolygonClick(Sender: TObject);
procedure mmiPolylineClick(Sender: TObject);
procedure mmiRectangleClick(Sender: TObject);
procedure mmiRoundRectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure mmiPolyBezierClick(Sender: TObject);
private
FBitmap: TBitmap;
public
procedure ClearCanvas;
procedure SetFillPattern;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ClearCanvas;
begin
// Apaga o conteúdo da tela de Canvas
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
FillRect(ClientRect);
end;
end;
procedure TMainForm.SetFillPattern;
begin
{ Determina se a forma deve ser desenhada com um padrão de bitmap, para nesse
caso carregar um bitmap. Se não, usa o padrão de pincel. }
if mmiUseBitmapPattern.Checked then
Canvas.Brush.Bitmap := FBitmap
else
with Canvas.Brush do
begin
Bitmap := nil;
Color := clBlue;
Style := bsCross;
end;
end;
70
procedure TMainForm.mmiFillClick(Sender: TObject);
begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.4 Continuação
mmiFill.Checked := not mmiFill.Checked;
{ Se mmiUseBitmapPattern foi marcado, desmarca para definir o
bitmap do pincel como nulo. }
if mmiUseBitmapPattern.Checked then
begin
mmiUseBitmapPattern.Checked := not mmiUseBitmapPattern.Checked;
Canvas.Brush.Bitmap := nil;
end;
end;
procedure TMainForm.mmiUseBitmapPatternClick(Sender: TObject);
begin
{ Define mmiFil1.Checked mmiUseBitmapPattern.Checked. Isso chamará o
procedimento SetFillPattern. Porém, se mmiUseBitmapPattern estiver
sendo definido, define Canvas.Brush.Bitmap como nulo. }
mmiUseBitmapPattern.Checked := not mmiUseBitmapPattern.Checked;
mmiFill.Checked := mmiUseBitmapPattern.Checked;
if not mmiUseBitmapPattern.Checked then
Canvas.Brush.Bitmap := nil;
end;
procedure TMainForm.mmiArcClick(Sender: TObject);
begin
ClearCanvas;
with ClientRect do
Canvas.Arc(Left, Top, Right, Bottom, Right, Top, Left, Top);
end;
procedure TMainForm.mmiChordClick(Sender: TObject);
begin
ClearCanvas;
with ClientRect do
begin
if mmiFill.Checked then
SetFillPattern;
Canvas.Chord(Left, Top, Right, Bottom, Right, Top, Left, Top);
end;
end;
procedure TMainForm.mmiEllipseClick(Sender: TObject);
begin
ClearCanvas;
if mmiFill.Checked then
SetFillPattern;
Canvas.Ellipse(0, 0, ClientWidth, ClientHeight);
end;
procedure TMainForm.mmiPieClick(Sender: TObject);
begin
ClearCanvas;
if mmiFill.Checked then
SetFillPattern;
Canvas.Pie(0, 0, ClientWidth, ClientHeight, 50, 5, 300, 50);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
71
Listagem 8.4 Continuação
procedure TMainForm.mmiPolygonClick(Sender: TObject);
begin
ClearCanvas;
if mmiFill.Checked then
SetFillPattern;
Canvas.Polygon([Point(0, 0), Point(150, 20), Point(230, 130),
Point(40, 120)]);
end;
procedure TMainForm.mmiPolylineClick(Sender: TObject);
begin
ClearCanvas;
Canvas.PolyLine([Point(0, 0), Point(120, 30), Point(250, 120),
Point(140, 200), Point(80, 100), Point(30, 30)]);
end;
procedure TMainForm.mmiRectangleClick(Sender: TObject);
begin
ClearCanvas;
if mmiFill.Checked then
SetFillPattern;
Canvas.Rectangle(10 , 10, 125, 240);
end;
procedure TMainForm.mmiRoundRectClick(Sender: TObject);
begin
ClearCanvas;
if mmiFill.Checked then
SetFillPattern;
Canvas.RoundRect(15, 15, 150, 200, 50, 50);
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FBitmap := TBitmap.Create;
FBitMap.LoadFromFile(‘Pattern.bmp’);
Canvas.Brush.Bitmap := nil;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FBitmap.Free;
end;
procedure TMainForm.mmiPolyBezierClick(Sender: TObject);
begin
ClearCanvas;
Canvas.PolyBezier([Point(0, 100), Point(100, 0), Point(200, 50),
Point(300, 100)]);
end;
end.
72
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Os manipuladores de evento do menu principal realizam as funções de desenho de forma. Dois métodos públicos, ClearCanvas( ) e SetFillPattern( ), servem como funções auxiliadoras para os manipuladores de evento. Os oito primeiros itens do menu resultam em uma função de desenho de forma sendo
desenhada na tela do formulário. Os dois últimos itens, “Fill” e “Use Bitmap Pattern”, especificam se a
forma deve ser preenchida com um padrão de pincel ou padrão de bitmap, respectivamente.
Você já deve estar acostumado com a funcionalidade de ClearCanvas( ). O método SetFillPattern( )
determina se deve ser usado um padrão de pincel ou um padrão de bitmap para preencher as formas desenhadas pelos outros métodos. Se um bitmap for selecionado, ele será atribuído à propriedade Canvas.Brush.Bitmap.
Todos os manipuladores de evento para desenho de forma chamam ClearCanvas( ) para apagar o
que foi desenhado anteriormente na tela. Depois eles chamam SetFillPattern( ) se a propriedade mmiFill.Checked estiver definida como True. Finalmente, a rotina de desenho apropriada de TCanvas é chamada. Os componentes da origem discutem a finalidade de cada função. Um método que merece ser mencionado aqui é mmiPolylineClick( ).
Quando você estiver desenhando formas, um contorno delimitador é necessário para preencher
uma área com um padrão de pincel ou bitmap. Embora seja possível usar PolyLine( ) e FloodFill( ) para
criar um contorno delimitador preenchido com um padrão, isso não deve ser encorajado. PolyLine( ) é
usado especificamente para desenhar linhas. Se você quiser desenhar polígonos preenchidos, chame o
método TCanvas.Polygon( ). Uma falha em um pixel no local em que as linhas de PolyLine( ) são desenhadas permitirá que a chamada a FloodFill( ) vaze e preencha a tela inteira. O desenho de formas preenchidas com Polygon( ) usa técnicas matemáticas que são imunes a variações no posicionamento de pixel.
Pintando texto com TCanvas
TCanvas encapsula rotinas da GDI do Win32 para desenhar texto em uma superfície de desenho. As próxi-
mas seções ilustram como usar essas rotinas e também como usar as funções da GDI do Win32 que não
são encapsuladas pela classe TCanvas.
Usando as rotinas de desenho de texto de TCanvas
Nos capítulos anteriores, você usou a função TextOut( ) de Canvas para desenhar texto na área do cliente
do formulário.Canvas possui alguns outros métodos úteis para determinar o tamanho, em pixels, do texto
contido em um string, usando a fonte desenhada de Canvas. Essas funções são TextWidth( ) e TextHeight( ).
O código a seguir determina a largura e a altura para a string “Delphi 5 – Yes!”:
var
S: String;
Windows, hipertexto: Integer;
begin
S := ‘Delphi 5 – Yes!’;
w := Canvas.TextWidth(S);
h := Canvas.TextHeigth(S);
end.
O método TextRect( ) também escreve texto no formulário, mas somente dentro de um retângulo
especificado por uma estrutura TRect. O texto fora dos limites de TRect será recortado. Na linha
Canvas.TextRect(R,0,0,’Delphi 5 Yes!’);
a string “Delphi 5 Yes!” é escrita na tela no local 0,0. No entanto, a parte da string que fica fora das coordenadas especificadas por R, uma estrutura TRect, é removida.
A Listagem 8.5 ilustra o uso de parte das rotinas de desenho de texto.
73
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.5 Uma unidade que ilustra as operações de desenho de texto
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Menus;
const
DString = ‘Delphi 5 YES!’;
DString2 = ‘Delphi 5 Rocks!’;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiText: TMenuItem;
mmiTextRect: TMenuItem;
mmiTextSize: TMenuItem;
mmiDrawTextCenter: TMenuItem;
mmiDrawTextRight: TMenuItem;
mmiDrawTextLeft: TMenuItem;
procedure mmiTextRectClick(Sender: TObject);
procedure mmiTextSizeClick(Sender: TObject);
procedure mmiDrawTextCenterClick(Sender: TObject);
procedure mmiDrawTextRightClick(Sender: TObject);
procedure mmiDrawTextLeftClick(Sender: TObject);
public
procedure ClearCanvas;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.ClearCanvas;
begin
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
FillRect(ClipRect);
end;
end;
74
procedure TMainForm.mmiTextRectClick(Sender: TObject);
var
R: TRect;
TWidth, THeight: integer;
begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.5 Continuação
ClearCanvas;
Canvas.Font.Size := 18;
// Calcula a largura/altura da string de texto
TWidth := Canvas.TextWidth(DString);
THeight := Canvas.TextHeight(DString);
{ Inicializa uma estrut. TRect. A altura desse retângulo será
metade da altura da string de texto. Isso serve para ilustrar
o corte do texto pelo retângulo desenhado. }
R := Rect(1, THeight div 2, TWidth + 1, THeight+(THeight div 2));
// Desenha um retângulo com base nos tamanhos do texto
Canvas.Rectangle(R.Left-1, R.Top-1, R.Right+1, R.Bottom+1);
// Desenha o texto dentro do retângulo
Canvas.TextRect(R,0,0,DString);
end;
procedure TMainForm.mmiTextSizeClick(Sender: TObject);
begin
ClearCanvas;
with Canvas do
begin
Font.Size := 18;
TextOut(10, 10, DString);
TextOut(50, 50, ‘TextWidth = ‘+IntToStr(TextWidth(DString)));
TextOut(100, 100, ‘TextHeight = ‘+IntToStr(TextHeight(DString)));
end;
end;
procedure TMainForm.mmiDrawTextCenterClick(Sender: TObject);
var
R: TRect;
begin
ClearCanvas;
Canvas.Font.Size := 10;
R := Rect(10, 10, 80, 100);
// Desenha um retângulo para cercar os limites de TRect por 2 pixels }
Canvas.Rectangle(R.Left-2, R.Top-2, R.Right+2, R.Bottom+2);
// Desenha texto centralizado especificando a opção dt_Center
DrawText(Canvas.Handle, PChar(DString2), -1, R, dt_WordBreak or dt_Center);
end;
procedure TMainForm.mmiDrawTextRightClick(Sender: TObject);
var
R: TRect;
begin
ClearCanvas;
Canvas.Font.Size := 10;
R := Rect(10, 10, 80, 100);
// Desenha retângulo para cercar os limites de TRect em 2 pixels
Canvas.Rectangle(R.Left-2, R.Top-2, R.Right+2, R.Bottom+2);
// Desenha texto alinhado à direita especificando a opção dt_Right
DrawText(Canvas.Handle, PChar(DString2), -1, R, dt_WordBreak or dt_Right);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
75
Listagem 8.5 Continuação
procedure TMainForm.mmiDrawTextLeftClick(Sender: TObject);
var
R: TRect;
begin
ClearCanvas;
Canvas.Font.Size := 10;
R := Rect(10, 10, 80, 100);
// Desenha retângulo para cercar os limites de TRect em 2 pixels
Canvas.Rectangle(R.Left-2, R.Top-2, R.Right+2, R.Bottom+2);
// Desenha texto alinhado à esquerda especificando a opção dt_Left
DrawText(Canvas.Handle, PChar(DString2), -1, R, dt_WordBreak or dt_Left);
end;
end.
Como em outros projetos, este projeto contém o método ClearCanvas( ) para apagar o conteúdo da
tela de desenho (canvas) do formulário.
Os diversos métodos do formulário principal são manipuladores de evento para o menu principal
do formulário.
O método mmiTextRectClick( ) ilustra o uso do método TextRect( ). Ele determina a largura e a altura
do texto, e desenha o texto dentro de um retângulo da altura do tamanho de texto original. Sua saída
aparece na Figura 8.10.
FIGURA 8.10
A saída de mmiTextRectClick( ).
mmiTextSizeClick( ) mostra como determinar o tamanho de uma string de
TCanvas.TextWidth( ) e TCanvas.TextHeigth( ). Sua saída aparece na Figura 8.11.
texto usando os métodos
76
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.11
A saída de mmiTextSizeClick( ).
Usando rotinas de saída de texto da GDI sem TCanvas
No projeto de exemplo, os métodos mmiDrawTextCenter( ), mmiDrawTextRight( ) e mmiDrawTextLeft( ) ilustram
todos eles o uso da função DrawText( ) da GDI do Win32. DrawText( ) é uma função da GDI não encapsulada pela classe TCanvas.
Esse código ilustra como o encapsulamento do Delphi 5 da GDI do Win32 através da classe TCanvas
não impede que você utilize as inúmeras funções da GDI do Win32. Em vez disso, TCanvas realmente só
simplifica o uso das rotinas mais comuns enquanto permite chamar qualquer função da GDI do Win32
que você possa precisar.
Se você verificar as várias funções da GDI, como BitBlt( ) e DrawText( ), descobrirá que um dos parâmetros necessários é um DC (Device Context), ou contexto de dispositivo. O contexto de dispositivo é
acessado por meio da propriedade Handle da tela de desenho. TCanvas.Handle é o DC para essa tela.
Contextos de dispositivo
Contextos de dispositivo (DCs) são alças fornecidas pelo Win32 para identificar a conexão de uma
aplicação Win32 a um dispositivo de saída, como um monitor, uma impressora ou uma plotadora
através de um driver de dispositivo. Na programação tradicional do Windows, você é responsável por
solicitar um DC sempre que precisar pintar a superfície de uma janela; depois, quando terminar, você
precisa devolver o DC para o Windows. O Delphi 5 simplifica o gerenciamento de DCs, encapsulando
o gerenciamento de DC na classe TCanvas. Na verdade, TCanvas até mesmo apanha seu DC, guardando-o para mais tarde, de modo que os pedidos à Win32 ocorram com menos freqüência, agilizando
assim a execução geral do seu programa.
Para ver como usar TCanvas com uma função da GDI do Win32, você usou a rotina da GDI DrawText( ) para gerar um texto com recursos de formatação avançados. DrawText utiliza os cinco parâmetros a
seguir:
Parâmetro
Descrição
DC
Contexto de dispositivo da superfície de desenho.
Str
Ponteiro para um buffer contendo o texto a ser desenhado. Esse deve ser uma string terminada em nulo se o parâmetro Count for -1.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
77
Parâmetro
Descrição
Count
Número de bytes em Str. Se esse valor for -1, Str é um ponteiro para uma string terminada
em nulo.
Rect
Ponteiro para uma estrutura TRect contendo as coordenadas do retângulo em que o texto é
formatado.
Format
Um campo de bit que contém flags especificando as várias opções de formatação para Str.
No exemplo, você inicializa uma estrutura TRect usando a função Rect( ). Você usará a estrutura
para desenhar um retângulo ao redor do texto desenhado com a função DrawText( ). Cada um dos três
métodos passa um conjunto diferente de flags de formatação para a função DrawText( ). Os flags de formatação dt_WordBreak e dt_Center são passados para a função DrawText( ) para centralizar o texto no retângulo especificado pela variável de TRect, R. dt_WordBreak, realizando um OR com dt_Right, é usado para alinhar o texto à direita no retângulo. Da mesma forma, dt+WordBreak, realizando um OR com dt_Left, alinha o
texto à esquerda. O especificador dt_WordBreak quebra o texto dentro da largura indicada pelo parâmetro
do retângulo e modifica a altura do retângulo para conter o texto depois de ser quebrado.
A saída de mmiDrawTextCenterClick( ) aparece na Figura 8.12.
FIGURA 8.12
A saída de mmiDrawTextCenterClick( ).
TCanvas também possui os métodos Draw( ), Copy( ), CopyRect( ) e StretchDraw( ), que permitem dese-
nhar, copiar, expandir e encurtar uma imagem ou uma parte de uma imagem em outra tela de desenho.
Você usará CopyRect( ) quando mostrarmos como criar um programa de pintura mais adiante neste capítulo. Além disso, o Capítulo 16 mostra como usar o método StretcjDraw( ) para esticar uma imagem de
bitmap na área do cliente de um formulário.
Sistemas de coordenadas e modos de mapeamento
A maioria das rotinas de desenho exige um conjunto de coordenadas que especifica o local em que o desenho deve ocorrer. Essas coordenadas são baseadas em uma unidade de medida, como o pixel. Além
disso, as rotinas da GDI consideram uma orientação para o eixo vertical e horizontal – ou seja, como o
aumento ou a diminuição dos valores das coordenadas X,Y move a posição em que ocorre o desenho. O
Win32 utiliza dois fatores para realizar rotinas de desenho. São eles o sistema de coordenadas e o modo
78 de mapeamento do Win32 para a área que está sendo desenhada.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Os sistemas de coordenadas do Win32, geralmente, não são diferentes de qualquer outro sistema de
coordenadas. Você define uma coordenada para um eixo X,Y e o Win32 representa esse local em um
ponto da sua superfície de desenho com base em uma determinada orientação. O Win32 utiliza três sistemas de coordenadas para as áreas de plotagem nas superfícies de desenho, chamados dispositivo, lógico e
mundo. O Windows 95 não aceita transformações de mundo (rotação de bitmap, shearing, giro etc.).
Explicaremos os dois primeiros modos neste capítulo.
Coordenadas do dispositivo
Coordenadas do dispositivo, como o nome sugere, refere-se ao dispositivo em que o Win32 está rodando. Suas medidas são em pixels, e a orientação é tal que os eixos horizontal e vertical aumentam da esquerda para a direita e de cima para baixo. Por exemplo, se você estiver rodando o Windows em uma tela
de 640x480 pixels, as coordenadas no canto superior esquerdo do seu dispositivo são (0,0), enquanto as
coordenadas do canto inferior direito são (639,479).
Coordenadas lógicas
Coordenadas lógicas referem-se ao sistema de coordenadas usado por qualquer área no Win32 que tenha
um contexto de dispositivo (ou DC), como uma tela, um formulário ou a área do cliente de um formulário. A diferença entre coordenadas do dispositivo e lógicas será explicada mais adiante. As coordenadas
da tela, da janela do formulário e da ára do cliente do formulário são explicadas em primeiro lugar.
Coordenadas da tela
Coordenadas da tela referem-se ao dispositivo de exibição; portanto, segue-se que as coordenadas são
baseadas em medidas de pixel. Em uma tela de 640x480, Screen.Width e Screen.Height também são 640 e
480 pixels, respectivamente. Para obter um contexto de dispositivo para a tela, use a função GetDC( ) da
API do Win32. Você precisa observar qualquer função que recupere um contexto de dispositivo com
uma chamada para ReleaseDC( ). O código a seguir ilustra isso:
var
ScreenDC: HDC;
begin
ScreenDC := GetDC(0);
try
{ Faz o que for preciso com ScreenDC }
finally
ReleaseDC(0, ScreenDC);
end;
end;
Coordenadas do formulário
Coordenadas do formulário significa o mesmo que o termo coordenadas da janela e refere-se a um formulário ou janela inteira, incluindo a barra de título e as bordas. O Delphi 5 não oferece um DC para a área
de desenho do formulário através de uma propriedade do formulário, mas você pode obter um usando a
função GetWindowDC( ) da API do Win32, da seguinte forma:
MyDC := GetWindowDC(Form1.Handle);
Essa função retorna o DC para a alça da janela passada a ela.
79
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
NOTA
Você pode usar o objeto TCanvas para encapsular os contextos de dispositivo obtidos das chamadas a
GetDC( ) e GetWindowDC( ). Isso permitirá usar os métodos de TCanvas contra esses contextos de dispositivo.
Você só precisa criar uma instância de TCanvas e depois atribuir o resultado de GetDC( ) ou GetWindowDC( )
à propriedade TCanvas.Handle. Isso funciona porque o objeto TCanvas tem propriedade da alça que você
lhe atribui, e liberará o DC quando a tela de desenho for liberada. O código a seguir ilustra essa técnica:
var
c: Tcanvas;
begin
c := TCanvas.Create;
try
c.Handle := GetDC(0);
c.TextOut(10, 10, “Hello World’);
finally
c.Free;
end;
end;
As coordenadas da área do cliente de um formulário referem-se à área do cliente de um formulário
cujo DC é a propriedade Handle do Canvas do formulário e cujas medidas são obtidas por Canvas.ClientWidht
e Canvas.ClientHeight.
Mapeamento de coordenadas
Mas por que não usar simplesmente coordenadas de dispositivo em vez de coordenadas lógicas ao se realizar rotinas de desenho? Examine a linha de código a seguir:
Form1.Canvas.TextOut(0, 0, ‘Canto sup. esquerdo do formulário’);
Essa linha coloca a string no canto superior esquerdo do formulário. As coordenadas (0,0) são mapeadas para a posição (0,0) no contexto de dispositivo do formulário – coordenadas lógicas. No entanto,
a posição (0,) para o formulário é completamente diferente nas coordenadas do dispositivo e depende de
onde o formulário está localizado na sua tela inteira. Se o formulário estiver localizado no canto superior
esquerdo da sua tela, então as coordenadas (0,0) do formulário poderão de fato estar mapeadas para
(0,0) nas coordenadas do dispositivo. No entanto, quando você mover o formulário para outro local, a
posição (0,0) do formulário será mapeada para um local completamente diferente no dispositivo.
DICA
Você pode obter um ponto com base nas coordenadas do dispositivo desse ponto conforme representadas
nas coordenadas lógicas, e vice-versa, usando as funções ClientToScreen( ) e ScreenToClient( ) da API
do Win32, respectivamente. Estes também são métodos de TControl. Observe que isso só funciona com
DCs de tela associados a um controle visível. Para DCs de impressora ou metafile, que não são baseados
na tela, converta os pixels lógicos para os pixels do dispositivo usando a função LPtoDP( ) do Win32. Veja
também D{toLP( ) na ajuda on-line do Win32.
Por baixo da chamada a Canvas.TextOut( ), o Win32 na realidade utiliza coordenadas de dispositivo.
Para o Win32 fazer isso, ele precisa “mapear”, para coordenadas de dispositivo, as coordenadas lógicas
do DC para onde o desenho é enviado. Ele faz isso usando o modo de mapeamento associado ao DC.
80
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Outro motivo para se usar coordenadas lógicas é que você pode não querer usar pixels para realizar
rotinas de desenho. Talvez você queira desenhar usando polegadas ou milímetros. O Win32 permite mudar a unidade em que realizar suas rotinas de desenho alterando seu modo de mapeamento, como veremos em breve.
Os modos de mapeamento definem dois atributos para o DC: a tradução que o Win32 utiliza para
converter unidades lógicas em unidades de dispositivo e a orientação do eixo X,Y para o DC.
NOTA
Pode não ser aparente que rotinas de desenho, modos de mapeamento, orientação e outros estejam associados a um DC, pois, no Delphi 5, você usa a “canvas” para desenhar. Lembre-se de que TCanvas é um
wrapper para um DC. Isso se torna mais óbvio ao compararmos as rotinas da GDI do Win32 com as rotinas
equivalentes de Canvas. Aqui estão alguns exemplos:
Rotina de Canvas: Canvas.Rectangle(0, 0, 50, 50);
Rotina da GDI: Rectangle(ADC, 0, 0, 50, 50);
Quando você estiver usando a rotina da GDI, um DC será passado para a função, enquanto a rotina de
Canvas utiliza o DC que ela encapsula.
O Win32 permite que você defina o modo de mapeamento para um DC ou TCanvas.Handle. Na realidade, o Win32 define oito modos de mapeamento que você pode utilizar. Esses modos de mapeamento,
juntamente com seus atributos, aparecem na Tabela 8.4. O projeto de exemplo na próxima seção ilustra
melhor os modos de mapeamento.
Tabela 8.4 Modos de mapeamento do Win32
Modo de mapeamento
Tamanho da unidade lógica
Orientação (X,Y)
MM_ANISOTROPIC
Arbitrário (x < > y) ou (x = y)
Definível/definível
MM_HIENGLISH
0,001 polegada
Direita/acima
MM_HIMETRIC
0,01 mm
Direita/acima
MM_ISOTROPIC
Arbitrário (x = y)
Definiível/definível
MM_LOENGLISH
0,01 polegada
Direita/acima
MM_LOMETRIC
0,1 mm
Direita/acima
MM_TEXT
1 pixel
Direita/abaixo
MM_TWIPS
1/1440 polegada
Direita/acima
O Win32 define algumas funções que permitem alterar ou descobrir informações sobre os modos
de mapeamento para um determinado DC. Veja a seguir um resumo dessas funções:
l
SetMapMode( ).
Define o modo de mapeamento para um determinado contexto de dispositivo.
l
GetMapMode( ).
Apanha o modo de mapeamento para um determinado contexto de dispositivo.
l
SetWindowOrgEx( ).
l
SetViewPortOrgEx( ).
Define a origem de uma janela (ponto 0,0) para um determinado DC.
Define a origem de uma viewport (ponto 0,0) para um determinado DC.
81
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
l
. Define as extensões X,Y para o DC de uma determinada janela. Esses valores
são usados em conjunto com as extensões X,Y da viewport para realizar tradução de unidades lógicas para unidades físicas.
SetWindowExtEx( )
SetViewPortExtEx( ). Define as extensões X,Y para o DC de uma determinada viewport. Esses valores são usados em conjunto com as extensões X,Y da janela para realizar tradução de unidades
lógicas para unidades físicas.
Observe que essas funções contêm a palavra Window ou ViewPort. A janela ou viewport é simplesmente
um meio pelo qual a GDI do Win32 pode realizar a tradução de unidades lógicas para dispositivo. As
funções com Window referem-se ao sistema de coordenadas lógicas, enquanto ViewPort refere-se ao sistema
de coordenadas do dispositivo. Com a exceção dos modos de mapeamento MM_ANISOTROPIC e MM_ISOTROPIC,
você não precisa se preocupar muito com isso. Na realidade, o Win32 utiliza o modo de mapeamento
MM_TEXT por default.
NOTA
MM+TEXT é o modo de mapeamento default, e ele mapeia coordenadas lógicas 1:1 com coordenadas do
dispositivo. Assim, você está sempre usando coordenadas do dispositivo em todos os DCs, a menos que
mude o modo de mapeamento. Existem algumas funções da API onde isso é significativo: alturas de fonte,
por exemplo, sempre são especificadas em pixels do dispositivo, e não em pixels lógicos.
Definindo o modo de mapeamento
Você notará que cada modo de mapeamento utiliza um tamanho de unidade lógica diferente. Em alguns
casos, pode ser conveniente usar um modo de mapeamento diferente por esse motivo. Por exemplo, você
pode querer exibir uma linha com dois polegadas de largura, independente da resolução do seu dispositivo
de saída. Nesse caso, MM_LOENGLISH seria um bom candidato para o modo de mapeamento utilizado.
Como exemplo de desenho de um retângulo de uma polegada no formulário, você primeiro muda o
modo de mapeamento de Form1.Canvas.Handle para MM_HIENGLISH ou MM_LOENGLISH:
SetMapMode(Canvas.Handle, MM_LOENGLISH);
Depois você desenha o retângulo usando as unidades de medida apropriadas para um retângulo de
uma polegada. Como MM_LOENGLISH usa 1/100 polegada, você simplesmente passa o valor 100, da seguinte
maneira (isso também será ilustrado mais adiante, em outro exemplo):
Canvas.Rectangle(0, 0, 100, 100);
Visto que MM_TEXT utiliza pixels como unidade de medida, você pode usar a função GetDeviceCaps( )
da API do Win32 para recuperar as informações de que precisa para realizar a tradução de pixels para
polegadas ou milímetros. Aí poderá fazer seus próprios cálculos, se desejar. Isso é demonstrado no Capítulo 10. Os modos de mapeamento são uma maneira de permitir que o Win32 faça o trabalho para você.
No entanto, observe que você provavelmente nunca conseguirá obter medidas exatas para exibição na
tela. Existem alguns motivos para isso: o Windows não pode registrar o tamanho de exibição da tela – ele
precisa adivinhar. Além disso, o Windows geralmente incha as escalas de exibição para melhorar a legibilidade do texto em monitores de pior resolução. Assim, por exemplo, você poderá notar que uma fonte
de corpo 10 na tela terá quase o mesmo tamanho de uma fonte de corpo 12 ou 14 no papel.
Definindo as extensões de janela/viewport
82
As funções SetWindowExtEx( ) e SetViewPortExtEx( ) permitem definir como o Win32 traduz unidades lógicas em unidades do dispositivo. Essas funções possuem efeito apenas quando o modo de mapeamento da
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
janela é MM_ANIISOTROPIC ou MM_ISOTROPIC. De outra maneira, elas serão ignoradas. Portanto, as linhas de código a seguir significa que uma unidade lógica requer duas unidades do dispositivo (pixels):
SetWindowExtEx(Canvas.Handle, 1, 1, nil)
SetViewportExtEx(Canvas.Handle, 2, 2, nil);
Da mesma forma, estas linhas de código significa que cinco unidades lógicas exigem dez unidades
do dispositivo:
SetWindowExtEx(Canvas.Handle, 5, 5, nil)
SetViewportExtEx(Canvas.Handle, 10, 10, nil);
Observe que isso é exatamente o mesmo que o exemplo anterior. Ambos possuem o mesmo efeito,
o de definir uma taxa de 1:2 de unidades lógicas para dispositivo. Veja um exemplo de como isso pode
ser usado para mudar as unidades para um formulário:
SetWindowExtEx(Canvas.Handle, 500, 500, nil)
SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
NOTA
A mudança do modo de mapeamento para um contexto de dispositivo representado por uma tela de desenho da VCL não é duradouro, o que significa que ele pode retornar ao seu modo original. Em geral, o
modo de mapeamento precisa ser definido dentro do manipulador que realiza o próprio desenho.
Isso permite trabalhar com um formulário cuja largura e altura do cliente sejam 500x500 unidades
(não pixels), a despeito de qualquer redimensionamento do formulário.
As funções SetWindowOrgEx( ) e SetViewPortOrgEx( ) lhe permitem deslocar o ponto de origem, ou posição (0,0), a qual, por default, é o canto superior esquerdo da ára do cliente de um formulário no modo de
mapeamento MM_TEXT. Normalmente, você só modifica a origem da viewport. Por exemplo, a linha a seguir configura um sistema de coordenadas de quatro quadrantes, como o que é ilustrado na Figura 8.13:
SetViewportOrgEx(Canvas.Handle, ClientWidth div 2, ClientHeight div 2, nil);
FIGURA 8.13
Um sistema de coordenadas de quatro quadrantes.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
83
Observe que passamos um valor nil como último parâmetro das funções SetWindowOrgEx( ), SetViewPortOrgEx( ), SetWindowExtEx( ) e SetViewPortExtEx( ). As funções SetWindowOrgEx( ) e SetViewPortOrgEx( ) utilizam uma variável TPoint que recebe o último valor de origem, de modo que você possa restaurar a origem para o DC, se for preciso. As funções SetWindowExtEx( ) e SetViewPortExtEx( ) utilizam uma estrutura
TSize para armazenar as extensões originais para o DC pelo mesmo motivo.
Projeto de exemplo do modo de mapeamento
A Listagem 8.6 apresenta a unidade para um projeto. Esse projeto ilustra como definir modos de mapeamento, origens de janela e viewport, e extensões de janelas e viewports. Ele também ilustra como desenhar várias formas usando os métodos de TCanvas. Você pode carregar esse projeto a partir do CD que
acompanha este livro.
Listagem 8.6 Uma ilustração dos modos de mapeamento
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Menus, DB, DBCGrids, DBTables;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiMappingMode: TMenuItem;
mmiMM_ISOTROPIC: TMenuItem;
mmiMM_ANSITROPIC: TMenuItem;
mmiMM_LOENGLISH: TMenuItem;
mmiMM_HIINGLISH: TMenuItem;
mmiMM_LOMETRIC: TMenuItem;
mmiMM_HIMETRIC: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure mmiMM_ISOTROPICClick(Sender: TObject);
procedure mmiMM_ANSITROPICClick(Sender: TObject);
procedure mmiMM_LOENGLISHClick(Sender: TObject);
procedure mmiMM_HIINGLISHClick(Sender: TObject);
procedure mmiMM_LOMETRICClick(Sender: TObject);
procedure mmiMM_HIMETRICClick(Sender: TObject);
public
MappingMode: Integer;
procedure ClearCanvas;
procedure DrawMapMode(Sender: TObject);
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
84 procedure TMainForm.ClearCanvas;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.6 Continuação
begin
with Canvas do
begin
Brush.Style := bsSolid;
Brush.Color := clWhite;
FillRect(ClipRect);
end;
end;
procedure TMainForm.DrawMapMode(Sender: TObject);
var
PrevMapMode: Integer;
begin
ClearCanvas;
Canvas.TextOut(0, 0, (Sender as TMenuItem).Caption);
// Define modo de map. como MM_LOENGLISH e salva o modo anterior
PrevMapMode := SetMapMode(Canvas.Handle, MappingMode);
try
// Define a origem da viewport como esquerda, inferior
SetViewPortOrgEx(Canvas.Handle, 0, ClientHeight, nil);
{ Desenha algumas formas para ilustrar desenho de formas com diferentes
modos de mapeamento especificados por MappingMode }
Canvas.Rectangle(0, 0, 200, 200);
Canvas.Rectangle(200, 200, 400, 400);
Canvas.Ellipse(200, 200, 400, 400);
Canvas.MoveTo(0, 0);
Canvas.LineTo(400, 400);
Canvas.MoveTo(0, 200);
Canvas.LineTo(200, 0);
finally
// Restaura modo de mapeamento anterior
SetMapMode(Canvas.Handle, PrevMapMode);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
MappingMode := MM_TEXT;
end;
procedure TMainForm.mmiMM_ISOTROPICClick(Sender: TObject);
var
PrevMapMode: Integer;
begin
ClearCanvas;
// Define modo de map. MM_ISOTROPIC e salva modo de map. anterior
PrevMapMode := SetMapMode(Canvas.Handle, MM_ISOTROPIC);
try
// Define extensão da janela para 500 x 500
SetWindowExtEx(Canvas.Handle, 500, 500, nil);
// Define extensão da Viewport para área do cliente da janela
SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
// Define ViewPortOrg para o centro da área do cliente
SetViewportOrgEx(Canvas.Handle, ClientWidth div 2, ClientHeight div 2, nil);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
85
Listagem 8.6 Continuação
// Desenha retângulo baseado nos valores atuais
Canvas.Rectangle(0, 0, 250, 250);
{ Define extensão da viewport para um valor diferente e
desenha outro retângulo. Continua a fazer isso mais três vezes
para que um retângulo seja desenhado para representar
o plano em um quadrado de quatro quadrantes }
SetViewportExtEx(Canvas.Handle, ClientWidth, -ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
SetViewportExtEx(Canvas.Handle, -ClientWidth, -ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
SetViewportExtEx(Canvas.Handle, -ClientWidth, ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
// Desenha uma elipse no centro da área do cliente
Canvas.Ellipse(-50, -50, 50, 50);
finally
// Restaura o modo de mapeamento anterior
SetMapMode(Canvas.Handle, PrevMapMode);
end;
end;
procedure TMainForm.mmiMM_ANSITROPICClick(Sender: TObject);
var
PrevMapMode: Integer;
begin
ClearCanvas;
// Define o modo de map. MM_ANISOTROPIC e salva o modo de map. anterior
PrevMapMode := SetMapMode(Canvas.Handle, MM_ANISOTROPIC);
try
// Define a extensão da janela para 500 x 500
SetWindowExtEx(Canvas.Handle, 500, 500, nil);
// Define extensão da Viewport para área do cliente da janela
SetViewportExtEx(Canvas.Handle, ClientWidth, ClientHeight, nil);
// Define ViewPortOrg para o centro da área do cliente
SetViewportOrgEx(Canvas.Handle, ClientWidth div 2, ClientHeight div 2, nil);
// Desenha retângulo baseado nos valores atuais
Canvas.Rectangle(0, 0, 250, 250);
{ Define extensão da viewport para um valor diferente e
desenha outro retângulo. Continua a fazer isso mais três vezes
para que um retângulo seja desenhado para representar
o plano em um quadrado de quatro quadrantes }
SetViewportExtEx(Canvas.Handle, ClientWidth, -ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
SetViewportExtEx(Canvas.Handle, -ClientWidth, -ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
86
SetViewportExtEx(Canvas.Handle, -ClientWidth, ClientHeight, nil);
Canvas.Rectangle(0, 0, 250, 250);
// Desenha uma elipse no centro da área do cliente
Canvas.Ellipse(-50, -50, 50, 50);
finally
// Restaura o modo de mapeamento anterior
SetMapMode(Canvas.Handle, PrevMapMode);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.6 Continuação
end;
end;
procedure TMainForm.mmiMM_LOENGLISHClick(Sender: TObject);
begin
MappingMode := MM_LOENGLISH;
DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_HIINGLISHClick(Sender: TObject);
begin
MappingMode := MM_HIENGLISH;
DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_LOMETRICClick(Sender: TObject);
begin
MappingMode := MM_LOMETRIC;
DrawMapMode(Sender);
end;
procedure TMainForm.mmiMM_HIMETRICClick(Sender: TObject);
begin
MappingMode := MM_HIMETRIC;
DrawMapMode(Sender);
end;
end.
O MappingMode do formulário principal é usado para conter o modo de mapeamento atual que é inicializado no método FormCreate( ) como MM_TEXT. Essa variável é definida sempre que os métodos
MMLOENGLISH1Click( ), MMHIENGLISH1Click( ), MMLOMETRIC1Click( ) e MMLOENGLISH1Click( ) são chamados por seus
respectivos menus. Esses métodos, então, chamam o método DrawMapMode( ), que define o modo de mapeamento do formulário principal para aquele especificado por MappingMode. Depois, ele desenha algumas
formas e linhas usando valores constantes para especificar seus tamanhos. Quando diferentes modos de
mapeamento são usados para o desenho de formas, elas terão tamanhos diferentes no formulário, pois as
medidas utilizadas são usadas no contexto do modo de mapeamento especificado. As Figuras 8.14 e 8.15
ilustram a saída de DrawMapMode( ) para os modos de mapeamento MM_LOENGLISH e MM_LOMETRIC.
FIGURA 8.14
Saída de DrawMapMode( ) usando o modo de mapeamento MM_LOENGLISH.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
87
FIGURA 8.15
Saída de DrawMapMode( ) usando o modo de mapeamento MM_LOMETRIC.
O método mmiMM_ISOTROPICClick( ) ilustra o desenho com a tela do formulário no modo MM_ISOTROPIC.
Esse método primeiro define o modo de mapeamento e depois define o centro da área do cliente do formulário, permitindo que todos os quatro quadrantes do sistema de coordenadas sejam vistos.
Em seguida, o método desenha um retângulo em cada plano e uma elipse no centro da área do cliente. Observe como você pode usar os mesmos valores nos parâmetros passados para Canvas.Rectangle( ),
embora desenhando em diferentes áreas da tela. Isso é feito passando-se os valores negativos para o parâmetro X, parâmetro Y ou ambos os parâmetros passados a SetViewPortExt( ).
O método mmiMM_ANISOTROPICClick( ) realiza as mesmas operações, mas utiliza o modo MM_ANISOTROPIC.
A finalidade de mostrar ambos é para ilustrar a diferença de princípio entre os modos de mapeamento
MM_ISOTROPIC e MM_ANISOTROPIC.
Usando o modo MM_ISOTROPIC, o Win32 garante que os dois eixos usam o mesmo tamanho físico e faz
os ajustes necessários para que isso aconteça. O modo MM_ANISOTROPIC, no entanto, utiliza dimensões físicas
que podem não ser iguais. As Figuras 8.16 e 8.17 ilustram isso mais claramente. Você pode ver que o
modo MM_ISOTROPIC garante igualdade com os dois eixos, enquanto o mesmo código usando o modo
MM_ANISOTROPIC não garante igualdade. De fato, o modo MM_ISOTROPIC garante ainda mais que as coordenadas lógicas do quadrado serão mapeadas nas coordenadas de dispositivo de tal forma que a relação do
quadrado seja preservada, mesmo que o sistema de coordenadas do dispositivo não seja quadrado.
FIGURA 8.16
Saída do modo de mapeamento MM_ISOTROPIC.
88
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.17
Saída do modo de mapeamento MM_ANIISOTROPIC.
Criação de um programa de pintura
O programa principal mostrado aqui utiliza várias técnicas avançadas no trabalho com a GDI e imagens
gráficas. Você pode encontrar esse projeto no CD como DDGPaint.dpr. A Listagem 8.7 mostra o código-fonte para esse projeto.
Listagem 8.7 O programa de pintura DDGPaint
unit MainFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Buttons, ExtCtrls, ColorGrd, StdCtrls, Menus, ComCtrls;
const
crMove = 1;
type
TDrawType = (dtLineDraw, dtRectangle, dtEllipse, dtRoundRect,
dtClipRect, dtCrooked);
TMainForm = class(TForm)
sbxMain: TScrollBox;
imgDrawingPad: TImage;
pnlToolBar: TPanel;
sbLine: TSpeedButton;
sbRectangle: TSpeedButton;
sbEllipse: TSpeedButton;
sbRoundRect: TSpeedButton;
pnlColors: TPanel;
cgDrawingColors: TColorGrid;
pnlFgBgBorder: TPanel;
pnlFgBgInner: TPanel;
Bevel1: TBevel;
mmMain: TMainMenu;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
89
Listagem 8.7 Continuação
90
mmiFile: TMenuItem;
mmiExit: TMenuItem;
N2: TMenuItem;
mmiSaveAs: TMenuItem;
mmiSaveFile: TMenuItem;
mmiOpenFile: TMenuItem;
mmiNewFile: TMenuItem;
mmiEdit: TMenuItem;
mmiPaste: TMenuItem;
mmiCopy: TMenuItem;
mmiCut: TMenuItem;
sbRectSelect: TSpeedButton;
SaveDialog: TSaveDialog;
OpenDialog: TOpenDialog;
stbMain: TStatusBar;
pbPasteBox: TPaintBox;
sbFreeForm: TSpeedButton;
RgGrpFillOptions: TRadioGroup;
cbxBorder: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure sbLineClick(Sender: TObject);
procedure imgDrawingPadMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure imgDrawingPadMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure imgDrawingPadMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure cgDrawingColorsChange(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiSaveFileClick(Sender: TObject);
procedure mmiSaveAsClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure mmiNewFileClick(Sender: TObject);
procedure mmiOpenFileClick(Sender: TObject);
procedure mmiEditClick(Sender: TObject);
procedure mmiCutClick(Sender: TObject);
procedure mmiCopyClick(Sender: TObject);
procedure mmiPasteClick(Sender: TObject);
procedure pbPasteBoxMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure pbPasteBoxMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure pbPasteBoxMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure pbPasteBoxPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure RgGrpFillOptionsClick(Sender: TObject);
public
{ Declarações públicas }
MouseOrg: TPoint;
// Armazena informações do mouse
NextPoint: TPoint;
// Armazena informações do mouse
Drawing: Boolean;
// Flag de desenho sendo realizado
DrawType: TDrawType; // Contém informação de tipo de desenho: TDrawType
FillSelected,
// Preenche flag de formas
BorderSelected: Boolean; // Flag de desenho de formas sem borda
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
EraseClipRect: Boolean;
// Especifica se apaga ou não
// o retângulo de corte
Modified: Boolean;
// Flag de imagem modificada
FileName: String;
// Contém o nome do arquivo de imagem
OldClipViewHwnd: Hwnd; // Contém a janela da visão antiga do clipboard
{ Cola variáveis Image }
PBoxMoving: Boolean;
// Flag PasteBox está movendo
PBoxMouseOrg: TPoint; // Armazena variáveis do mouse para mover PasteBox
PasteBitMap: TBitmap; // Armazena imagem de bitmap dos dados colados
Pasted: Boolean;
// Flag de dados colados
LastDot: TPoint;
// Mantém a coordenada TPoint para realizar
// desenho de linha livre
procedure DrawToImage(TL, BR: TPoint; PenMode: TPenMode);
{ Este procedimento pinta a imagem especificada pelo campo
DrawType em imgDrawingPad }
procedure SetDrawingStyle;
{ Este procedimento define vários estilos de Pen/Brush com base nos valores
especificados pelos controles do formulário. A grade Panels and color é
usada para definir esses valores }
procedure CopyPasteBoxToImage;
{ Esse procedimento copia dos dados colados do clipboard do Windows
para o componente principal da imagem, imgDrawingPad }
procedure WMDrawClipBoard(var Msg: TWMDrawClipBoard);
message WM_DRAWCLIPBOARD;
{ Esse manipulador de mensagem captura as mensagens WM_DRAWCLIPBOARD
que são enviadas a todas as janelas que foram incluídas na cadeia
do visualizador do clipboard. Uma aplicação pode ser incluída na
cadeia do visualizador do clipboard usando a função SetClipBoardViewer( )
da API do Win32, como é feito em FormCreate( ) }
procedure CopyCut(Cut: Boolean);
{ Este método copia uma parte da imagem principal, imgDrawingPad,
para o clipboard do Windows. }
end;
var
MainForm: TMainForm;
implementation
uses ClipBrd, Math;
{$R *.DFM}
procedure TMainForm.FormCreate(Sender: TObject);
{ Este método define o campo do formulário para seus valores padrão.
Depois ele cria um bitmap para o imgDrawingPad. Essa é a imagem na
qual o desenho é feito. Finalmente, ele inclui esta aplicação como
parte da cadeia do visualizador de clipboard do Windows usando a
função SetClipBoardViewer( ). Isso permite que o formulário apanhe
mensagens WM_DRAWCLIPBOARD que são enviadas a todas as janelas na
cadeia do visualizador do clipboard sempre que os dados do clipboard
são modificados. }
begin
Screen.Cursors[crMove] := LoadCursor(hInstance, ‘MOVE’);
FillSelected
:= False;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
91
Listagem 8.7 Continuação
BorderSelected := True;
Modified := False;
FileName := ‘’;
Pasted := False;
pbPasteBox.Enabled := False;
// Cria um bitmap para imgDrawingPad e define seus limites
with imgDrawingPad do
begin
SetBounds(0, 0, 600, 400);
Picture.Graphic := TBitMap.Create;
Picture.Graphic.Width := 600;
Picture.Graphic.Height := 400;
end;
// Agora cira uma imagem do bitmap para conter os dados colados
PasteBitmap := TBitmap.Create;
pbPasteBox.BringToFront;
{ Inclui o formulário na cadeia do visualizador do clipboard. Salva a
alça da próxima janela na cadeia, para que possa ser restaurada
pela função ChangeClipboardChange( ) da API do WIn32 no método
FormDestroy( ) deste formulário. }
OldClipViewHwnd := SetClipBoardViewer(Handle);
end;
procedure TMainForm.WMDrawClipBoard(var Msg: TWMDrawClipBoard);
begin
{ Este método será chamado sempre que os dados do clipboard forem
alterados. Como o formulário principal foi incluído na cadeia do
visualizador do clipboard, ele receberá a mensagem WM_DRAWCLIPBOARD
indicando que os dados do clipboard foram alterados. }
inherited;
{ Certifica-se de que os dados contidos no clipboard são realmente dados
de bitmap. }
if ClipBoard.HasFormat(CF_BITMAP) then
mmiPaste.Enabled := True
else
mmiPaste.Enabled := False;
Msg.Result := 0;
end;
procedure TMainForm.DrawToImage(TL, BR: TPoint; PenMode: TPenMode);
{ Este método realiza a operação de desenho especificada. A operação
de desenho é especificada pelo campo DrawType. }
begin
with imgDrawingPad.Canvas do
begin
Pen.Mode := PenMode;
92
case DrawType of
dtLineDraw:
begin
MoveTo(TL.X, TL.Y);
LineTo(BR.X, BR.Y);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
end;
dtRectangle:
Rectangle(TL.X, TL.Y, BR.X, BR.Y);
dtEllipse:
Ellipse(TL.X, TL.Y, BR.X, BR.Y);
dtRoundRect:
RoundRect(TL.X, TL.Y, BR.X, BR.Y,
(TL.X - BR.X) div 2, (TL.Y - BR.Y) div 2);
dtClipRect:
Rectangle(TL.X, TL.Y, BR.X, BR.Y);
end;
end;
end;
procedure TMainForm.CopyPasteBoxToImage;
{ Este método copia a imagem colada do clipboard do Windows para
imgDrawingPad. Primeiro ele apaga qualquer retângulo delimitador
desenhado pelo componente PaintBox, pbPasteBox. Depois copia os
dados de pbPasteBox para imgDrawingPad, no local em que pbPasteBox foi
arrastado sobre imgDrawingPad. O motivo para não copiarmos o
conteúdo da tela de desenho de pbPasteBox e usarmos a tela de
PasteBitmap em vez disso é que, quando uma parte de pbPasteBox é
arrastada para fora da área visível, o Windows não pinta a parte de
pbPasteBox não visível. Portanto, isso é necessário para o bitmap
colado a partir do bitmap fora da tela. }
var
SrcRect, DestRect: TRect;
begin
// Primeiro, apaga o retângulo desenhado por pbPasteBox
with pbPasteBox do
begin
Canvas.Pen.Mode := pmNotXOR;
Canvas.Pen.Style := psDot;
Canvas.Brush.Style := bsClear;
Canvas.Rectangle(0, 0, Width, Height);
DestRect := Rect(Left, Top, Left+Width, Top+Height);
SrcRect := Rect(0, 0, Width, Height);
end;
{ Aqui, temos de usar PasteBitmap no lugar de pbPasteBox porque
pbPasteBox recortará qualquer coisa do lado de fora da área visível. }
imgDrawingPad.Canvas.CopyRect(DestRect, PasteBitmap.Canvas, SrcRect);
pbPasteBox.Visible := false;
pbPasteBox.Enabled := false;
Pasted := False; // Pasting operation is complete
end;
procedure TMainForm.imgDrawingPadMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Modified := True;
// Apaga o retângulo de corte, se houver um desenhado
if (DrawType = dtClipRect) and EraseClipRect then
DrawToImage(MouseOrg, NextPoint, pmNotXOR)
else if (DrawType = dtClipRect) then
EraseClipRect := True; // Ativa o apagamento do retângulo de corte
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
93
Listagem 8.7 Continuação
{ Se um bitmap foi colado no clipboard, copia-o para a imagem e remove
o PaintBox. }
if Pasted then
CopyPasteBoxToImage;
Drawing := True;
// Salva as informações do mouse
MouseOrg := Point(X, Y);
NextPoint := MouseOrg;
LastDot := NextPoint;
// Lastdot é atualizado enquanto o mouse se move
imgDrawingPad.Canvas.MoveTo(X, Y);
end;
procedure TMainForm.imgDrawingPadMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
{ Este método determina a operação de desenho a ser realizada e realiza
o desenho de linha em forma livre ou chama o método DrawToImage,
que desenha a forma especificada. }
begin
if Drawing then
begin
if DrawType = dtCrooked then
begin
imgDrawingPad.Canvas.MoveTo(LastDot.X, LastDot.Y);
imgDrawingPad.Canvas.LineTo(X, Y);
LastDot := Point(X,Y);
end
else begin
DrawToImage(MouseOrg, NextPoint, pmNotXor);
NextPoint := Point(X, Y);
DrawToImage(MouseOrg, NextPoint, pmNotXor)
end;
end;
// Atualiza a barra de status com o local atual do mouse
stbMain.Panels[1].Text := Format(‘X: %d, Y: %D’, [X, Y]);
end;
procedure TMainForm.imgDrawingPadMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
{ Impede que o retângulo de corte destrua as imagens que já estão na
imagem. }
if not (DrawType = dtClipRect) then
DrawToImage(MouseOrg, Point(X, Y), pmCopy);
Drawing := False;
end;
94
procedure TMainForm.sbLineClick(Sender: TObject);
begin
// Primeiro apaga o retângulo de corte do tipo de desenho atual
if DrawType = dtClipRect then
DrawToImage(MouseOrg, NextPoint, pmNotXOR);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
{ Agora define o campo DrawType para aquele especificado pelo
TSpeedButton chamando esse método. Os valores de Tag de
TSpeedButton correspondem a um valor específico de TDrawType,
que é o motivo para o typecast a seguir atribuir com sucesso um
um valor de TDrawType válido para o campo DrawType. }
if Sender is TSpeedButton then
DrawType := TDrawType(TSpeedButton(Sender).Tag);
// Agora cuida para que o estilo dtClipRect não apague desenhos anteriores
if DrawType = dtClipRect then begin
EraseClipRect := False;
end;
// Define o estilo do desenho
SetDrawingStyle;
end;
procedure TMainForm.cgDrawingColorsChange(Sender: TObject);
{ Este método desenha o retângulo representando cores de preenchimento
e borda para indicar a seleção do usuário para as duas cores.
pnlFgBgInner e pnlFgBgBorder são TPanels organizados um sobre o
outro para gerar o efeito desejado. }
begin
pnlFgBgBorder.Color := cgDrawingColors.ForeGroundColor;
pnlFgBgInner.Color := cgDrawingColors.BackGroundColor;
SetDrawingStyle;
end;
procedure TMainForm.SetDrawingStyle;
{ Este método define os vários estilos de desenho com base nas seleções
no TPanel pnlFillStyle para os estilos de preenchimento e borda. }
begin
with imgDrawingPad do
begin
if DrawType = dtClipRect then
begin
Canvas.Pen.Style := psDot;
Canvas.Brush.Style := bsClear;
Canvas.Pen.Color := clBlack;
end
else if FillSelected then
Canvas.Brush.Style := bsSolid
else
Canvas.Brush.Style := bsClear;
if BorderSelected then
Canvas.Pen.Style := psSolid
else
Canvas.Pen.Style := psClear;
if FillSelected and (DrawType < > dtClipRect) then
Canvas.Brush.Color := pnlFgBgInner.Color;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
95
Listagem 8.7 Continuação
if DrawType < > dtClipRect then
Canvas.Pen.Color := pnlFgBgBorder.Color;
end;
end;
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close; // Termina a aplicação
end;
procedure TMainForm.mmiSaveFileClick(Sender: TObject);
{ Este método salva a imagem no arquivo especificado por FileName. Se
FileName estiver em branco, SaveAs1Click é chamado para apanhar um
nome de arquivo. }
begin
if FileName = ‘’ then
mmiSaveAsClick(nil)
else begin
imgDrawingPad.Picture.SaveToFile(FileName);
stbMain.Panels[0].Text := FileName;
Modified := False;
end;
end;
procedure TMainForm.mmiSaveAsClick(Sender: TObject);
{ Este método inicia SaveDialog para apanhar um nome de arquivo no qual
o conteúdo da imagem será salvo. }
begin
if SaveDialog.Execute then
begin
FileName := SaveDialog.FileName; // Store the filename
mmiSaveFileClick(nil)
end;
end;
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
{ Se o usuário tentar fechar o formulário antes de salvar a imagem, ele
é solicitado a fazer isso neste método. }
var
Rslt: Word;
begin
CanClose := False; // Assume fail.
if Modified then begin
Rslt := MessageDlg(‘File has changed, save?’, mtConfirmation, mbYesNOCancel, 0);
case Rslt of
mrYes: mmiSaveFileClick(nil);
mrNo: ; // não precisa fazer nada.
mrCancel: Exit;
end
end;
CanClose := True;
// Permite que o usuário feche a aplicação
end;
96
procedure TMainForm.mmiNewFileClick(Sender: TObject);
{ Este método apaga qualquer desenho na imagem principal depois de pedir
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
ao usuário para salvá-lo em um arquivo, quando o manipulador de
evento mmiSaveFileClick será chamado. }
var
Rslt: Word;
begin
if Modified then begin
Rslt := MessageDlg(‘File has changed, save?’, mtConfirmation, mbYesNOCancel, 0);
case Rslt of
mrYes: mmiSaveFileClick(nil);
mrNo: ; // não precisa fazer nada.
mrCancel: Exit;
end
end;
with imgDrawingPad.Canvas do begin
Brush.Style := bsSolid;
Brush.Color := clWhite; // clWhite apaga a imagem
FillRect(ClipRect);
// Apaga a imagem
FileName := ‘’;
stbMain.Panels[0].Text := FileName;
end;
SetDrawingStyle;
// Restaura o estilo de desenho anterior
Modified := False;
end;
procedure TMainForm.mmiOpenFileClick(Sender: TObject);
{ Este método abre um arquivo de bitmap especificado por OpenDialog.FileName.
Se um arquivo já tiver sido criado, o usuário é solicitado a salvar o
arquivo, quando o evento mmiSaveFileClick será chamado. }
var
Rslt: Word;
begin
if OpenDialog.Execute then
begin
if Modified then begin
Rslt := MessageDlg(‘File has changed, save?’, mtConfirmation, mbYesNOCancel, 0);
case Rslt of
mrYes: mmiSaveFileClick(nil);
mrNo: ; // não precisa fazer nada.
mrCancel: Exit;
end
end;
imgDrawingPad.Picture.LoadFromFile(OpenDialog.FileName);
FileName := OpenDialog.FileName;
stbMain.Panels[0].Text := FileName;
Modified := false;
end;
end;
97
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
procedure TMainForm.mmiEditClick(Sender: TObject);
{ O timer é usado para determinar se uma área da imagem principal está
cercada por um retângulo delimitador. Se estiver, então os itens do
menu Copy e Cut são ativados. Caso contrário, eles são desativados. }
var
IsRect: Boolean;
begin
IsRect := (MouseOrg.X < > NextPoint.X) and (MouseOrg.Y < > NextPoint.Y);
if (DrawType = dtClipRect) and IsRect then
begin
mmiCut.Enabled := True;
mmiCopy.Enabled := True;
end
else begin
mmiCut.Enabled := False;
mmiCopy.Enabled := False;
end;
end;
98
procedure TMainForm.CopyCut(Cut: Boolean);
{ Este método copia uma parte da imagem principal para o clipboard. A parte
copiada é especificada por um retângulo delimitador na imagem principal.
Se Cut for verdadeiro, a área no retângulo delimitador será apagada. }
var
CopyBitMap: TBitmap;
DestRect, SrcRect: TRect;
OldBrushColor: TColor;
begin
CopyBitMap := TBitMap.Create;
try
{ Define o tamanho de CopyBitmap com base nas coordenadas do
retângulo delimitador }
CopyBitMap.Width := Abs(NextPoint.X - MouseOrg.X);
CopyBitMap.Height := Abs(NextPoint.Y - MouseOrg.Y);
DestRect := Rect(0, 0, CopyBitMap.Width, CopyBitmap.Height);
SrcRect := Rect(Min(MouseOrg.X, NextPoint.X)+1,
Min(MouseOrg.Y, NextPoint.Y)+1,
Max(MouseOrg.X, NextPoint.X)-1,
Max(MouseOrg.Y, NextPoint.Y)-1);
{ Copia a parte da imagem principal cercada pelo retângulo delimitador
para o clipboard do Windows. }
CopyBitMap.Canvas.CopyRect(DestRect, imgDrawingPad.Canvas, SrcRect);
{ Versões anteriores do Delphi exigiam que a propriedade Handle
do bitmap fosse indicada para que o bitmap estivesse
disponível. Isso era devido ao modo como o Delphi apanhava
as imagens mapeadas em bits. A etapa a seguir pode não ser
necessária. }
CopyBitMap.Handle;
// Atribui a imagem ao clipboard.
ClipBoard.Assign(CopyBitMap);
{ Se o recorte foi especificado, então apaga a parte da imagem
principal cercada pelo relatório delimitador. }
if Cut then
with imgDrawingPad.Canvas do
begin
OldBrushColor := Brush.Color;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
Brush.Color := clWhite;
try
FillRect(SrcRect);
finally
Brush.Color := OldBrushColor;
end;
end;
finally
CopyBitMap.Free;
end;
end;
procedure TMainForm.mmiCutClick(Sender: TObject);
begin
CopyCut(True);
end;
procedure TMainForm.mmiCopyClick(Sender: TObject);
begin
CopyCut(False);
end;
procedure TMainForm.mmiPasteClick(Sender: TObject);
{ Este método cola os dados contidos no clipboard no bitmap da colagem.
O motivo para ser colado em PasteBitmap, um bitmap fora da tela, é
para que o usuário possa reposicionar a imagem colada para qualquer
lugar da imagem principal. Isso é realizado fazendo o pbPasteBox, um
componente de TPaintBox, desenhar o conteúdo de PasteImage. Quando o
usuário termina de posicionar o pbPasteBox, o conteúdo de TPasteBitmap
é desenhado em imgDrawingPad, no local especificado pelo local
de pbPasteBox.}
begin
{ Apaga o retângulo delimitador }
pbPasteBox.Enabled := True;
if DrawType = dtClipRect then
begin
DrawToImage(MouseOrg, NextPoint, pmNotXOR);
EraseClipRect := False;
end;
PasteBitmap.Assign(ClipBoard);
// Apanha os dados do clipboard
Pasted := True;
// Define a posição da imagem colada para superior esquerda
pbPasteBox.Left := 0;
pbPasteBox.Top := 0;
// Define o tamanho de pbPasteBox para corresponder ao tamanho de PasteBitmap
pbPasteBox.Width := PasteBitmap.Width;
pbPasteBox.Height := PasteBitmap.Height;
pbPasteBox.Visible := True;
pbPasteBox.Invalidate;
end;
procedure TMainForm.pbPasteBoxMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
99
Listagem 8.7 Continuação
{ Este método configura pbPasteBox, um TPaintBox para ser movido pelo
usuário enquanto o botão esquerdo do mouse estiver pressionado. }
begin
if Button = mbLeft then
begin
PBoxMoving := True;
Screen.Cursor := crMove;
PBoxMouseOrg := Point(X, Y);
end
else
PBoxMoving := False;
end;
procedure TMainForm.pbPasteBoxMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
{ Este método move pbPasteBox se o flag pBoxMoving for verdadeiro,
indicando que o usuário está segurando o botão esquerdo do mouse e
está arrastando o PaintBox. }
begin
if PBoxMoving then
begin
pbPasteBox.Left := pbPasteBox.Left + (X - PBoxMouseOrg.X);
pbPasteBox.Top := pbPasteBox.Top + (Y - PBoxMouseOrg.Y);
end;
end;
procedure TMainForm.pbPasteBoxMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
{ Este método desativa o movimento de pbPasteBox quando o usuário
suspende o botão esquerdo do mouse. }
if PBoxMoving then
begin
PBoxMoving := False;
Screen.Cursor := crDefault;
end;
pbPasteBox.Refresh; // Redesenha o pbPasteBox.
end;
procedure TMainForm.pbPasteBoxPaint(Sender: TObject);
{ O paintbox é desenhado sempre que o usuário seleciona a opção Paste no
menu. pbPasteBox desenha o conteúdo de PasteBitmap, que contém a
imagem apanhada do clipboard. O motivo para desenhar o conteúdo de
PasteBitmap em pbPasteBox, uma classe de TPaintBox, é para que o
usuário também possa movimentar o objeto sobre a imagem principal.
Em outras palavras, pbPasteBox pode ser movido e ocultado, quando
for preciso. }
var
DestRect, SrcRect: TRect;
begin
// Só apresenta o paintbox se tiver ocorrido uma operação de colagem.
if Pasted then
begin
{ Primeiro pinta o conteúdo de PasteBitmap usando o CopyRect da tela
de desenho, mas somente se o paintbox não estiver sendo movido. Isso
reduz as piscadas na tela. }
if not PBoxMoving then
100
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.7 Continuação
begin
DestRect := Rect(0, 0, pbPasteBox.Width, pbPasteBox.Height);
SrcRect := Rect(0, 0, PasteBitmap.Width, PasteBitmap.Height);
pbPasteBox.Canvas.CopyRect(DestRect, PasteBitmap.Canvas, SrcRect);
end;
{ Agora copia um retângulo delimitador para indicar que o pbPasteBox
é um objeto que pode ser movido. Usamos um modo de caneta
pmNotXOR, pois precisamos apagar esse retângulo quando o usuário
copiar o conteúdo do PaintBox na imagem principal, e temos que
preservar o conteúdo original. }
pbPasteBox.Canvas.Pen.Mode := pmNotXOR;
pbPasteBox.Canvas.Pen.Style := psDot;
pbPasteBox.Canvas.Brush.Style := bsClear;
pbPasteBox.Canvas.Rectangle(0, 0, pbPasteBox.Width, pbPasteBox.Height);
end;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// Remove o formulário da cadeia do clipboard
ChangeClipBoardChain(Handle, OldClipViewHwnd);
PasteBitmap.Free; // Libera a instância de PasteBitmap
end;
procedure TMainForm.RgGrpFillOptionsClick(Sender: TObject);
begin
FillSelected
:= RgGrpFillOptions.ItemIndex = 0;
BorderSelected := cbxBorder.Checked;
SetDrawingStyle;
end;
end.
Como funciona o programa de pintura
O programa de pintura tem realmente um código grande. Como seria difícil explicar como ele funciona
fora do código, incluímos bastante comentário dentro do próprio código. Explicaremos apenas a funcionalidade geral do programa de pintura. O formulário principal aparece na Figura 8.18.
FIGURA 8.18
O formulário principal para o programa de pintura.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
101
NOTA
Observe que um componente TImage é usado como superfície de desenho para o programa de pintura. Tenha em mente que isso só pode acontecer se a imagem usar um objeto TBitmap.
O formulário principal contém um componente de imagem principal, imgDrawingPad, que é colocado
em um componente TScrollBox. imgDrawingPad é o local em que o usuário realiza o desenho. O botão selecionado na barra de ferramentas do formulário especifica o tipo de desenho que o usuário realiza.
O usuário pode desenhar linhas, retângulos, elipses e retângulos arredondados. além de realizar desenho em forma livre. Além disso, uma parte da imagem principal pode ser selecionada e copiada para o
Clipboard do Windows, de modo que possa ser colada para outra aplicação que trate de dados de bitmap. Da mesma forma, o programa de pintura pode aceitar dados de bitmap do Clipboard do Windows.
Técnicas de TPanel
O estilo de preenchimento e o tipo de borda são especificados pelo grupo de botões de opção Fill Options (opções de preenchimento). As cores de preenchimento e borda são definidas usando a grade de cores no ColorPanel mostrado na Figura 8.18.
Colagem de dados de bitmap do Clipboard
Para colar os dados do Clipboard, você utiliza um bitmap fora da tela, PasteBitMap, para conter os dados
colados. Um componente de TPaintBox, pbPasteBox, desenha então os dados de PasteBitMap. O motivo para
usar um componente TPaintBox para desenhar o conteúdo de PasteBitMap é para que o usuário possa mover
o pbPasteBox para qualquer local na imagem principal para designar onde os dados colados devem ser copiados para a imagem principal.
Anexando à cadeia do visualizador do Clipboard do Win32
Outra técnica mostrada pelo programa de pintura é como uma aplicação pode se anexar à cadeia do visualizador do Clipboard do Win32. Isso é feito no método FormCreate( ) pela chamada à função SetClipboardViewer( ) da API do Win32. Essa função apanha a alça da janela que se conecta à cadeia e retorna a alça
para a próxima janela na cadeia. O valor de retorno deve ser armazenado de modo que, quando a aplicação for encerrada, ela possa restaurar o estado anterior da cadeia usando ChangeClipboardChain( ), que
apanha a alça sendo removida e a alça salva. O programa de pintura restaura a cadeia no método FormDestroy( ) do formulário. Quando uma aplicação é anexada à cadeia do visualizador do Clipboard, ela recebe as mensagens WM_DRAWCLIPBOARD sempre que os dados do Clipboard são modificados. Você aproveita isso
capturando essa mensagem e ativando o item de menu Paste se os dados alterados no Clipboard forem
dados de bitmap. Isso é feito no método WMDrawClipBoard( ).
Cópia de bitmaps
As operações de cópia do bitmap são realizadas nos métodos CopyCut( ), pbPaintBoxPaint( ) e CopyPasteBoxToImage( ). O método CopyCut( ) copia para o Clipboard uma parte da imagem principal selecionada por
um retângulo delimitador e depois apaga a área delimitada se o parâmetro Cut passado para ela for True.
Caso contrário, ele deixa a área intacta.
PbPasteBoxPaint( ) copia o conteúdo do bitmap fora da tela para pbPasteBox.Canvas, mas somente
quando pbPasteBox não estiver sendo movido. Isso ajuda a reduzir as piscadas enquanto o usuário move
pbPasteBox.
CopyPasteBoxToImage( ) copia o conteúdo do bitmap fora da tela para a imagem principal imgDrawingPad
no local especificado por pbPasteBox.
102
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Comentários do programa de pintura
Como já dissemos, grande parte da funcionalidade do programa de pintura está documentada no comentário do código-fonte. Uma ótima idéia é ler o código com seus comentários, percorrendo-o por inteiro
para entender melhor o que acontece no programa.
Animação com programação gráfica
Esta seção demonstra como você pode conseguir uma animação simples de sprites (imagens animadas)
misturando as classes do Delphi 5 com as funções da GDI do Win32. O projeto de animação reside no
CD que acompanha este livro, com o nome Animate.dpr. A Listagem 8.8 mostra a unidade que contém a
funcionalidade do formulário principal.
Listagem 8.8 O formulário principal do projeto de animação
unit MainFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Menus, Stdctrls, AppEvnts;
{$R SPRITES.RES } // Link in the bitmaps
type
TSprite = class
private
FWidth: integer;
FHeight: integer;
FLeft: integer;
FTop: integer;
FAndImage, FOrImage: TBitMap;
public
property Top: Integer read FTop write FTop;
property Left: Integer read FLeft write FLeft;
property Width: Integer read FWidth write FWidth;
property Height: Integer read FHeight write FHeight;
constructor Create;
destructor Destroy; override;
end;
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiFile: TMenuItem;
mmiSlower: TMenuItem;
mmiFaster: TMenuItem;
N1: TMenuItem;
mmiExit: TMenuItem;
appevMain: TApplicationEvents;
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormDestroy(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
103
Listagem 8.8 Continuação
procedure mmiExitClick(Sender: TObject);
procedure mmiSlowerClick(Sender: TObject);
procedure mmiFasterClick(Sender: TObject);
procedure appevMainIdle(Sender: TObject; var Done: Boolean);
private
BackGnd1, BackGnd2: TBitMap;
Sprite: TSprite;
GoLeft,GoRight,GoUp,GoDown: boolean;
FSpeed, FSpeedIndicator: Integer;
procedure DrawSprite;
end;
const
BackGround = ‘BACK2.BMP’;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
constructor TSprite.Create;
begin
inherited Create;
{ Cria os bitmaps para conter as imagens de sprite que serão usadas para
realizar a operação AND/OR para criar animação. }
FAndImage := TBitMap.Create;
FAndImage.LoadFromResourceName(hInstance, ‘AND’);
FOrImage := TBitMap.Create;
FOrImage.LoadFromResourceName(hInstance, ‘OR’);
Left := 0;
Top := 0;
Height := FAndImage.Height;
Width := FAndImage.Width;
end;
destructor TSprite.Destroy;
begin
FAndImage.Free;
FOrImage.Free;
end;
104
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Cria a imagem de fundo original
BackGnd1 := TBitMap.Create;
with BackGnd1 do
begin
LoadFromResourceName(hInstance, ‘BACK’);
Parent := nil;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.8 Continuação
SetBounds(0, 0,
end;
Width, Height);
// Cria uma cópia da imagem do fundo
BackGnd2 := TBitMap.Create;
BackGnd2.Assign(BackGnd1);
// Cria uma imagem do sprite
Sprite := TSprite.Create;
// Inicializa as variáveis de direção
GoRight := true;
GoDown := true;
GoLeft := false;
GoUp := false;
FSpeed := 0;
FSpeedIndicator := 0;
{ Define o evento OnIdle da aplicação para MyIdleEvent, que iniciará o
movimento do sprite. }
// Ajusta a largura/altura do cliente do formulário
ClientWidth := BackGnd1.Width;
ClientHeight := BackGnd1.Height;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// Libera todos os objetos criados no construtor Create do formulário
BackGnd1.Free;
BackGnd2.Free;
Sprite.Free;
end;
procedure TMainForm.DrawSprite;
var
OldBounds: TRect;
begin
// Salva os limites do sprite em OldBounds
with OldBounds do
begin
Left := Sprite.Left;
Top := Sprite.Top;
Right := Sprite.Width;
Bottom := Sprite.Height;
end;
{ Agora muda os limites do sprite para que ele se mova em uma direção
ou mude de direção quando entrar em contato com os limites do
formulário. }
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
105
Listagem 8.8 Continuação
with Sprite do
begin
if GoLeft then
if Left > 0 then
Left := Left - 1
else begin
GoLeft := false;
GoRight := true;
end;
if GoDown then
if (Top + Height) < self.ClientHeight then
Top := Top + 1
else begin
GoDown := false;
GoUp := true;
end;
if GoUp then
if Top > 0 then
Top := Top - 1
else begin
GoUp := false;
GoDown := true;
end;
if GoRight then
if (Left + Width) < self.ClientWidth then
Left := Left + 1
else begin
GoRight := false;
GoLeft := true;
end;
end;
{ Apaga o desenho original do sprite em BackGnd2 copiando um retângulo
a partir de BackGnd1. }
with OldBounds do
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Right, Bottom,
BackGnd1.Canvas.Handle, Left, Top, SrcCopy);
{ Agora desenha o sprite no bitmap fora da tela. Realizando o desenho
em um bitmap fora da tela, eliminam-se as piscadas na tela. }
with Sprite do
begin
{ Agora cria um furo preto no qual o sprite existia inicialmente, através
da operação And de FAndImage com BackGnd2. }
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Width, Height,
FAndImage.Canvas.Handle, 0, 0, SrcAnd);
// Agora preenche o furo preto com as cores originais do sprite
BitBlt(BackGnd2.Canvas.Handle, Left, Top, Width, Height,
FOrImage.Canvas.Handle, 0, 0, SrcPaint);
end;
106
{ Copia o sprite em seu novo local na tela de desenho do formulário.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.8 Continuação
Um retângulo ligeiramente maior do que o sprite é necessário para
apagar efetivamente o sprite, sobrescrevendo-o, e desenhar o novo
sprite no novo local com uma única chamada a BitBlt. }
with OldBounds do
BitBlt(Canvas.Handle, Left - 2, Top - 2, Right + 2, Bottom + 2,
BackGnd2.Canvas.Handle, Left - 2, Top - 2, SrcCopy);
end;
procedure TMainForm.FormPaint(Sender: TObject);
begin
// Desenha a imagem do fundo sempre que o formulário for pintado
BitBlt(Canvas.Handle, 0, 0, ClientWidth, ClientHeight,
BackGnd1.Canvas.Handle, 0, 0, SrcCopy);
end;
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.mmiSlowerClick(Sender: TObject);
begin
Inc(FSpeedIndicator, 100);
end;
procedure TMainForm.mmiFasterClick(Sender: TObject);
begin
if FSpeedIndicator >= 100 then
Dec(FSpeedIndicator, 100)
else
FSpeedIndicator := 0;
end;
procedure TMainForm.appevMainIdle(Sender: TObject; var Done: Boolean);
begin
if FSpeed >= FSpeedIndicator then
begin
DrawSprite;
FSpeed := 0;
end
else
inc(FSpeed);
Done := False;
end;
end.
O projeto de animação consiste em uma imagem de fundo em que um sprite, um disco voador, é desenhado e movimentado acima da área do cliente no fundo. O fundo é representado por um bitmap contendo estrelas dispersas (ver Figura 8.19).
107
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.19
O fundo para o projeto de animação.
O sprite é composto de dois bitmaps de 64x32. Veja mais sobre esses bitmaps um pouco adiante;
por enquanto, vamos discutir sobre o que acontece no código-fonte.
A unidade define uma classe TSprite. TSprite contém os campos que mantêm o local do sprite na
imagem do fundo e dois objetos TBitmap para contar cada um dos bitmaps de sprite. O construtor TSprite.Create tanto cria instâncias de TBitmap quanto as carrega com os bitmaps reais. Ambos, os bitmaps de
sprite e o bitmap do fundo, são mantidos em um arquivo de recursos que você vincula ao projeto incluindo a seguinte instrução na unidade principal:
{$Relatório SPRITES.RES }
Depois que o bitmap estiver carregado, os limites do sprite são definidos. O destruidor TSprite.Done
libera as duas instâncias de bitmap.
O formulário principal contém dois objetos TBitmap, um objeto TSprite e indicadores de direção para
especificar a direção do movimento do sprite. Além disso, o formulário principal define outro método,
DrawSprite( ), que possui funcionalidade para desenho de sprites. O componente TApplicationEvents é um
novo componente do Delphi 5, que permite conectar aos eventos em nível de Application. Antes do Delphi 5, você tinha que fazer isso incluindo eventos em nível de Application em runtime. Agora, com esse
componente, você pode fazer todo o gerenciamento de eventos para TApplication durante o projeto. Usaremos esse componente para fornecer um evento OnIdle para o objeto TApplication.
Observe que duas variáveis privadas são usadas para controlar a velocidade da animação: FSpeed e
FSpeedIndicator. Estas são usadas no método DrawSprite( ) para reduzir a velocidade da animação em máquinas mais velozes.
O manipulador do evento FormCreate( ) cria as duas instâncias de TBitmap e carrega cada uma com o
mesmo bitmap no fundo. (O motivo para usar dois bitmaps será discutido mais adiante.) Depois ele cria
uma instância de TSprite e define os indicadores de direção. Finalmente, FormCreate( ) redimensiona o
formulário para o tamanho da imagem no fundo.
O método FormPaint( ) pinta o BackGnd1 em sua tela, e FormDestroy( ) libera as instâncias de TBitmap e
TSprite.
O método appevMainIdle( ) chama DrawSprite( ), que move e desenha o sprite no fundo. O trabalho
principal é realizado no método DrawSprite( ). appevMainIdle( ) será chamado sempre que a aplicação estiver em um estado ocioso. Ou seja, sempre que não houver ações do usuário para a aplicação responder.
O método DrawSprite( ) reposiciona o sprite na imagem do fundo. É necessário haver uma série de
etapas para apagar o sprite antigo no fundo e depois desenhá-lo em seu novo local, preservando as cores
do fundo em torno da imagem do sprite real. Além do mais, DrawSprite( ) precisa realizar essas etapas sem
108 produzir piscadas enquanto o sprite está se movendo.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Para conseguir isso, o desenho é feito no bitmap fora da tela, BackGnd2. BackGnd2 e BackGnd1 são cópias
exatas da imagem no fundo. No entanto, BackGnd1 nunca é modificado, de modo que é sempre uma cópia
limpa do fundo. Quando o desenho estiver completo em BackGnd2, a área modificada de BackGnd2 é copiada
para a tela do formulário. Isso permite apenas uma operação BitBlt( ) na tela do formulário, tanto para
apagar quanto para desenhar o sprite em seu novo local. As operações de desenho realizadas sobre
BackGnd2 são as seguintes.
Primeiro, uma região retangular é copiada de BackGnd1 para BackGnd2 sobre a área ocupada pelo sprite. Isso efetivamente apaga o sprite de BackGnd2. Depois o bitmap FAndImage é copiado para BackGnd2 em seu
novo local usando a operação de bit AND. Isso efetivamente cria um furo preto em BackGnd2 onde existe o
sprite e ainda preserva as cores de BackGnd2 ao redor do sprite. A Figura 8.20 mostra FAndImage.
Na Figura 8.20, o sprite é representado por pixels pretos, e o restante da imagem ao redor dele consiste em pixels brancos. A cor preta possui um valor 0, e a cor branca possui o valor 1. As Tabelas 8.5 e 8.6
mostram os resultados da operação AND com cores pretas e brancas.
Tabela 8.5 Operação AND com cor preta
Fundo
Valor
Cor
BackGnd2
1001
Alguma cor
FAndImage
0000
Preta
Resultado
0000
Preta
Tabela 8.6 Operação AND com cor branca
Fundo
Valor
Cor
BackGnd2
1001
Alguma cor
FAndImage
1111
Branca
Resultado
1001
Alguma cor
F I G U R A 8 . 2 0 FAndImage
para um sprite.
109
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Essas tabelas mostram como a operação AND resulta em escurecer a área na qual existe o sprite em
BackGnd2. Na Tabela 8.5, Value representa uma cor de pixel. Se um pixel em BackGnd2 tiver alguma cor arbitrária, a combinação dessa com cor a cor preta usando o operador AND resulta na mudança desse pixel
para preto. A combinação dessa cor com a cor branca usando o operador AND resulta na mesma cor arbitrária, como mostra a Tabela 8.6. Como a cor ao redor do sprite em FAndImage é branca, os pixels em
BackGnd2 onde essa parte de FAndImage é copiada mantêm suas cores.
Depois de copiar FAndImage para BackGnd2 \, FOrImage precisa ser copiado para o mesmo local em
BackGnd2 para preencher o furo preto criado por FAndImage com as cores reais do sprite. FOrImage também
possui um retângulo ao redor da imagem real do sprite. Novamente, você precisa apanhar as cores do
sprite em BackGnd2 enquanto preserva as cores de BackGnd2 ao redor do sprite. Isso é feito combinando-se
FOrImage com BackGnd2 por meio da operação OR. FOrImage aparece na Figura 8.21.
F I G U R A 8 . 2 1 FOrImage.
Observe que a área ao redor da imagem do sprite é preta. A Tabela 8.7 mostra os resultados de realizar a operação OR sobre FOrImage e BackGnd2.
Tabela 8.7 Uma operação OR com a cor preta
Fundo
Valor
Cor
BackGnd2
1001
Alguma cor
FOrImage
0000
Preta
Resultado
1001
Alguma cor
A Tabela 8.7 mostra que se BackGnd2 tiver uma cor arbitrária, o uso da operação OR para combiná-la
com preto resultará na cor de BackGnd2.
Lembre-se de que esse desenho é realizado no bitmap fora da tela. Quando o desenho estiver completo, um único BitBlt( ) será feito na tela do formulário para apagar e copiar o sprite.
A técnica que mostramos nesta seção é um método muito comum para realizar animação. Você
poderia estender a funcionalidade da classe TSprite para, por conta própria, mover e desenhar em uma
tela pai.
110
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Fontes avançadas
Embora a VCL permita manipular fontes com relativa facilidade, ela não oferece os inúmeros recursos
de apresentação de fonte, fornecidos pela API do Win32. Esta seção oferece uma base sobre fontes do
Win32 e mostra como manipulá-las.
Tipos de fontes do Win32
Existem basicamente dois tipos de fontes no Win32: fontes da GDI e fontes de dispositivo. As fontes da
GDI são armazenadas nos arquivos de recurso de fonte e possuem a extensão font (de fontes de rastreio e
de vetor) ou tot e ttf(para fontes TrueType). As fontes de dispositivo são específicas a um determinado
dispositivo, como uma impressora. Ao contrário das fontes da GDI, quando o Win32 utiliza uma fonte
de dispositivo para imprimir texto, ele só precisa enviar o caracter ASCII para o dispositivo, e o dispositivo se encarrega de apresentar o caractere na fonte especificada. Caso contrário, o Win32 converte a fonte para um bitmap ou realiza a função da GDI para desenhar a fonte. O desenho da fonte usando bitmaps
ou funções da GDI normalmente é mais demorado, como acontece com as fontes da GDI. Embora as
fontes de dispositivo sejam mais rápidas, elas são específicas do dispositivo, e normalmente são muito limitadoras quanto às fontes que um determinado dispositivo aceita.
Elementos básicos da fonte
Antes que você aprenda a usar as várias fontes no Win32, é preciso conhecer os vários termos e elementos associados às fontes do Win32.
Face de tipo, família e medidas de fonte
Pense em uma fonte como simplesmente uma figura ou glifo representando um caractere. Cada caractere
possui duas características: uma face de tipo e um tamanho.
No Win32, a face de tipo (ou typeface) de uma fonte refere-se ao estilo da fonte e seu tamanho. Provavelmente, a melhor definição da face de tipo e como ela se relaciona a uma fonte esteja no arquivo de
ajuda do Win32. Essa definição diz: “Uma face de tipo é uma coleção de caracteres que compartilham características de projeto; por exemplo, Courier é uma face de tipo comum. Uma fonte é uma coleção de
caracteres que possuem a mesma face de tipo e tamanho”.
O Win32 categoriza essas diferentes faces de tipo em cinco famílias de fonte: Decorative, Modern,
Roman, Script e Swiss. Os recursos distintos da fonte nessas famílias são as espessuras das serifas e traços
da fonte.
Uma serifa é uma pequena linha no início ou no final dos principais traços de uma fonte, que dão à
fonte uma aparência acabada. Um traço é a linha principal que compõe a fonte. A Figura 8.22 ilustra essas duas características.
Algumas das fontes típicas que você encontrará nas diferentes famílias de fonte são listadas na Tabela 8.8.
Tabela 8.8 Famílias de fonte e fontes típicas
Família de fonte
Fontes típicas
Decorative
Modern
Fontes inovadoras: Old English
Fontes com espessuras de traço constantes, que podem ou não ter serifas: Pica, Elite e
Courier New
Fotnes com espessuras de traço e serifas variáveis: Times New Roman e New Century
SchoolBook
Fontes que se parecem com escrita à mão: Script e Cursive
Fontes com espessuras de traço variáveis sem serifas: Arial e Helvetica
Roman
Script
Swiss
111
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.22
Serifas e traços.
O tamanho de uma fonte é representado em pontos (um ponto é equivalente a 1/72 de uma polegada). A altura de uma fonte consiste em seu ascendente e descendente. O ascendente e o descendente são
representados pelos valores tmAscent e tmDescent, conforme mostrado na Figura 8.23. A Figura 8.23 também mostra outros valores essenciais para a medida do caractere.
Os caracteres residem em uma célula de caractere, uma área cercando o caractere, que consiste em
espaço branco. Ao referir-se a medidas de caractere, lembre-se de que a medida poderá incluir tanto o
glifo do caractere (a parte visível do caractere) quanto a célula do caractere. Outras podem se referir somente a um ou outro.
A Tabela 8.9 explica o significado das várias medidas do caractere.
FIGURA 8.23
Os valores de medida de um caractere.
Tabela 8.9 Medidas de caracter
Medida
Significado
Leading externo
O espaço entre as linhas de texto
Leading interno
A diferença entre a altura do glifo do caractere e a altura da célula da fonte
Ascendente
Medida da linha de base ao topo da célula do caractere
Descendente
Medida da linha de base à parte inferior da célula do caractere
Tamanho de ponto
A altura do caractere menos tmInternalLeading
Altura
A soma de ascendente, descendente e leading interno
Linha de base
A linha em que os caracteres se apóiam
Categorias de fonte da GDI
Existem basicamente três categorias separadas de fontes da GDI: fontes de rastreio, fontes de vetor (também chamadas fontes de desenho) e fontes TrueType. As duas primeiras existiam nas versões mais antigas
do Win32, enquanto a última foi introduzida no Windows 3.1.
112
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Explicação sobre fontes de rastreio
Fontes de rastreio são basicamente bitmaps fornecidos para uma determinada resolução ou relação entre os
eixos (relação da altura e largura de pixel de um determinado dispositivo) e tamanho de fonte. Como essas
fontes são fornecidas em tamanhos específicos, o Win32 pode sintetizar a fonte para gerar uma nova fonte
no tamanho solicitado, mas só pode fazer isso para produzir uma fonte maior a partir de uma fonte menor.
O contrário não é possível, pois a técnica que o Win32 utiliza para sintetizar as fontes é duplicar as linhas e
colunas que compõem o bitmap original da fonte. As fontes de rastreio são convenientes quando o tamanho solicitado está disponível. Elas são mais rápidas de serem exibidas e possuem boa aparência quando
usadas no tamanho intencionado. A desvantagem é que elas ficam um tanto serrilhadas quando aparecem
em tamanhos maiores, como vemos na Figura 8.24, que apresenta a fonte System do Win32.
FIGURA 8.24
Uma fonte de rastreio.
Explicação sobre fontes de vetor
Fontes de vetor são geradas pelo Win32 com uma série de linhas criadas pelas funções da GDI, ao contrário de bitmaps. Essas fontes oferecem maior facilidade para redimensionamento do que as fontes de rastreio, mas possuem uma densidade muito menor quando exibidas, o que pode ou não ser desejado. Além
disso, o desempenho das fontes de vetor é lento em comparação com as fontes de rastreio. As fontes de
vetor servem melhor para uso com plotadoras, mas não são recomendadas para a criação de boas interfaces com o usuário. A Figura 8.25 mostra uma fonte de vetor típica.
FIGURA 8.25
Uma fonte de vetor.
Explicação sobre fontes TruteType
Fontes TrueType provavelmente sejam as preferidas dentre os três tipos de fonte. A vantagem do uso de
fontes TrueType é que elas podem representar praticamente qualquer estilo de fonte em qualquer tamanho, e podem parecer mais agradáveis aos olhos. O Win32 exibe fontes TrueType usando uma coleção
de pontos e hints (indicações) que descrevem o contorno da fonte. Hints são simplesmente algoritmos
para distorcer o contorno de uma fonte redimensionada a fim de melhorar sua aparência em diferentes
resoluções. A Figura 8.26 mostra uma fonte TrueType.
FIGURA 8.26
Uma fonte TrueType.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
113
Exibindo diferentes tipos de fonte
Até aqui, vimos os conceitos gerais em torno da tecnologia de fonte do Windows. Se você estiver interessado em aprofundar-se nos muitos detalhes sobre as fontes, dê uma olhada no arquivo de ajuda on-line
do Win32, consultando “Fonts Overview” (visão geral sobre fontes), que oferece uma grande quantidade de informações sobre o assunto. Agora, você aprenderá a usar a API e as estruturas do Win32 para
criar e exibir fontes de qualquer forma e tamanho.
Projeto de exemplo de criação de fonte
O exemplo a seguir ilustra o processo de instanciação de diferentes tipos de fonte no Windows. O projeto ilustra também como obter informações sobre uma fonte apresentada. Esse projeto está localizado no
CD como MakeFont.dpr.
Como funciona o projeto
Através do formulário principal, você seleciona vários atributos de fonte a serem usados na criação da
fonte. A fonte é então desenhada em um componente TPaintBox, sempre que você mudar o valor de um
dos atributos da fonte. (Todos os componentes estão ligados ao manipulador do evento FontChanged( )
através de seus eventos OnChange ou OnClick.) Você também pode exibir informações sobre uma fonte dando um clique no botão Font Information. A Figura 8.27 mostra o formulário principal para esse projeto.
A Listagem 8.9 mostra a unidade que define o formulário principal.
FIGURA 8.27
O formulário principal para o projeto de criação de fonte.
Listagem 8.9 O projeto de criação de fonte
unit MainFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Mask, Spin;
114
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.9 Continuação
const
// Array para representar os valores de TLOGFONT.lfCharSet
CharSetArray: array[0..4] of byte = (ANSI_CHARSET, DEFAULT_CHARSET,
SYMBOL_CHARSET, SHIFTJIS_CHARSET, OEM_CHARSET);
// Array para representar os valores de TLOGFONT.lfWeight
WeightArray: array[0..9] of integer =
(FW_DONTCARE, FW_THIN, FW_EXTRALIGHT, FW_LIGHT, FW_NORMAL, FW_MEDIUM,
FW_SEMIBOLD, FW_BOLD, FW_EXTRABOLD, FW_HEAVY);
// Array para representar os valores de TLOGFONT.lfOutPrecision
OutPrecArray: array[0..7] of byte = (OUT_DEFAULT_PRECIS,
OUT_STRING_PRECIS, OUT_CHARACTER_PRECIS, OUT_STROKE_PRECIS,
OUT_TT_PRECIS, OUT_DEVICE_PRECIS, OUT_RASTER_PRECIS,
OUT_TT_ONLY_PRECIS);
// Array para representar os 4 valores de bit altos de TLOGFONT.lfPitchAndFamily
FamilyArray: array[0..5] of byte = (FF_DONTCARE, FF_ROMAN,
FF_SWISS, FF_MODERN, FF_SCRIPT, FF_DECORATIVE);
// Array para representar os 2 valores de bit baixos de TLOGFONT.lfPitchAndFamily
PitchArray: array[0..2] of byte = (DEFAULT_PITCH, FIXED_PITCH,
VARIABLE_PITCH);
// Array para representar os valores de TLOGFONT.lfClipPrecision
ClipPrecArray: array[0..6] of byte = (CLIP_DEFAULT_PRECIS,
CLIP_CHARACTER_PRECIS, CLIP_STROKE_PRECIS, CLIP_MASK, CLIP_LH_ANGLES,
CLIP_TT_ALWAYS, CLIP_EMBEDDED);
// Array para representar os valores de TLOGFONT.lfQuality
QualityArray: array[0..2] of byte = (DEFAULT_QUALITY, DRAFT_QUALITY,
PROOF_QUALITY);
type
TMainForm = class(TForm)
lblHeight: TLabel;
lblWidth: TLabel;
gbEffects: TGroupBox;
cbxItalic: TCheckBox;
cbxUnderline: TCheckBox;
cbxStrikeOut: TCheckBox;
cbWeight: TComboBox;
lblWeight: TLabel;
lblEscapement: TLabel;
cbEscapement: TComboBox;
pbxFont: TPaintBox;
cbCharSet: TComboBox;
lblCharSet: TLabel;
cbOutPrec: TComboBox;
lblOutPrecision: TLabel;
cbFontFace: TComboBox;
rgPitch: TRadioGroup;
cbFamily: TComboBox;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
115
Listagem 8.9 Continuação
lblFamily: TLabel;
lblClipPrecision: TLabel;
cbClipPrec: TComboBox;
rgQuality: TRadioGroup;
btnSetDefaults: TButton;
btnFontInfo: TButton;
lblFaceName: TLabel;
rgGraphicsMode: TRadioGroup;
lblOrientation: TLabel;
cbOrientation: TComboBox;
seHeight: TSpinEdit;
seWidth: TSpinEdit;
procedure pbxFontPaint(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure btnFontInfoClick(Sender: TObject);
procedure btnSetDefaultsClick(Sender: TObject);
procedure rgGraphicsModeClick(Sender: TObject);
procedure cbEscapementChange(Sender: TObject);
procedure FontChanged(Sender: TObject);
private
{ Declarações privadas }
FLogFont: TLogFont;
FHFont: HFont;
procedure MakeFont;
procedure SetDefaults;
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
uses FontInfoFrm;
{$R *.DFM}
116
procedure TMainForm.MakeFont;
begin
// Apaga o conteúdo de FLogFont
FillChar(FLogFont, sizeof(TLogFont), 0);
// Define os campos de TLOGFONT
with FLogFont do
begin
lfHeight
:= StrToInt(seHeight.Text);
lfWidth
:= StrToInt(seWidth.Text);
lfEscapement
:= StrToInt(cbEscapement.Items[cbEscapement.ItemIndex]);
lfOrientation
:= StrToInt(cbOrientation.Items[cbOrientation.ItemIndex]);
lfWeight
:= WeightArray[cbWeight.ItemIndex];
lfItalic
:= ord(cbxItalic.Checked);
lfUnderline
:= ord(cbxUnderLine.Checked);
lfStrikeOut
:= ord(cbxStrikeOut.Checked);
lfCharSet
:= CharSetArray[cbCharset.ItemIndex];
lfOutPrecision
:= OutPrecArray[cbOutPrec.ItemIndex];
lfClipPrecision := ClipPrecArray[cbClipPrec.ItemIndex];
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.9 Continuação
lfQuality
:= QualityArray[rgQuality.ItemIndex];
lfPitchAndFamily := PitchArray[rgPitch.ItemIndex] or FamilyArray[cbFamily.ItemIndex];
StrPCopy(lfFaceName, cbFontFace.Items[cbFontFace.ItemIndex]);
end;
// Recupera a fonte solicitada
FHFont := CreateFontIndirect(FLogFont);
// Atribui Font.Handle
pbxFont.Font.Handle := FHFont;
pbxFont.Refresh;
end;
procedure TMainForm.SetDefaults;
begin
// Define os diversos controles com valores padrão para ALogFont
seHeight.Text
:= ‘0’;
seWidth.Text
:= ‘0’;
cbxItalic.Checked
:= false;
cbxStrikeOut.Checked
:= false;
cbxUnderline.Checked
:= false;
cbWeight.ItemIndex
:= 0;
cbEscapement.ItemIndex := 0;
cbOrientation.ItemIndex := 0;
cbCharset.ItemIndex
:= 1;
cbOutPrec.Itemindex
:= 0;
cbFamily.ItemIndex
:= 0;
cbClipPrec.ItemIndex
:= 0;
rgPitch.ItemIndex
:= 0;
rgQuality.ItemIndex
:= 0;
// Fill CBFontFace TComboBox with the screen’s fonts
cbFontFace.Items.Assign(Screen.Fonts);
cbFontFace.ItemIndex := cbFontFace.Items.IndexOf(Font.Name);
end;
procedure TMainForm.pbxFontPaint(Sender: TObject);
begin
with pbxFont do
begin
{ Observe que, no Windows 95, o modo gráfico sempre será GM_COMPATIBLE,
pois GM_ADVANCED é reconhecido apenas pelo Windows NT. }
case rgGraphicsMode.ItemIndex of
0: SetGraphicsMode(pbxFont.Canvas.Handle, GM_COMPATIBLE);
1: SetGraphicsMode(pbxFont.Canvas.Handle, GM_ADVANCED);
end;
Canvas.Rectangle(2, 2, Width-2, Height-2);
// Escreve o nome da fonte
Canvas.TextOut(Width div 2, Height div 2, CBFontFace.Text);
end;
end;
procedure TMainForm.FormActivate(Sender: TObject);
begin
SetDefaults;
MakeFont;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
117
Listagem 8.9 Continuação
procedure TMainForm.btnFontInfoClick(Sender: TObject);
begin
FontInfoForm.ShowModal;
end;
procedure TMainForm.btnSetDefaultsClick(Sender: TObject);
begin
SetDefaults;
MakeFont;
end;
procedure TMainForm.rgGraphicsModeClick(Sender: TObject);
begin
cbOrientation.Enabled := rgGraphicsMode.ItemIndex = 1;
if not cbOrientation.Enabled then
cbOrientation.ItemIndex := cbEscapement.ItemIndex;
MakeFont;
end;
procedure TMainForm.cbEscapementChange(Sender: TObject);
begin
if not cbOrientation.Enabled then
cbOrientation.ItemIndex := cbEscapement.ItemIndex;
end;
procedure TMainForm.FontChanged(Sender: TObject);
begin
MakeFont;
end;
end.
Em MAINFORM.PAS, você verá várias definições de array que serão explicadas em breve. Por enquanto,
observe que o formulário possui duas variáveis privadas: FLogFont e FHFont. FLogFont é do tipo TLOGFONT, uma
estrutura de registro usada para descrever a fonte a ser criada. FHFont é a alça para a fonte que está sendo
criada. O método privado MakeFont( ) é o local no qual você cria a fonte, primeiro preenchendo a estrutura FLogFont com valores especificados nos componentes do formulário principal e depois passando essa
estrutura para CreateFontIndirect( ), uma função da GDI do Win32 que retorna a alça de uma fonte para
a nova fonte. No entanto, antes de prosseguirmos, você precisa entender a estrutura TLOGFONT.
A estrutura TLOGFONT
Como já dissemos, você usa a estrutura TLOGFONT para definir a fonte que deseja criar. Essa estrutura é definida na unidade WINDOWS da seguinte forma:
118
TlogFont = record
lfHeight: Integer;
lfWidth: Integer;
lfEscapement: Integer;
lfOrientation: Integer;
lfWeight: Integer;
lfItalic: Byte;
lfUnderline: Byte;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
lfStrikeOut: Byte;
lfCharSet: Byte;
lfOutPrecision: Byte;
lfClipPrecision: Byte;
lfQuality: Byte;
lfPitchAndFamily: Byte;
lfFaceName: array[0..lf_FaceSize – 1] of Char;
end;
Você insere valores nos campos de TLOGFONT que especificam os atributos que deseja que sua fonte tenha. Cada campo representa um tipo de atributo diferente. Por default, a maioria dos campos pode ser
definida como zero, o que é feito com o botão Set Defaults (definir padrões) no formulário principal.
Nesse caso, o Win32 escolhe os atributos para a fonte e retornar o que lhe agradar. A regra geral é esta:
quanto mais campos você preencher, mais poderá ajustar o estilo da fonte. A lista a seguir explica o que
representa cada campo de TLOGFONT. Alguns dos campos podem receber valores de constante, que são predefinidos na unidade WINDOWS. Consulte a ajuda do Win32 para obter uma descrição detalhada desses valores; vamos mostrar apenas os mais usados:
Valor do campo
Descrição
lfHeight
A altura da fonte. Um valor maior do que zero indica uma altura de célula. Um valor
menor do que zero indica a altura do glifo (a altura da célula menos o leading interno). Defina esse campo em zero para deixar que o Win32 decida a altura para você.
lfWidth
A largura média da fonte. Defina esse campo em zero para deixar que o Win32 escolha uma largura de fonte para você.
lfEscapement
O ângulo (em décimos de graus) de rotação da linha de base da fonte (a linha ao longo da qual os caracteres são desenhados). No Windows 95/98, a string de texto e os
caracteres individuais são desenhados usando o mesmo ângulo. Ou seja, lfEscapement
e lfOrientation são iguais. No Windows NT, o texto é desenhado independentemente do ângulo de orientação de cada caractere na string de texto. Para conseguir esse
último resultado, você precisa definir o modo gráfico do dispositivo como
GM_ADVANCED usando a função SetGraphicsMode( ) da GDI do Win32. Por default, o
modo gráfico é GM_COMPATIBLE, que torna o comportamento do Windows NT semelhante ao do Windows 95. Esse efeito de rotação de fonte só está disponível para fon-
tes TrueType.
lfOrientation
Permite que você especifique um ângulo em que desenhará caracteres individuais. No
Windows 95/98, isso tem o mesmo valor de lfEscapement. No Windows NT, os valores podem ser diferentes. (Ver lfEscapement.)
lfWeight
Isso afeta a densidade da fonte. A unidade WINDOWS define várias constantes para esse
campo, como FW_BOLD e FW_NORMAL. Defina esse campo em FW_DONTCARE para permitir
que o Win32 escolha um peso para você.
lfItalic
Diferente de zero significa itálico; zero significa não-itálico.
lfUnderline
Diferente de zero significa sublinhado; zero significa não-sublinhado.
lfStrikeOut
Diferente de zero significa que uma linha é desenhada através da fonte, enquanto um
valor zero não desenha uma linha atravessando a fonte.
lfCharSet
O Win32 define os conjuntos de caractere: ANSI_CHARSET=0, DEFAULT_CHARSET=1,
SYMBOL_CHARSET=2, SHIFTJIS_CHARSET=128 E OEM_CHARSET=255. Por default, use
DEFAULT_CHARSET.
lfOutPrecision
Especifica como o Win32 precisa combinar o tamanho e as características da fonte solicitada com uma fonte real. Use TT_ONLY_PRECIS para especificar apenas fontes TrueType. Outros tipos são definidos na unidade WINDOWS.
119
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Valor do campo
Descrição
lfClipPrecision
Especifica como o Win32 corta os caracteres fora de uma região de corte. Use
CLIP_DEFAULT_PRECIS para deixar que o Win32 escolha.
lfQuality
Define a qualidade de saída da fonte, conforme desenhada pela GDI. use
DEFAULT_QUALITY para deixar que o Win32 decida, ou então você pode especificar
PROOF_QUALITY ou DRAFT_QUALITY.
lfPitchAndFamily
Define o pitch (espaçamento horizontal) da fonte nos dois bits de menor ordem.
Especifica a família nos quatro bits de alta ordem. A Tabela 8.8 apresenta essas
famílias.
lfFaceName
O nome da face de tipo da fonte.
O procedimento MakeFont( ) utiliza os valores definidos na seção de constantes de MainForm.pas. Essas
constantes de array contêm os diversos valores de constante predefinida para a estrutura TLOGFONT. Esses
valores são colocados na mesma ordem das escolhas nos componentes TComboBox do formulário principal.
Por exemplo, as escolhas para a família de fonte na caixa de combinação CBFamily estão na mesma ordem
dos valores em FamilyArray. Usamos essa técnica para reduzir o código necessário para preencher a estrutura TLOGFONT. A primeira linha da função MakeFont( )
fillChar(FLogFont, sizeof(TLogFont), 0);
apaga a estrutura FLogFont antes que quaisquer valores sejam definidos. Quando FLogFont tiver sido definido, a linha
FHFont := CreateFontIndirect(FLogFont);
chama a função CreateFontIndirect( ) da API do Win32, que aceita a estrutura TLOGFONT como um parâmetro e retorna uma alça para a fonte solicitada. Essa alça é então definida para a propriedade de alça de
TPaintBox.Font. Depois que a atribuição for feita, você redesenha pbxFont chamando seu método Refresh( ).
O método SetDefaults( ) inicializa a estrutura TLOGFONT com valores padrão. Esse método é chamado
quando o formulário principal é criado e sempre que o usuário dá um clique no botão Set Defaults (definir padrões). Experimente o projeto para ver os diferentes efeitos que você pode obter com as fontes,
conforme vemos na Figura 8.28.
120 F I G U R A 8 . 2 8 Uma fonte girada.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Exibindo informações sobre fontes
O botão Font Information (informações da fonte) do formulário principal chama o formulário
FontInfoForm, que apresenta informações sobre a fonte selecionada. Quando você especifica atributos
de fonte na estrutura TLOGFONT, o Win32 tenta oferecer a fonte mais semelhante à sua fonte solicitada. É
inteiramente possível que a fonte que você receba de volta da função CreateFontIndirect( ) tenha atributos completamente diferentes do que você solicitou. FontInfoForm permite que você analise os atributos
da sua fonte selecionada. Ele usa a função GetTextMetrics( ) da API do Win32 para apanhar as informações sobre a fonte.
GetTextMetrics( ) utiliza dois parâmetros: a alça para o contexto de dispositivo cuja fonte você quer
examinar e uma referência para outra estrutura do Win32, TTEXTMETRIC. GetTextMetric( ) atualiza então a
estrutura TTEXTMETRIC com informações sobre a fonte indicada. A unidade WINDOWS define o registro
TTEXTMETRIC da seguinte forma:
TTextMetric = record
tmHeight: Integer;
tmAscent: Integer;
tmDescent: Integer;
tmInternalLeading: Integer;
tmExternalLeading: Integer;
tmAveCharWidth: Integer;
tmMaxCharWidth: Integer;
tmWeight: Integer;
tmItalic: Byte;
tmUnderlined: Byte;
tmStruckOut: Byte;
tmFirstChar: Byte;
tmLastChar: Byte;
tmDefaultChar: Byte;
tmBreakChar: Byte;
tmPitchAndFamily: Byte;
tmCharSet: Byte;
tmOverhang: Integer;
tmDigitizedAspectX: Integer;
tmDigitizedAspectY: Integer;
end;
Os campos do registro TTEXTMETRIC contêm grande parte das mesmas informações que já discutimos
anteriormente sobre fontes. Por exemplo, ele mostra a altura da fonte, a largura média do caractere e se a
fonte é sublinhada, em itálico, tachada etc. Consulte a ajuda on-line da API do Win32 para obter informações detalhadas sobre a estrutura de TTEXTMETRIC. A Listagem 8.10 mostra o código para o formulário
de informações de fonte.
Listagem 8.10 Código-fonte para o formulário de informações de fonte
unit FontInfoFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, ExtCtrls, StdCtrls;
type
121
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.10 Continuação
TFontInfoForm = class(TForm)
lbFontInfo: TListBox;
procedure FormActivate(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
FontInfoForm: TFontInfoForm;
implementation
uses MainFrm;
{$R *.DFM}
procedure TFontInfoForm.FormActivate(Sender: TObject);
const
PITCH_MASK: byte = $0F; // Ativa os quatro bits de ordem inferior
FAMILY_MASK: byte = $F0; // Ativa os quatro bits de ordem suferior
var
TxMetric: TTextMetric;
FaceName: String;
PitchTest, FamilyTest: byte;
begin
// Aloca memória para a string FaceName
SetLength(FaceName, lf_FaceSize+1);
// Primeiro apanha a informação da fonte
with MainForm.pbxFont.Canvas do
begin
GetTextFace(Handle, lf_faceSize-1, PChar(FaceName));
GetTextMetrics(Handle, TxMetric);
end;
// Agora inclui informação na caixa de listagem, vinda da estrutura TTEXTMETRIC.
with lbFontInfo.Items, TxMetric do
begin
Clear;
Add(‘Font face name:
‘+FaceName);
Add(‘tmHeight:
‘+IntToStr(tmHeight));
Add(‘tmAscent:
‘+IntToStr(tmAscent));
Add(‘tmDescent:
‘+IntToStr(tmDescent));
Add(‘tmInternalLeading:
‘+IntToStr(tmInternalLeading));
Add(‘tmExternalLeading:
‘+IntToStr(tmExternalLeading));
Add(‘tmAveCharWidth:
‘+IntToStr(tmAveCharWidth));
Add(‘tmMaxCharWidth:
‘+IntToStr(tmMaxCharWidth));
Add(‘tmWeight:
‘+IntToStr(tmWeight));
122
if tmItalic < > 0 then
Add(‘tmItalic: YES’)
else
Add(‘tmItalic: NO’);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 8.10 Continuação
if tmUnderlined < > 0 then
Add(‘tmUnderlined: YES’)
else
Add(‘tmUnderlined: NO’);
if tmStruckOut < > 0 then
Add(‘tmStruckOut: YES’)
else
Add(‘tmStruckOut: NO’);
// Verifica o tipo de pitch da fonte
PitchTest := tmPitchAndFamily and PITCH_MASK;
if (PitchTest and TMPF_FIXED_PITCH) = TMPF_FIXED_PITCH then
Add(‘tmPitchAndFamily-Pitch: Fixed Pitch’);
if (PitchTest and TMPF_VECTOR) = TMPF_VECTOR then
Add(‘tmPitchAndFamily-Pitch: Vector’);
if (PitchTest and TMPF_TRUETYPE) = TMPF_TRUETYPE then
Add(‘tmPitchAndFamily-Pitch: True type’);
if (PitchTest and TMPF_DEVICE) = TMPF_DEVICE then
Add(‘tmPitchAndFamily-Pitch: Device’);
if PitchTest = 0 then
Add(‘tmPitchAndFamily-Pitch: Unknown’);
// Verifica o tipo da família da fonte
FamilyTest := tmPitchAndFamily and FAMILY_MASK;
if (FamilyTest and FF_ROMAN) = FF_ROMAN then
Add(‘tmPitchAndFamily-Family: FF_ROMAN’);
if (FamilyTest and FF_SWISS) = FF_SWISS then
Add(‘tmPitchAndFamily-Family: FF_SWISS’);
if (FamilyTest and FF_MODERN) = FF_MODERN then
Add(‘tmPitchAndFamily-Family: FF_MODERN’);
if (FamilyTest and FF_SCRIPT) = FF_SCRIPT then
Add(‘tmPitchAndFamily-Family: FF_SCRIPT’);
if (FamilyTest and FF_DECORATIVE) = FF_DECORATIVE then
Add(‘tmPitchAndFamily-Family: FF_DECORATIVE’);
if FamilyTest = 0 then
Add(‘tmPitchAndFamily-Family: Unknown’);
Add(‘tmCharSet:
‘+IntToStr(tmCharSet));
Add(‘tmOverhang:
‘+IntToStr(tmOverhang));
Add(‘tmDigitizedAspectX:
‘+IntToStr(tmDigitizedAspectX));
Add(‘tmDigitizedAspectY:
‘+IntToStr(tmDigitizedAspectY));
end;
end;
end.
O método FormActive( ) primeiro apanha o nome da fonte com a função GetTextFace( ) da API do
Win32, que apanha um contexto de dispositivo, um tamanho de buffer e um buffer de caracteres terminado em nulo como parâmetros. FormActivate( ) então utiliza GetTextMetrics( ) para preencher TxMetric,
uma estrutura de registro de TTEXTMETRIC, para a fonte selecionada. O manipulador de evento então acrescenta os valores de TxMetric na caixa de listagem como strings. Para o valor de tmPitchAndFamily, você mascara o bit de alta ou baixa ordem, dependendo do valor que você está testando, e inclui os valores apropriados na caixa de listagem. A Figura 8.29 mostra FontInfoForm exibindo informações sobre uma fonte. 123
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 8.29
O formulário de informações de fonte.
Resumo
Este capítulo apresentou muitas informações sobre a interface de dispositivos gráficos (GDI) do Win32.
Discutimos sobre a TCanvas do Delphi 5, suas propriedades e seus métodos de desenho. Também discutimos sobre a representação de imagens no Delphi 5 com seu componente TImage, além dos modos de mapeamento e sistemas de coordenadas do Win32. Você viu como pode usar as técnicas de programação
gráfica para criar um programa de pintura e realizar animação simples. Finalmente, discutimos sobre
fontes e como criá-las e exibir informações sobre elas na tela. Uma das coisas mais interessantes da GDI é
que o trabalho com ela pode ser muito divertido. Livros inteiros já foram escritos apenas sobre esse assunto. Gaste algum tempo experimentando as rotinas de desenho, criando suas próprias fontes ou simplesmente mexendo com os modos de mapeamento para ver o tipo de efeito que consegue.
124
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Impressão em
Delphi 5
CAPÍTULO
10
NE STE CAP ÍT UL O
l
O objeto TPrinter
l
TPrinter.Canvas
l
Impressão simples
l
Impressão de um formulário
l
Impressão avançada
l
Tarefas de impressão diversas
l
Como obter informações da impressora
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 4 — 1ª PROVA
A impressão no Windows tem sido a ruína de muitos programadores para Windows. No entanto,
não fique desencorajado; o Delphi simplifica a maioria do que você precisa saber sobre impressão. Você
pode escrever rotinas simples para gerar texto ou imagens de bitmap com pouco esforço. Para a impressão mais complexa, alguns conceitos e técnicas são tudo o que você realmente precisa para poder realizar
qualquer tipo de impressão personalizada. Quando você entender isso, a impressão não será difícil.
NOTA
Você encontrará um conjunto de componentes de relatório da QuSoft na página QReport da Component
Palette. A documentação para essa ferramenta está localizada no arquivo de ajuda QuickRpt.hlp.
As ferramentas da QuSoft são apropriadas para aplicações que geram relatórios complexos. No entanto,
elas o limitam a sua utilização dos detalhes da impressão em nível de código-fonte, onde terá mais controle
sobre o que é impresso. Este capítulo não aborda o QuickReports; em vez disso, ele aborda a criação dos
seus próprios relatórios no Delphi.
O objeto TPrinter do Delphi, que encapsula o mecanismo de impressão do Windows, realiza um ótimo trabalho para você, que de outra forma teria de ser feito por você mesmo.
Este capítulo ensina a realizar diversas operações de impressão usando TPrinter. Você aprenderá sobre tarefas simples que o Delphi tornou muito mais fáceis para a criação de tarefas de impressão. Também aprenderá sobre as técnicas de criação de rotinas avançadas para impressão, que lhe dará partida
para se tornar um guru da impressão.
O objeto TPrinter
O objeto TPrinter encapsula a interface de impressão do Windows, tornando a maioria do gerenciamento
de impressão invisível para você. Os métodos e as propriedades de TPrinter permitem imprimir em sua
tela (canvas) como se você estivesse desenhando sua saída na superfície de um formulário. A função Printer( ) retorna uma instância global de TPrinter na primeira vez que é chamada. As propriedades e métodos de TPrinter são listados nas Tabelas 10.1 e 10.2.
Tabela 10.1 Propriedades de TPrinter
Propriedade
Finalidade
Aborted
Variável Booleana que determina se o usuário abortou o trabalho de impressão.
A superfície de impressão para a página atual.
Contém uma lista de fontes aceitas pela impressora.
Um número exclusivo representando a alça de dispositivo da impressora. Veja a nota explicativa “Alças” no Capítulo 20.
Determina impressão horizontal (poLandScape) ou (vertical poPortrait).
Altura da superfície da página impressa, em pixels.
Indica a página sendo impressa. O valor é incrementado a cada chamada subseqüente a
TPrinter.NewPage( ).
Largura, em pixels, da superfície da página impressa.
Indica a impressora selecionada a partir das impressoras disponíveis no sistema do usuário.
Uma lista das impressoras disponíveis no sistema.
Determina se o trabalho de impressão está sendo impresso.
Texto que aparece no Gerenciador de Impressão e nas páginas em rede.
Canvas
Fonts
Handle
Orientation
PageHeight
PageNumber
PageWidth
PrinterIndex
Printers
Printing
Title
126
f
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela 10.2 Métodos de TPrinter
Método
Finalidade
Abort
Cancela um trabalho de impressão.
BeginDoc
Inicia um trabalho de impressão.
EndDoc
Termina um trabalho de impressão. (EndDoc termina um trabalho quando a impressão estiver
terminada; Abort pode encerrar o trabalho antes que a impressão normal termine.)
GetPrinter
Obtém a impressora ativa.
NewPage
Força a impressora a começar a impressão em uma nova página e incrementa a propriedade
PageCount.
SetPrinter
Especifica a impressora como uma impressora ativa.
TPrinter.Canvas
TPritner.Canvas é semelhante à tela de desenho do seu formulário; ela representa a superfície de desenho
na qual o texto e os gráficos são desenhados. A diferença é que TPritner.Canvas representa a superfície de
desenho para a sua saída impressa, ao contrário da tela do monitor de vídeo. A maioria das rotinas que
você usa para desenhar texto, para desenhar formas e para exibir imagens é usada da mesma forma para a
saída impressa. Entretanto, ao imprimir, você precisa levar em consideração algumas diferenças:
l
l
l
l
O desenho na tela é dinâmico – você pode apagar o que colocou na saída da tela. O desenho na
impressora não é tão flexível. O que for desenhado em TPritner.Canvas é impresso no papel da
impressora.
O desenho de texto ou gráficos na tela é quase instantâneo, enquanto o desenho na impressora é
lento, até mesmo em algumas impressoras a laser de alto desempenho. Portanto, você deve permitir que os usuários cancelem um trabalho de impressão usando uma caixa de diálogo Abortar
ou por algum outro método que lhes permita encerrar o trabalho de impressão.
Visto que seus usuários estão usando o Windows, poderá considerar que seu vídeo suporta saída
gráfica. No entanto, você não pode considerar o mesmo para suas impressoras. Diferentes impressoras possuem diferentes recursos. Algumas delas podem ser impressoras de alta resolução;
outras impressoras podem ter resolução muito baixa e podem não aceitar qualquer impressão
gráfica. Você precisa levar isso em consideração nas suas rotinas de impressão.
Você nunca verá uma mensagem de erro como esta:
Vídeo sem espaço na tela,
favor inserir mais espaço na tela do seu vídeo.
Mas você pode apostar que verá um erro informando que a impressora está sem papel. Tanto o
Windows NT/2000 quanto o Windows 95/98 oferecem tratamento de erro quando isso ocorre.
No entanto, você deverá fornecer um meio para o usuário cancelar a impressão quando isso
acontecer.
l
Texto e gráficos na sua tela não têm a mesma aparência quando impressos em papel. As impressoras e os monitores possuem resoluções muito diferentes. Aquele bitmap de 300x300 pode parecer espetacular em uma tela de 640x480, mas é um simples borrão quadrado de 2,5 x 2,5 cm
na sua impressora a laser de 300 dpi (pontos por polegada). Você é responsável por fazer os ajustes nas suas rotinas de desenho para que os usuários não precisem de uma lente de aumento para
ler sua saída impressa.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
127
Impressão simples
Em muitos casos, você deseja enviar um fluxo de texto para a sua impressora sem considerar formatação
especial ou posicionamento do texto. O Delphi facilita a impressão simples, conforme ilustramos nas
próximas seções.
Imprimindo o conteúdo de um componente TMemo
A impressão de linhas de texto é realmente muito simples com o procedimento AssignPrn( ). O procedimento AssignPrn( ) permite atribuir uma variável de arquivo de texto para a impressora atual. Ele é usado
com os procedimentos Rewrite( ) e CloseFile( ). As linhas de código a seguir ilustram essa sintaxe:
var
f: TextFile;
begin
AssignPrn(f);
try
Rewrite(f);
writeln(f, ‘Imprime a saída’);
finally
CloseFile(f);
end;
end;
A impressão de uma linha de texto na impressora é o mesmo que imprimir uma linha de texto em
um arquivo. Você usa esta sintaxe:
writeln(f, ‘Esta é a minha linha de texto’);
No Capítulo 16, você incluirá opções de menu para imprimir o conteúdo do formulário TMdiEditForm. A Listagem 10.1 mostra como imprimir o conteúdo de TMdiEditForm. Você usará a mesma técnica
para imprimir texto de praticamente qualquer origem.
Listagem 10.1 Código de impressão para TMdiEditForm
procedure TMdiEditForm.mmiPrintClick(Sender: TObject);
var
i: integer;
PText: TextFile;
begin
inherited;
if PrintDialog.Execute then
begin
AssignPrn(PText);
Rewrite(PText);
try
Printer.Canvas.Font := memMainMemo.Font;
for i := 0 to memMainMemo.Lines.Count -1 do
writeln(PText, memMainMemo.Lines[i]);
finally
CloseFile(PText);
end;
end;
end;
128
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Observe que a fonte do memo também foi atribuída à fonte de Printer, fazendo com que a saída impressa tenha a mesma fonte de memMainMemo.
ATENÇÃO
Saiba que a impressora imprimirá com a fonte especificada por Printer.Fonr somente se ela aceitar a
fonte. Caso contrário, a impressora usará uma fonte que se aproxime às características da fonte especificada.
Imprimindo um bitmap
A impressão de um bitmap é bastante simples. O exemplo MdiApp do Capítulo 16 mostra como imprimir o
conteúdo de um bitmap em TMdiBmpForm. Este manipulador de evento aparece na Listagem 10.2.
Listagem 10.2 Código de impressão para TMdiBmpForm
procedure TMdiBMPForm.mmiPrintClick(Sender: TObject);
begin
inherited;
with ImgMain.Picture.Bitmap do
begin
Printer.BeginDoc;
Printer.Canvas.StretchDraw(Canvas.ClipRect, imgMain.Picture.Bitmap);
Printer.EndDoc;
end; { with }
end;
Apenas três linhas de código são necessárias para imprimir o bitmap usando o método TCanvas.StretchDraw( ). Essa grande simplificação da impressão de um bitmap é possível pelo fato de que, des-
de o Delphi 3, por default, os bitmaps estão no formato DIB, e os DIBs são o que o driver de impressora
exige. Se você tiver uma alça para um bitmap que não esteja no formato DIB, poderá copiá-lo (Assign) a
um TBitmap temporário, forçar o bitmap temporário para o formato DIB atribuindo bmDIB à propriedade
TBitmap.HandleType e depois imprimir pelo novo DIB.
NOTA
Uma das chaves para a impressão é poder imprimir imagens conforme aparecem na tela e aproximadamente no mesmo tamanho. Uma imagem de 10 x 10 cm em uma tela de 640x480 pixels usa menos pixels
do que usaria em uma impressora de 300 dpi, por exemplo. Portanto, estique a imagem na tela de desenho
de TPrinter como foi feito no exemplo, na chamada a StretchDraw( ). Outra técnica é desenhar a imagem
usando um modo de mapeamento diferente, conforme descrito no Capítulo 8. Lembre-se de que algumas
impressoras mais antigas podem não aceitar imagens esticadas. Você poderá obter informações valiosas
sobre os recursos da impressora usando a função GetDeviceCaps( ) da API do Win32.
129
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Imprimindo dados formatados como “rich text”
A impressão do conteúdo de um componente TRichEdit é uma questão de uma chamada ao método. O código a seguir mostra como fazer isso (esse também é o código para imprimir TMidRtfForm no exemplo
MdiApp do Capítulo 16):
procedure TMdiRtfForm.mmiPrintClick(Sender: Tobject);
begin
inherited;
reMain.Print(Caption);
end;
Impressão de um formulário
Conceitualmente, a impressão de um formulário pode ser uma das tarefas mais difíceis de se realizar. No
entanto, essa tarefa foi bastante simplificada graças ao método Print( ) de TForm, na VCL. O procedimento de uma linha a seguir imprime a área do cliente do seu formulário, além de todos os componentes que
residem na área do cliente:
procedure TForm1.PrintMyForm(Sender: Tobject);
begin
Print;
end;
NOTA
A impressão do seu formulário é um modo rápido e fácil de imprimir a saída gráfica. No entanto, somente o
que está visível na tela será impresso, devido ao corte realizado pelo Windows. Além disso, o bitmap é criado na densidade de pixel da tela, e depois esticado para a resolução da impressora. O texto no formulário
não é desenhado na resolução da impressora; ele é desenhado na resolução da tela e esticado, de modo
que, em geral, o formulário ficará com uma aparência serrilhada e em blocos. Você precisa usar técnicas
mais elaboradas para imprimir gráficos complexos; essas técnicas são discutidas mais adiante neste capítulo.
Impressão avançada
Constantemente você precisa imprimir algo muito específico, que não seja facilitado pela ferramenta de
desenvolvimento que está utilizando ou por uma ferramenta de relatório de terceiros. Nesse caso, você
mesmo precisa realizar as tarefas de impressão em baixo nível. As próximas seções lhe mostram como escrever tais rotinas de impressão e apresentam uma metodologia que você pode aplicar a todas as suas tarefas de impressão.
NOTA
Embora esta seção se refira à impressão, você precisa saber que, no momento em que este livro foi escrito,
vários componentes de impressão de terceiros estão disponíveis para tratar da maioria das suas necessidades de impressão. Você encontrará demonstrações de algumas dessas ferramentas no CD que acompanha
este livro.
130
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Imprimindo um relatório em colunas
Muitas aplicações, principalmente as de bancos de dados, imprimem algum tipo de relatório. Um estilo
de relatório comum é o relatório em colunas.
O projeto a seguir imprime um relatório em colunas a partir de uma das tabelas nos diretórios de
demonstração do Delphi. Cada página contém um cabeçalho, títulos de coluna e depois a listagem dos
registros. Cada página subseqüente também possui o cabeçalho e os títulos de coluna antes da listagem
dos registros.
A Figura 10.1 mostra o formulário principal desse projeto. Os pares TEdit/TUpDown permitem que o
usuário especifique as larguras de coluna em décimos de polegadas. Usando os componentes TUpDown,
você pode especificar valores mínimo e máximo. O controle TEdit1, edtHeaderFont, contém um cabeçalho
que pode ser impresso usando uma fonte diferente daquela usada para o restante do relatório.
FIGURA 10.1
Formulário principal do relatório em colunas.
A Listagem 10.3 mostra o código-fonte para o projeto. O manipulador de evento mmiPrintClick( )
basicamente realiza as seguintes etapas:
1.
2.
3.
4.
5.
6.
Inicia um trabalho de impressão.
Imprime um cabeçalho.
Imprime nomes de coluna.
Imprime uma página.
Continua nas etapas 2, 3 e 4 até a impressão terminar.
Encerra o trabalho de impressão.
Listagem 10.3 Demonstração de relatório em colunas
unit MainFrm;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Grids, DBGrids, DB, DBTables, Menus, StdCtrls, Spin,
Gauges, ExtCtrls, ComCtrls;
type
TMainForm = class(TForm)
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
131
Listagem 10.3 Continuação
{Componentes não incluídos na listagem, por favor, refira-se ao CD fonte}
procedure mmiPrintClick(Sender: TObject);
procedure btnHeaderFontClick(Sender: TObject);
private
PixelsInInchx: integer;
LineHeight: Integer;
{ Registra o espaço vertical em pixels impressos em uma página }
AmountPrinted: integer;
{ Número de pixels em 1/10”. Isso é usado para o espaçamento de linha }
TenthsOfInchPixelsY: integer;
procedure PrintLine(Items: TStringList);
procedure PrintHeader;
procedure PrintColumnNames;
end;
var
MainForm: TMainForm;
implementation
uses printers, AbortFrm;
{$R *.DFM}
procedure TMainForm.PrintLine(Items: TStringList);
var
OutRect: TRect;
Inches: double;
i: integer;
begin
// Primeiro posiciona o retângulo de impressão na tela de impressão
OutRect.Left := 0;
OutRect.Top := AmountPrinted;
OutRect.Bottom := OutRect.Top + LineHeight;
With Printer.Canvas do
for i := 0 to Items.Count - 1 do
begin
Inches := longint(Items.Objects[i]) * 0.1;
// Determina a borda direita
OutRect.Right := OutRect.Left + round(PixelsInInchx*Inches);
if not Printer.Aborted then
// Imprime a linha
TextRect(OutRect, OutRect.Left, OutRect.Top, Items[i]);
// Ajusta a borda direita
OutRect.Left := OutRect.Right;
end;
{ À medida que cada linha é impressa, AmountPrinted precisa aumentar
para refletir o espaço em que uma página foi impressa com base
na altura da linha. }
AmountPrinted := AmountPrinted + TenthsOfInchPixelsY*2;
end;
132
procedure TMainForm.PrintHeader;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.3 Continuação
var
SaveFont: TFont;
begin
{ Salva a fonte da impressora ativa, e depois define uma nova fonte
de impressão com base na seleção de Edit1 }
SaveFont := TFont.Create;
try
Savefont.Assign(Printer.Canvas.Font);
Printer.Canvas.Font.Assign(edtHeaderFont.Font);
// Primeiro imprime o cabeçalho
with Printer do
begin
if not Printer.Aborted then
Canvas.TextOut((PageWidth div 2)-(Canvas.TextWidth(edtHeaderFont.Text)
div 2),0, edtHeaderFont.Text);
// Incrementa AmountPrinted por LineHeight
AmountPrinted := AmountPrinted + LineHeight+TenthsOfInchPixelsY;
end;
// Restaura a fonte antiga para a propriedade Canvas de Printer
Printer.Canvas.Font.Assign(SaveFont);
finally
SaveFont.Free;
end;
end;
procedure TMainForm.PrintColumnNames;
var
ColNames: TStringList;
begin
{ Cria uma TStringList para conter os nomes de coluna e as posições
em que a largura de cada coluna é baseada nos valores dos
controles TEdit. }
ColNames := TStringList.Create;
try
// Imprime cabeçalhos de coluna usando estilo negrito/sublinhado
Printer.Canvas.Font.Style := [fsBold, fsUnderline];
with ColNames do
begin
// Mantém cabeçalhos e larguras de coluna no objeto TStringList
AddObject(‘LAST NAME’, pointer(StrToInt(edtLastName.Text)));
AddObject(‘FIRST NAME’, pointer(StrToInt(edtFirstName.Text)));
AddObject(‘ADDRESS’,
pointer(StrToInt(edtAddress.Text)));
AddObject(‘CITY’,
pointer(StrToInt(edtCity.Text)));
AddObject(‘STATE’,
pointer(StrToInt(edtState.Text)));
AddObject(‘ZIP’,
pointer(StrToInt(edtZip.Text)));
end;
PrintLine(ColNames);
Printer.Canvas.Font.Style := [ ];
finally
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
133
Listagem 10.3 Continuação
ColNames.Free;
end;
end;
// Free the column name TStringList instance
procedure TMainForm.mmiPrintClick(Sender: TObject);
var
Items: TStringList;
begin
{ Cria uma instância de TStringList para conter campos e larguras
de colunas em que serão desenhados com base no conteúdo dos
controles de edição. }
Items := TStringList.Create;
try
// Determina pixels por polegada na horizontal
PixelsInInchx := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
TenthsOfInchPixelsY := GetDeviceCaps(Printer.Handle,
LOGPIXELSY) div 10;
AmountPrinted := 0;
MainForm.Enabled := false; // Disable the parent form
try
Printer.BeginDoc;
AbortForm.Show;
Application.ProcessMessages;
{ Calcula a altura da linha com base na altura do texto usando
a fonte atualmente utilizada }
LineHeight := Printer.Canvas.TextHeight(‘X’)+TenthsOfInchPixelsY;
if edtHeaderFont.Text < > ‘’ then
PrintHeader;
PrintColumnNames;
tblClients.First;
{ Mantém o valor de cada campo na TStringList, além de sua
largura de coluna. }
while (not tblClients.Eof) or Printer.Aborted do
begin
134
Application.ProcessMessages;
with Items do
begin
AddObject(tblClients.FieldByName(‘LAST_NAME’).AsString,
pointer(StrToInt(edtLastName.Text)));
AddObject(tblClients.FieldByName(‘FIRST_NAME’).AsString,
pointer(StrToInt(edtFirstName.Text)));
AddObject(tblClients.FieldByName(‘ADDRESS_1’).AsString,
pointer(StrToInt(edtAddress.Text)));
AddObject(tblClients.FieldByName(‘CITY’).AsString,
pointer(StrToInt(edtCity.Text)));
AddObject(tblClients.FieldByName(‘STATE’).AsString,
pointer(StrToInt(edtState.Text)));
AddObject(tblClients.FieldByName(‘ZIP’).AsString,
pointer(StrToInt(edtZip.Text)));
end;
PrintLine(Items);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.3 Continuação
{ Força a impressão a começar em nova página, se a saída impressa
exceder a altura da página. }
if AmountPrinted + LineHeight > Printer.PageHeight then
begin
AmountPrinted := 0;
if not Printer.Aborted then
Printer.NewPage;
PrintHeader;
PrintColumnNames;
end;
Items.Clear;
tblClients.Next;
end;
AbortForm.Hide;
if not Printer.Aborted then
Printer.EndDoc;
finally
MainForm.Enabled := true;
end;
finally
Items.Free;
end;
end;
procedure TMainForm.btnHeaderFontClick(Sender: TObject);
begin
{ Atribui a fonte selecionada com FontDialog1 a Edit1. }
FontDialog.Font.Assign(edtHeaderFont.Font);
if FontDialog.Execute then
edtHeaderFont.Font.Assign(FontDialog.Font);
end;
end.
mmiPrintClick( ) primeiro cria uma instância de TStringList para conter os strings para uma linha a
ser impressa. Em seguida, é determinado o número de pixels por polegada no eixo horizontal em PixelsInInchX, que é usado para calcular larguras de coluna. TenthsOfInchPixelsY é usado para espaçar cada linha em 0,1 polegada. AmountPrinted contém a quantidade total de pixels, para cada linha impressa, ao longo do eixo vertical da superfície impressa. Isso é necessário para determinar se uma nova página deve ser
iniciada quando AmountPrinted exceder a Printer.PageHeigh.
Se houver um cabeçalho em edtHeaderFont.Text ele será impresso em PrinterHeader( ). PrintColumnNames( ) imprime os nomes das colunas para cada campo a ser impresso. (Esses dois procedimentos são discutidos mais adiante nesta seção.) Finalmente, os registros da tabela são impressos.
O loop a seguir incrementa os registros de tblClients e imprime campos selecionados dentro de cada
um dos registros:
while (not tblClients.Eof) or Printer.Aborted do begin
Dentro do loop, os valores de campo são incluídos na TStringList usando o método AddObject( ).
Aqui, você armazena a string e a largura da coluna. A largura da coluna é incluída na propriedade do ar- 135
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
ray Items.Objects. Items é então passado para o procedimento PrintLine( ), que imprime as strings em um
formato de colunas.
Em grande parte do código anterior, você viu referências a Printer.Aborted. Esse é um teste para determinar se o usuário abortou o trabalho de impressão, um tópico explicado na próxima seção.
DICA
As propriedades do array Objects de TStrings e TStringList são locais convenientes para se armazenar valores inteiros. Usando AddObject( ) ou InsertObject( ), você pode manter qualquer número até MaxLongInt. Como AddObject( ) espera uma referência a TObject como seu segundo parâmetro, você precisa
passar esse parâmetro como um ponteiro, como vemos no código a seguir:
MyList.AddObject(‘SomeString’, pointer(SomeInteger));
Para apanhar o valor, use um modificador Longint:
MyInteger := Longint(MyList.Objects[Index]);
O manipulador de evento determina então se a impressão de uma nova linha excederá a altura da
página:
if AmountPrinted + LineHeight > Printer.PageHeight then
Se a expressão for avaliada como True, AmountPrinted retornará para 0, Printer.NewPage será chamado
para imprimir uma nova página e os nomes de cabeçalho e coluna serão impressos novamente. Printer.EndDoc é chamado para encerrar o trabalho de impressão depois que os registros de tblClients tiverem
sido impressos.
O procedimento PrintHeader( ) imprime o cabeçalho centralizado no topo do relatório, usando edtHeaderFont.Text e edtHeaderFont.Font. AmountPrinted é então incrementado e a fonte de Printer é restaurada
ao seu estilo original.
Como o nome já diz, PrintColumnNames( ) imprime os nomes de coluna do relatório. Nesse método,
os nomes são incluídos a um objeto TStringList, ColNames, que é então passado para PrintLine( ). Observe
que os nomes de coluna são impressos com uma fonte em negrito e sublinhado. A definição correta de
Printer.Canvas.Font faz isso.
O procedimento PrintLine( ) apanha um argumento de TStringList chamado Items e imprime
cada string de Items em uma única linha em formato de colunas. A variável OutRect contém valores
para um retângulo delimitador em um local na tela de desenho (canvas) de Printer, no qual o texto é
desenhado. OutRect é passado para TextRect( ), junto com o texto a desenhar. Multiplicando
Items.Object[i] por 0,1, o valor de OutRect.Right é obtido, pois Items.Object[i] está em décimos de polegada. Dentro do loop for, OutRect é recalculado ao longo do mesmo eixo X para posicioná-lo para a
próxima coluna e desenhar o próximo valor de texto. Finalmente, AmountPrinted é incrementado em
LineHeight + TenthsOfInchPixelsY.
Embora esse relatório seja totalmente funcional, você poderá estendê-lo para incluir um rodapé,
números de página e até mesmo configurações de margem.
Abortando o processo de impressão
136
Em outro ponto deste capítulo, você aprendeu que seus usuários precisam de um meio para determinar um
modo para terminar a impressão depois que eles a tiverem iniciado. O procedimento TPrinter.Abort( ) e a
propriedade Aborted o ajudam a fazer isto. O código da Listagem 10.3 contém essa lógica. Para incluir a lógica para abortar a impressão nas rotinas, seu código precisa atender a estas três condições:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
l
l
Você precisa estabelecer um evento que, quando ativado, chame Printer.Abort, abortando assim
o processo de impressão.
Você precisa verificar a condição TPrinter.Aborted = True antes de chamar quaisquer funções de
impressão de TPrinter, como TextOut( ), NewPage( ) e EndDoc( ).
Você precisa encerrar sua lógica de impressão verificando se o valor de TPrinter.Aborted é True.
Uma caixa de diálogo de Abort simples pode satisfazer a primeira condição. Você usou tal caixa de
diálogo no exemplo anterior. Essa caixa deverá conter um botão para chamar o processo que aborta a
impressão.
O manipulador de evento desse botão deverá simplesmente chamar TPrinter.Abort, que encerra o
trabalho de impressão e cancela quaisquer pedidos pendentes feitos a TPrinter.
Na unidade MainForm.pas, examine o código para mostrar AbortForm logo após chamar TPrinter.BeginDoc( ).
Printer.BeginDoc;
AbortForm.Show;
Application.ProcessMessages;
Como AbortForm aparece como uma caixa de diálogo não modal, a chamada a Application.ProcessMessages garante que ele será desenhado corretamente antes que continue qualquer processamento da lógica
de impressão.
Para satisfazer a segunda condição, o teste de Printer.Aborted = True é realizado antes de se chamar
quaisquer métodos de TPrinter. A propriedade Aborted é definida como True quando o método Abort( ) é
chamado a partir de AbortForm. Como exemplo, antes de chamar Printer.TextRect, verifique Aborted =
True:
if not Printer.Aborted then
TextRect(OutRect, OutRect.Left, OutRect.Top, Items[i]);
Além disso, você não deverá chamar EndDoc( ) ou quaisquer rotinas de desenho de TPrinter.Canvas
depois de chamar Abort( ), pois a impressora terá sido efetivamente fechada.
Para satisfazer a terceira condição neste exemplo, while not Table.Eof também verifica se o valor de
Printer.Aborted é True, o que faz com que a execução saia do loop no qual a lógica de impressão está sendo
executada.
Imprimindo envelopes
O exemplo anterior mostrou um método para imprimir um relatório em colunas. Embora essa técnica
fosse um pouco mais complicada do que o envio de uma série de chamadas writeln( ) para a impressora,
em sua maior parte ela ainda é uma impressão linha a linha. A impressão de envelopes acrescenta novos
fatores que complicam as coisas um pouco mais e são comuns à maioria dos tipos de impressão que você
realiza no Windows. Primeiramente, os objetos (itens) que devem ser impressos precisam estar posicionados em algum local específico da superfície de impressão. Segundo, a métrica dos itens, ou unidades de
medida, pode ser completamente diferente da que é usada pela tela de desenho da impressora. Levando
em consideração esses dois fatores, a impressão se torna muito mais do que apenas imprimir uma linha e
acompanhar o espaço que você já usou para imprimir.
Este exemplo de impressão de envelope mostra um processo passo a passo, que pode ser usado para
a impressão de qualquer coisa. Lembre-se de que tudo o que é passado para a tela de desenho da impressora é desenhado dentro de algum retângulo delimitador na tela de desenho ou em pontos específicos da
tela da impressora.
137
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Imprimindo no abstrato
Pense na tarefa de impressão em um sentido mais abstrato por um momento. Em todas as ocasiões, duas
coisas são certas: você tem uma superfície na qual deseja imprimir e tem um ou mais elementos para representar nessa superfície. Dê uma olhada na Figura 10.2.
Plano B
Plano A
Plano C
FIGURA 10.2
Três planos.
Na Figura 10.2, Plano A é a sua superfície de destino. Os Planos B e C são os elementos que você deseja superpor (imprimir) no Plano A. Considere um sistema de coordenadas para cada plano, onde a unidade de medida aumenta enquanto você percorre o eixo X em direção ao leste e o eixo Y em direção ao
sul – quer dizer, a menos que você more na Austrália. A Figura 10.3 representa esse sistema de coordenadas. O resultado da combinação dos planos aparece na Figura 10.4.
FIGURA 10.3
O sistema de coordenadas dos Planos A, B e C.
138
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Plano B
Plano C
Plano A
FIGURA 10.4
Planos B e C superpostos ao Plano A.
Observe que os Planos B e C foram girados em 90 graus para se conseguir o resultado final. Até
aqui, isso não parece tão ruim. Se os seus planos são medidos usando a mesma unidade de medida, você
pode facilmente desenhar esses retângulos para conseguir o resultado final por meio de alguma geometria simples. Mas, e se eles não estiverem na mesma unidade de medida?
Suponha que o Plano A, representando uma superfície para a qual as medidas são dadas em pixels.
Suas dimensões são 2.550 x 3.300 pixels. O Plano B é medido em polegadas: 6 1/2 x 3 3/4 polegadas. Suponha que você não saiba as dimensões para o Plano C; no entanto, você sabe que ele é medido em pixels, e você saberá suas medidas mais tarde. Essas medidas são ilustradas na Figura 10.5.
2.550 pixels
6-½ in
3-¾ in
3.300
pixels
?
?
Pixels
FIGURA 10.5
Medidas nos planos.
Essa abstração ilustra o problema associado à impressão. Na verdade, ela ilustra a própria tarefa de
impressão de um envelope. O Plano A representa o tamanho da página de uma impressora de 300 dpi
(em 300 dpi, 8 1/2 X 11 polegadas é igual a 2.550 x 3.300 pixels). O Plano B representa o tamanho do
envelope em polegadas, e o Plano C representa o retângulo delimitador para o texto que compõe o endereço. Lembre-se, no entanto, de que essa abstração não está ligada apenas a envelopes. Os Planos B e C
podem representar componentes de TImage medidos em milímetros.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
139
Analisando essa tarefa em sua abstração, você consegue atingir os três primeiros passos para a impressão no Windows: identificar cada elemento a imprimir, identificar a unidade de medida para a superfície de destino e identificar as unidades de medida para cada elemento individual a ser desenhado na
superfície de destino.
Agora considere outra dificuldade. Quando você está imprimindo um envelope em uma posição
vertical, o texto precisa girar verticalmente.
Processo de impressão passo a passo
A lista a seguir resume o processo que você deve seguir no código quando estiver preparando a saída impressa:
1. Identifique cada elemento a ser impresso na superfície de destino.
2. Identifique a unidade de medida para a superfície de destino ou tela de desenho da impressora.
3. Identifique as unidades de medida para cada elemento individual a ser desenhado na superfície de
destino.
4. Decida sobre a unidade de medida comum com a qual você realizará todas as rotinas de desenho.
Quase sempre, isso significará as unidades da tela – pixels.
5. Escreva as rotinas de tradução para converter as outras unidades de medida para a unidade de medida comum.
6. Escreva as rotinas para calcular o tamanho de cada elemento a ser impresso na unidade de medida
comum. Em Object Pascal, isso pode ser representado por uma estrutura TPoint. Lembre-se das dependências de outros valores. Por exemplo, o retângulo delimitador do endereço depende da posição do envelope. Portanto, os dados do envelope precisam ser calculados em primeiro lugar.
7. Escreva as rotinas para calcular a posição de cada elemento conforme aparecerá na tela de desenho
da impressora, com base no sistema de coordenadas dessa tela e nos tamanhos obtidos na etapa 6.
Em Object Pascal, isso pode ser representado por uma estrutura TRect. Novamente, lembre-se das
dependências.
8. Escreva a função de impressão, usando os dados reunidos nas etapas anteriores, para posicionar os
itens na superfície impressa.
NOTA
As etapas 5 e 6 podem ser obtidas usando-se uma técnica para realizar todo o desenho em um modo de
mapeamento específico. Os modos de mapeamento são discutidos no Capítulo 8.
Partindo para o trabalho
Com esse processo passo a passo, sua tarefa de impressão de um envelope deverá estar muito mais clara.
Você verá isso no projeto de impressão de envelope. O primeiro passo é identificar os elementos a imprimir ou representar. Os elementos para o exemplo de envelope são o próprio envelope e o endereço.
Neste exemplo, você aprenderá a imprimir dois tamanhos de envelope: um tamanho 10 e um tamanho 6 3/4.
O registro a seguir contém os tamanhos do envelope:
type
TEnvelope = record
Kind: string;
// Armazena o nome do tipo de envelope
Width: double; // Mantém a largura do envelope
Height: double; // Mantém a altura do envelope
end;
140
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
const
// Esse array de constantes armazena tipos de envelope
EnvArray: array[1..2] of TEnvelope =
((Kind:’Size 10’;Width:9.5;Height:4.125),
// 9-1/2 x 4-1/8
(Kind:’Size 6-3/4’;Width:6.5;Height:3.625)); // 6-1/2 x 3-3/4
As etapas 2 e 3 foram cobertas: você sabe que a superfície de destino é TPrinter.Canvas, que é representada em pixels. Os envelopes são representados em polegadas, e o endereço é representado em pixels.
A etapa 4 exige que você selecione uma unidade de medida comum. Para este projeto, você usará pixels
como unidade de medida comum.
Para a etapa 5, as únicas unidades que você precisa converter são de polegadas para pixels. A função
GetDeviceCaps( ) da API do Win32 pode retornar a quantidade de pixels por uma polegada no eixo horizontal e vertical de Printer.Canvas:
PixPerInX := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
PixPerInY := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
Para converter o tamanho do envelope em pixels, basta multiplicar o número de polegadas por Pixou PxPerInY para obter as medidas horizontal e vertical em pixels:
PerInX
EnvelopeWidthInPixels := trunc(EnvelopeWidthValue * PixPerInX);
EnvelopeHeigthInPixels := trunc(EnvelopeHeghtValue * PixPerInY);
Como a largura ou a altura do envelope pode ser um valor fracionário, é preciso usar a função
para retornar a parte inteira do tipo de ponto flutuante.
O mesmo projeto demonstra como você implementaria as etapas 6 e 7. O formulário principal do
projeto aparece na Figura 10.6; a Listagem 10.4 mostra o código-fonte para o projeto de impressão do
envelope.
Trunc( )
FIGURA 10.6
O formulário principal para a demonstração do envelope.
Listagem 10.4 Demonstração de impressão de envelope
unit MainFrm;
interface
uses
141
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.4 Continuação
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, printers, StdCtrls, ExtCtrls, Menus, ComCtrls;
type
TEnvelope = record
Kind: string;
// Armazena o nome do tipo de envelope
Width: double; // Mantém a largura do envelope
Height: double; // Mantém a altura do envelope
end;
const
// Esse array de constantes armazena tipos de envelope
EnvArray: array[1..2] of TEnvelope =
((Kind:’Size 10’;Width:9.5;Height:4.125),
// 9-1/2 x 4-1/8
(Kind:’Size 6-3/4’;Width:6.5;Height:3.625)); // 6-1/2 x 3-3/4
type
// Esse array de tipo enumerado representa posições de impressão.
TFeedType = (epLHorz, epLVert, epRHorz, epRVert);
TPrintPrevPanel = class(TPanel)
public
property Canvas; // Publica a propriedade Canvas
end;
142
TMainForm = class(TForm)
gbEnvelopeSize: TGroupBox;
rbSize10: TRadioButton;
rbSize6: TRadioButton;
mmMain: TMainMenu;
mmiPrintIt: TMenuItem;
lblAdressee: TLabel;
edtName: TEdit;
edtStreet: TEdit;
edtCityState: TEdit;
rgFeedType: TRadioGroup;
PrintDialog: TPrintDialog;
procedure FormCreate(Sender: TObject);
procedure rgFeedTypeClick(Sender: TObject);
procedure mmiPrintItClick(Sender: TObject);
private
PrintPrev: TPrintPrevPanel; // Imprime painel de prévia
EnvSize: TPoint;
// Armazena o tamanho do envelope
EnvPos: TRect;
// Armazena a posição do envelope
ToAddrPos: TRect;
// Armazena a posição do endereço
FeedType: TFeedType; // Armazena o tipo de origem do papel de TEnvPosition
function GetEnvelopeSize: TPoint;
function GetEnvelopePos: TRect;
function GetToAddrSize: TPoint;
function GetToAddrPos: TRect;
procedure DrawIt;
procedure RotatePrintFont;
procedure SetCopies(Copies: Integer);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.4 Continuação
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
function TMainForm.GetEnvelopeSize: TPoint;
// Apanha o tamanho do envelope representado por um TPoint
var
EnvW, EnvH: integer;
PixPerInX,
PixPerInY: integer;
begin
// Pixels por polegada no eixo horizontal
PixPerInX := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
// Pixels per inch along the vertical axis
PixPerInY := GetDeviceCaps(Printer.Handle, LOGPIXELSY);
// Tamanho do envelope difere, dependendo da seleção do usuário
if RBSize10.Checked then
begin
EnvW := trunc(EnvArray[1].Width * PixPerInX);
EnvH := trunc(EnvArray[1].Height * PixPerInY);
end
else begin
EnvW := trunc(EnvArray[2].Width * PixPerInX);
EnvH := trunc(EnvArray[2].Height * PixPerInY);
end;
// retorna Result como um registro TPoint
Result := Point(EnvW, EnvH)
end;
function TMainForm.GetEnvelopePos: TRect;
{ Retorna a posição do envelope relativa ao seu tipo de origem.
A função requer a inicialização da variável EnvSize. }
begin
// Determina tipo de origem com base na seleção do usuário.
FeedType := TFeedType(rgFeedType.ItemIndex);
{ Retorna uma estrutura TRect indicando a posição do envelope
conforme ele é ejetado da impressora. }
case FeedType of
epLHorz:
Result := Rect(0, 0, EnvSize.X, EnvSize.Y);
epLVert:
Result := Rect(0, 0, EnvSize.Y, EnvSize.X);
epRHorz:
Result := Rect(Printer.PageWidth - EnvSize.X, 0, Printer.PageWidth, EnvSize.Y);
epRVert:
Result := Rect(Printer.PageWidth - EnvSize.Y, 0, Printer.PageWidth, EnvSize.X);
end; // Case
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
143
Listagem 10.4 Continuação
function MaxLn(V1, V2: Integer): Integer;
// Retorna o maior dos dois. Se for igual, retorna o primeiro
begin
Result := V1;
// Resultado padrão em V1 }
if V1 < V2 then
Result := V2
end;
function TMainForm.GetToAddrSize: TPoint;
var
TempPoint: TPoint;
begin
// Calcula o tamanho da linha maior usando a função MaxLn( )
TempPoint.x := Printer.Canvas.TextWidth(edtName.Text);
TempPoint.x := MaxLn(TempPoint.x, Printer.Canvas.TextWidth(edtStreet.Text));
TempPoint.x := MaxLn(TempPoint.x, Printer.Canvas.TextWidth(edtCityState.Text))+10;
// Calcula a altura de todas as linhas de endereço
TempPoint.y := Printer.Canvas.TextHeight(edtName.Text)+
Printer.Canvas.TextHeight(edtStreet.Text)+
Printer.Canvas.TextHeight(edtCityState.Text)+10;
Result := TempPoint;
end;
144
function TMainForm.GetToAddrPos: TRect;
// A função exige a inicialização de EnvSize e EnvPos
Var
TempSize: TPoint;
LT, RB: TPoint;
begin
// Determina o tamanho do retângulo delimitador de Address
TempSize := GetToAddrSize;
{ Calcula os dois pontos, um representando a posição superior esquerda (LT)
e outro representando a posição inferior direita(RB) do retângulo
delimitador do endereço. Isso depende de FeedType. }
case FeedType of
epLHorz:
begin
LT := Point((EnvSize.x div 2) - (TempSize.x div 2),
((EnvSize.y div 2) - (TempSize.y div 2)));
RB := Point(LT.x + TempSize.x, LT.y + TempSize.Y);
end;
epLVert:
begin
LT := Point((EnvSize.y div 2) - (TempSize.y div 2),
((EnvSize.x div 2) - (TempSize.x div 2)));
RB := Point(LT.x + TempSize.y, LT.y + TempSize.x);
end;
epRHorz:
begin
LT := Point((EnvSize.x div 2) - (TempSize.x div 2) + EnvPos.Left,
((EnvSize.y div 2) - (TempSize.y div 2)));
RB := Point(LT.x + TempSize.x, LT.y + TempSize.Y);
end;
epRVert:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.4 Continuação
begin
LT := Point((EnvSize.y div 2) - (TempSize.y div 2) + EnvPos.Left,
((EnvSize.x div 2) - (TempSize.x div 2)));
RB := Point(LT.x + TempSize.y, LT.y + TempSize.x);
end;
end; // End Case
Result := Rect(LT.x, LT.y, RB.x, RB.y);
end;
procedure TMainForm.DrawIt;
// Este procedimento considera que EnvPos e EnvSize foram inicializados
begin
PrintPrev.Invalidate;
// Apaga conteúdo de Panel
PrintPrev.Update;
// Define o modo de mapeamento para o painel em MM_ISOTROPIC
SetMapMode(PrintPrev.Canvas.Handle, MM_ISOTROPIC);
// Define a extensão de TPanel para corresponder aos limites da impressora.
SetWindowExtEx(PrintPrev.Canvas.Handle,
Printer.PageWidth, Printer.PageHeight, nil);
// Define a extensão da viewport para o tamanho de TPanel de PrintPrev.
SetViewPortExtEx(PrintPrev.Canvas.Handle,
PrintPrev.Width, PrintPrev.Height, nil);
// Define a origem para a posição em 0, 0
SetViewportOrgEx(PrintPrev.Canvas.Handle, 0, 0, nil);
PrintPrev.Brush.Style := bsSolid;
with EnvPos do
// Desenha um retângulo para representar um envelope
PrintPrev.Canvas.Rectangle(Left, Top, Right, Bottom);
with ToAddrPos, PrintPrev.Canvas do
case FeedType of
epLHorz, epRHorz:
begin
Rectangle(Left, Top, Right, Top+2);
Rectangle(Left, Top+(Bottom-Top) div 2, Right, Top+(Bottom-Top) div 2+2);
Rectangle(Left, Bottom, Right, Bottom+2);
end;
epLVert, epRVert:
begin
Rectangle(Left, Top, Left+2, Bottom);
Rectangle(Left + (Right-Left)div 2, Top, Left + (Right-Left)div 2+2, Bottom);
Rectangle(Right, Top, Right+2, Bottom);
end;
end; // case
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
Ratio: double;
begin
// Calcula a razão entre PageWidth e PageHeight
Ratio := Printer.PageHeight / Printer.PageWidth;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
145
Listagem 10.4 Continuação
// Cria uma nova instância de TPanel
with TPanel.Create(self) do
begin
SetBounds(15, 15, 203, trunc(203*Ratio));
Color := clBlack;
BevelInner := bvNone;
BevelOuter := bvNone;
Parent := self;
end;
// Cria um painel de prévia de Print
PrintPrev := TPrintPrevPanel.Create(self);
with PrintPrev do
begin
SetBounds(10, 10, 200, trunc(200*Ratio));
Color := clWhite;
BevelInner := bvNone;
BevelOuter := bvNone;
BorderStyle := bsSingle;
Parent := self;
end;
end;
procedure TMainForm.rgFeedTypeClick(Sender: TObject);
begin
EnvSize := GetEnvelopeSize;
EnvPos := GetEnvelopePos;
ToAddrPos := GetToAddrPos;
DrawIt;
end;
procedure TMainForm.SetCopies(Copies: Integer);
var
ADevice, ADriver, APort: String;
ADeviceMode: THandle;
DevMode: PDeviceMode;
begin
SetLength(ADevice, 255);
SetLength(ADriver, 255);
SetLength(APort, 255);
{ Se ADeviceMode é zero, não há um driver de impressora carregado.
Portanto, a definição de PrinterIndex força o driver a ser carregado. }
if ADeviceMode = 0 then
begin
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(PChar(ADevice), PChar(ADriver), PChar(APort), ADeviceMode);
end;
146
if ADeviceMode < > 0 then
begin
DevMode := GlobalLock(ADeviceMode);
try
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.4 Continuação
DevMode^.dmFields := DevMode^.dmFields or DM_Copies;
DevMode^.dmCopies := Copies;
finally
GlobalUnlock(ADeviceMode);
end;
end
else
raise Exception.Create(‘Could not set printer copies’);
end;
procedure TMainForm.mmiPrintItClick(Sender: TObject);
var
TempHeight: integer;
SaveFont: TFont;
begin
if PrintDialog.Execute then
begin
// Define o número de cópias a imprimir
SetCopies(PrintDialog.Copies);
Printer.BeginDoc;
try
// Calcula uma altura de linha temporária
TempHeight := Printer.Canvas.TextHeight(edtName.Text);
with ToAddrPos do
begin
{ Ao imprimir verticalmente, gira a fonte de modo que
pinte em um ângulo de 90 graus. }
if (FeedType = eplVert) or (FeedType = epRVert) then
begin
SaveFont := TFont.Create;
try
// Salva a fonte original
SaveFont.Assign(Printer.Canvas.Font);
RotatePrintFont;
// Escreve as linhas de endereço na tela de desenho da impressora
Printer.Canvas.TextOut(Left, Bottom, edtName.Text);
Printer.Canvas.TextOut(Left+TempHeight+2, Bottom, edtStreet.Text);
Printer.Canvas.TextOut(Left+TempHeight*2+2, Bottom, edtCityState.Text);
// Restaura a fonte original
Printer.Canvas.Font.Assign(SaveFont);
finally
SaveFont.Free;
end;
end
else begin
{ Se o envelope não é impresso verticalmente,
só imprime as linhas de endereço normalmente. }
Printer.Canvas.TextOut(Left, Top, edtName.Text);
Printer.Canvas.TextOut(Left, Top+TempHeight+2, edtStreet.Text);
Printer.Canvas.TextOut(Left, Top+TempHeight*2+2, edtCityState.Text);
end;
end;
finally
Printer.EndDoc;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
147
Listagem 10.4 Continuação
end;
end;
procedure TMainForm.RotatePrintFont;
var
LogFont: TLogFont;
begin
with Printer.Canvas do
begin
with LogFont do
begin
lfHeight := Font.Height; // Define em Printer.Canvas.font.height
lfWidth := 0;
// o mapeador de fonte escolhe a largura
lfEscapement := 900;
// décimos de graus, portanto 900 = 90 graus
lfOrientation := lfEscapement; // Sempre define o valor de lfEscapement
lfWeight := FW_NORMAL;
// default
lfItalic := 0;
// sem itálico
lfUnderline := 0;
// sem sublinhado
lfStrikeOut := 0;
// sem tachado
lfCharSet := ANSI_CHARSET; // default
StrPCopy(lfFaceName, Font.Name); // nome da fonte de Printer.Canvas
lfQuality := PROOF_QUALITY;
lfOutPrecision := OUT_TT_ONLY_PRECIS;
// força fontes TrueType
lfClipPrecision := CLIP_DEFAULT_PRECIS; // default
lfPitchAndFamily := Variable_Pitch;
// default
end;
end;
Printer.Canvas.Font.Handle := CreateFontIndirect(LogFont);
end;
end.
Quando o usuário dá um clique em um dos botões de opção em gbEnvelopeSize ou gbFeedType, o manipulador de evento FeedTypeClick( ) é chamado. Esse manipulador de evento chama as rotinas para calcular o tamanho e a posição do envelope com base nas opções do botão de opção.
O tamanho e a posição do retângulo também são calculados nesses manipuladores de evento. A largura desse retângulo é baseada na maior largura de texto em cada um dos três componentes TEdit. A altura do retângulo consiste na altura combinada dos três componentes TEdit.
Todos os cálculos são baseados nos pixels de Printer.Canvas. mmiPrintItClick( ) contém a lógica para
imprimir o envelope com base nas escolhas selecionadas. Há também uma lógica adicional, responsável
por tratar da rotação da fonte quando o envelope é posicionado verticalmente. Além disso, uma pseudoprévia de impressão é criada no manipulador de evento FormCreate( ). Essa prévia de impressão é atualizada quando o usuário seleciona os botões de opção.
O tipo enumerado TFeedType representa cada posição do envelope, conforme ele sai da impressora:
TFeedType = (epLHorz, epLVert, epRHorz, epRVert);
148
TMainForm declara os métodos GetEnvelopeSize( ), GetEnvelopePos( ), GetToAddrSize( ) e GetToAddrPos( ) para determinar as várias medidas para os elementos a serem impressos, conforme especificado nas etapas 6 e 7 do modelo deste capítulo.
Em GetEnvelopeSize( ), a função GetDeviceCaps( ) é usada para converter o tamanho do envelope de
polegadas para pixels, com base na seleção de gbEnvelopeSize. GetEnvelopePos( ) determina a posição do envelope em TPrinter.Canvas, com base no sistema de coordenadas de Printer.Canvas.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
calcula o tamanho do retângulo delimitador do endereço, com base na medida do
texto contido nos três componentes TEdit. Aqui, os métodos TextHeight( ) e TextWidth( ) de Printer.Canvas
são usados para determinar esses tamanhos. A função MaxLn( ) é uma função auxiliadora usada para determinar a maior linha de texto dos três componentes TEdit, que é usada como largura do retângulo.
Você também pode usar a função Max( ) da unidade Math.pas para determinar a maior linha de texto.
GetToAddrPos( ) chama GetToAddrSize( ) e usa o valor retornado para calcular a posição do retângulo
delimitador do endereço em Printer.Canvas. Observe que o tamanho e o posicionamento do envelope são
necessários para que essa função posicione o retângulo de endereço corretamente.
O manipulador de evento mmiPrintItClick( ) realiza a lógica de impressão propriamente dita. Primeiro, ele inicializa a impressão com o método BeginDoc( ). Depois calcula uma altura de linha temporária
usada para o posicionamento do texto. Ele determina o TFeedType e, se for um dos tipos verticais, salva a
fonte da impressora e chama o método RotatePrintFont( ), que gira a fonte em 90 graus. Quando ele retorna de RotatePrintFont( ), restaura a fonte original de Printer.Canvas. Se o TFeedType for um dos tipos horizontais, ele realiza as chamadas TextOut( ) para imprimir o endereço. Finalmente, mmiPrintItClick( ) encerra a impressão com o método EndDoc( ).
RotatePrintFont( ) cria uma estrutura TLogFont e inicializa seus diversos valores obtidos de Printer.Canvas e outros valores padrão. Observe a atribuição para seu membro lfEscapement. Se você se lembra
do Capítulo 8, lfEscapement especifica um ângulo em décimos de graus em que a fonte deverá ser desenhada. Aqui, você especifica para imprimir a fonte em um ângulo de 90 graus atribuindo 900 a lfEscapement.
Uma coisa a observar aqui é que somente fontes TrueType podem ser giradas.
GetToAddrSize( )
Uma prévia de impressão simples
Normalmente, uma boa maneira de ajudar seus usuários a não cometerem um erro escolhendo a seleção
errada é permitir que vejam como será a saída impressa antes da própria impressão. O projeto desta seção contém um painel de visualização (prévia) da impressão. Você fez isso construindo uma classe descendente de TPanel e publicando sua propriedade Canvas:
TPrintPrevPanel = class(TPanel)
public
property Canvas;
// Publica esta propriedade
end;
O manipulador de evento FormCreate( ) realiza a lógica para instanciar um TPrintPrevPanel. A linha a
seguir determina a relação entre a largura e a altura de Printer:
Ratio := Printer.PageHeight / Printer.PageWidth;
Essa relação é usada para calcular a largura e a altura para a instância de TPrintPrevPanel.
No entanto, antes que TPrintPrevPanel seja criado, criamos um TPanel normal com uma cor preta para
servir como sombra para PrintPrev, a instância de TPrintPrevPanel. Seus limites são ajustados de modo que
fiquem ligeiramente à direita e abaixo dos limites de PrintPrev. O efeito é que PrintPrev recebe uma aparência tridimensional, com uma sombra por trás. PrintPrev é usado principalmente para mostrar como o
envelope seria impresso. A rotina DrawIt( ) realiza essa lógica.
TEnvPrintForm.DrawIt( ) chama PrintPrev.Invalidade para apagar seu conteúdo anterior. Depois ela
chama PrintPrev.Update( ) para garantir que a mensagem de pintura será processada antes da execução do
restante do código. Em seguida ela define o modo de mapeamento de PrintPrev como MM_ISOTROPIC para
permitir que aceite extensões arbitrárias junto com os eixos X e Y. SetWindowExt( ) define as extensões das
janelas de PrintPrev como as de Printer.Canvas, e SetViewPortExt( ) define as extensões da viewport de
PrintPrev como sua própria altura e largura (veja no Capítulo 8 uma discussão sobre os modos de mapeamento).
Isso permite que DrawIt( ) utilize os mesmos valores métricos para Printer.Canvas, o envelope, o retângulo de endereço e o painel PrintPrev. Essa rotina também usa retângulos para representar linhas de
texto. O efeito aparece na Figura 10.7.
149
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 10.7
Um formulário de impressão de envelope com um recurso para visualizar a impressão.
NOTA
Uma visualização de impressão alternativa e melhor pode ser criada com metafiles. Crie o metafile usando
a alça da impressora como dispositivo de referência; depois desenhe na tela do metafile conforme faria
com a tela da impressora; depois desenhe o metafile na tela. Assim, não é preciso redimensionar nem mexer com a extensão da viewport.
Tarefas de impressão diversas
Ocasionalmente, você precisará realizar uma tarefa de impressão que não está disponível por meio do
objeto TPrinter, como especificar a qualidade do seu trabalho de impressão. Para realizar tais tarefas,
você precisa lançar mão do método da API do Win32. Entretanto, isso não é tão difícil. Primeiro, você
precisa entender a estrutura TDeviceMode. A próxima seção discute isso. As outras seções mostram como
usar essa estrutura para realizar essas diversas tarefas de impressão.
A estrutura TDeviceMode
A estrutura TDeviceMode contém informações sobre dados de inicialização e ambiente de um driver de impressora. Os programadores usam essa estrutura para definir ou apanhar informações a respeito de vários atributos da impressora atual. Essa estrutura é definida no arquivo Windows.pas.
Você encontrará definições para cada um dos campos na ajuda on-line do Delphi. As próximas seções abordam alguns dos campos mais comuns dessa estrutura, mas seria uma boa idéia dar uma olhada
na ajuda on-line e ver para que são usados alguns dos outros campos. Em alguns casos, você pode ter de
se referir a esses campos, e alguns deles são usados de modo diferente no Windows NT/2000 e no Windows 95/98.
Para obter um ponteiro para a estrutura TDeviceMode da impressora ativa, você pode primeiro usar
TPrinter.GetPrinter( ) para obter uma alça para o bloco de memória que a estrutura ocupa. Depois use a
função GlobalLock( ) para recuperar um ponteiro para essa estrutura. A Listagem 10.5 ilustra como obter
o ponteiro para a estrutura TDeviceMode.
150
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.5 Obtendo um ponteiro para uma estrutura TDeviceMode
var
ADevice, ADriver, APort: array [0..255] of Char;
DeviceHandle: THandle;
DevMode: PDeviceMode; // Um ponteiro para uma estrutura TDeviceMode
begin
{ Primeiro apanha uma alça para a estrutura DeviceMode de TPritner }
Printer.GetPrinter(ADevice, ADriver, APort, DeviceHandle);
{ Se DeviceHandle ainda for 0, o driver não foi carregado. Define o
índice da impressora para forçar o driver de impressora a carregar,
tornando a alça disponível. }
if DeviceHandle = 0 then
begin
Printer.PrinterIndex := Printer.PrinterIndex;
Printer.GetPrinter(ADevice, ADriver, APort, DeviceHandle);
end;
{ Se DeviceHadle ainda é 0, então houve um erro. Caso contrário,
usa GlobalLock( ) para obter um ponteiro para a estrutura TDeviceMode }
if DeviceHandle = 0 then
Raise Exception.Create(‘Could Not Initialize TDeviceMode structure’)
else
DevMode := GlobalLock(DeviceHandle);
{ O código para usar a estrutura DevMode entra aqui }
{ !!! }
if not DeviceHandle = 0 then
GlobalUnlock(DeviceHandle);
end;
Os comentários da listagem anterior explicam as etapas necessárias para obter o ponteiro para a estrutura TDeviceMode. Depois que você tiver obtido esse ponteiro, poderá realizar várias rotinas de impressora, conforme ilustrado na próximas seções. Entretanto, observe primeiro este comentário da listagem
anterior:
{ O código para usar a estrutura DevMode entra aqui }
{ !!! }
É aqui que você coloca os exemplos de código a seguir.
Entretanto, antes de inicializar qualquer um dos membros da estrutura TDeviceMode, você deve especificar qual membro você está inicializando, marcando o bit apropriado nos flags de bit de dmFields. A Tabela 10.3 lista os vários flags de bit de dmFields e também especifica a que membro de TDeviceMode eles pertencem.
Tabela 10.3 Flags de bit de TDeviceMode.dmFields
Valor de dmField
Campo correspondente
DM_ORIENTATION
dmOrientation
DM_PAPERSIZE
dmPaperSize
DM_PAPERLENGTH
dmPaperLength
DM_PAPERWIDTH
dmPaperWidth
DM_SCALE
dmScale
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
151
Tabela 10.3 Continuação
Valor de dmField
Campo correspondente
DM_COPIES
dmCopies
DM_DEFAULTSOURCE
dmDefaultSource
DM_PRINTQUALITY
dmPrintQuality
DM_COLOR
dmColor
DM_DUPLEX
dmDuplex
DM_YRESOLUTION
dmYResolution
DM_TTOPTION
dmTTOption
DM_COLLATE
dmCollate
DM_FORMNAME
dmFormName
DM_LOGPIXELS
dmLogPixels
DM_BITSPERPEL
dmBitsPerPel
DM_PELSWIDTH
dmPelsWidth
DM_PELSHEIGHT
dmPelsHeight
DM_DISPLAYFLAGS
dmDisplayFlags
DM_DISPLAYFREQUENCY
dmDispayFrequency
DM_ICMMETHOD
dmICMMethod (apenas Windows 95)
DM_ICMINTENT
dmICMIntent (apenas Windows 95)
DM_MEDIATYPE
dmMediaType (apenas Windows 95)
DM_DITHERTYPE
dmDitherType (apenas Windows 95)
Nos exemplos a seguir, você verá como definir o flag de bit apropriado, além do membro de TDevicorrespondente.
ceMode
Especificando o número de cópias a imprimir
Você pode informar a um trabalho de impressão quantas cópias devem ser impressas, especificando o
número de cópias no campo dmCopies da estrutura TDeviceMode. O código a seguir ilustra como fazer isso:
with DevMode^ do
begin
dmFields := dmFields or DM_COPIES;
dmCopies := Copies;
end;
Primeiramente, você deverá definir o flag de bit apropriado do campo dmFields para indicar qual
membro da estrutura TDeviceMode foi inicializado. O código anterior é o que você inseriria no código da
Listagem 10.6, onde foi especificado. Depois, sempre que você iniciar seu trabalho de impressão, o número de cópias especificado deverá ser enviado para a impressora. Vale a pena mencionar que, embora
esse exemplo ilustre como definir as cópias a imprimir usando a estrutura TDeviceMode, a propriedade
TPrinter.Copies faz o mesmo.
152
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Especificando a orientação da impressora
A especificação da orientação da impressora é semelhante à especificação de cópias, exceto que você inicializa uma estrutura TDeviceMode diferente:
with DevMode^ do
begin
dmFields := dmFields or DM_ORIENTATION;
dmOrientation := DMORIENT_LANDSCAPE;
end;
As duas opções para dmOrientation são DMORIENT_LANDSCAPE e DMORIENT_PORTRAIT.
Especificando o comprimento do papel
Para especificar um tamanho de papel, você inicializa o membro dmPaperSize de TDeviceMode:
with DevMode^ do
begin
dmFields := dmFields or DM_PAPERSIZE;
dmPaperSize := DMPAPER_LETTER; //
end;
Existem diversos valores predefinidos para o membro dmPaperSize, que você poderá encontrar na ajuda on-line sob TDeviceMode. O membro dmPaperSize pode ser definido como zero se o tamanho do papel
for especificado pelos membros dmPaperWidth e dmPaperHeight.
Especificando o tamanho do papel
Você pode especificar o tamanho do papel em décimos de milímetro para a saída impressa definindo o
campo dmPaperLength. Isso substitui quaisquer definições aplicadas no campo dmPaperSize. O código a seguir ilustra a definição do tamanho do papel:
with DevMode^ do
begin
dmFields := dmFields or DM_PAPERLENGTH;
dmPaperLength := SomeLength;
end;
Especificando a largura do papel
A largura do papel também é especificada em décimos de milímetro. Para definir a largura do papel, você
precisa inicializar o campo dmPaperWidth da estrutura TDeviceMode. O código a seguir ilustra essa definição:
with DevMode^ do
begin
dmFields := dmFields or DM_PAPERWIDTH;
dmPaperWidth := SomeWidth;
end;
Isso também substitui as configurações para o campo dmPaperSize.
153
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Especificando a escala de impressão
A escala de impressão é o fator pelo qual a saída impressa é dimensionada. Portanto, o tamanho da página resultante é dimensionado a partir do tamanho físico da página por um fator de TDeviceMode.dmScale dividido por 100. Portanto, para encurtar a saída impressa (gráficos e texto) pela metade do seu tamanho
original, você atribuiria o valor 50 ao campo dmScale. O código a seguir ilustra como definir a escala de
impressão:
with DevMode^ do
begin
dmFields := dmFields or DM_SCALE;
dmScale := 50;
end;
Especificando a cor de impressão
Para impressoras que aceitam impressão colorida, você pode especificar se a impressora deve apresentar
impressão em cores ou monocromática, inicializando o campo dmColor, como vemos aqui:
with DevMode^ do
begin
dmFields := dmFields or DM_COLOR;
dmColor := DMCOLOR_COLOR;
end;
O outro valor que pode ser atribuído ao campo dmColor é DMCOLOR_MONOCHROME.
Especificando a comprimento de impressão
Qualidade de impressão é a resolução em que a impressora imprime sua saída. Existem quatro valores
predefinidos para a configuração da qualidade de impressão, como vemos na lista a seguir:
l
DMRES_HIGH.
Impressão com resolução alta.
l
DMRES_MEDIUM.
l
DMRES_LOW.
l
DMRES_DRAFT.
Impressão com resolução média.
Impressão com resolução baixa.
Impressão com resolução de rascunho.
Para alterar a qualidade da impressão, você inicializa o campo dmPrintQuality da estrutura TDeviceMode:
with DevMode^ do
begin
dmFields := dmFields or DM_PRINTQUALITY;
dmPrintQuality := DMRES_DRAFT;
end;
Especificando a impressão duplex
Algumas impressoras são capazes de realizar impressão duplex – imprimir nos dois lados do papel. Você
pode dizer à impressora para realizar a impressão nos dois lados inicializando o campo dmDuplex da estrutura TDeviceMode para um destes valores:
154
l
DMDUP_SIMPLEX
l
DMDUP_HORIZONTAL
l
DMDUP_VERTICAL
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Veja o exemplo a seguir:
with DevMode^ do
begin
dmFields := dmFields or DM_DUPLEX;
dmDuplex := DMDPU_HORIZONTAL;
end;
Alterando a impressora padrão
Embora seja possível alterar a impressora padrão ativando a pasta da impressora, você pode querer mudar a impressora padrão em runtime. Isso é possível, conforme ilustramos no projeto de exemplo que
aparece na Listagem 10.6.
Listagem 10.6 alterando a impressora padrão
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TMainForm = class(TForm)
cbPrinters: TComboBox;
lblPrinter: TLabel;
procedure FormCreate(Sender: TObject);
procedure cbPrintersChange(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
uses IniFiles, Printers;
{$R *.DFM}
procedure TMainForm.FormCreate(Sender: TObject);
begin
{ Copia os nomes de impressora para a cx de combinação e define essa
caixa para mostrar a impressora padrão selecionada atualmente }
cbPrinters.Items.Assign(Printer.Printers);
cbPrinters.Text := Printer.Printers[Printer.PrinterIndex];
// Atualiza o label para refletir a impressora padrão
lblPrinter.Caption := Printer.Printers[Printer.PrinterIndex];
end;
155
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.6 Continuação
procedure TMainForm.cbPrintersChange(Sender: TObject);
var
IniFile: TIniFile;
TempStr1, TempStr2: String;
S: array[0..64] of char;
begin
with Printer do
begin
// Define nova impressora com base na seleção da cx de combinação
PrinterIndex := cbPrinters.ItemIndex;
// Armazena o nome da impressora em uma string temporária
TempStr1 := Printers[PrinterIndex];
// Apaga a parte desnecessária do nome da impressora
System.Delete(TempStr1, Pos(‘ on ‘, TempStr1), Length(TempStr1));
// Cria uma classe TIniFile
IniFile := TIniFile.Create(‘WIN.INI’);
try
// Apanha o nome de dispositivo da impressora selecionada
TempStr2 := IniFile.ReadString(‘Devices’, TempStr1, ‘’);
// Muda a impressora padrão para aquela escolhida pelo usuário
IniFile.WriteString(‘windows’, ‘device’, TempStr1 + ‘,’ + TempStr2);
// Diz a todas as janelas que a impressora padrão mudou.
StrCopy(S, ‘windows’);
SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, LongInt(@S));
finally
IniFile.Free;
end;
end;
// Atualiza o label para refletir a nova selecoa de impressora
lblPrinter.Caption := Printer.Printers[Printer.PrinterIndex];
end;
end.
O projeto anterior consiste em um formulário principal com um componente TComboBox e um componente TLabel. Na criação do formulário, o componente TComboBox é inicializado com a lista de strings de
nomes de impressora, obtida da propriedade Printer.Printers. O componente TLabel é atualizado para refletir a impressora atualmente selecionada. O manipulador de evento cbPrintersChange( ) é onde colocamos o código para modificar a impressora padrão para todo o sistema. Isso ocasiona a mudança do item
[device] na seção [windows] do arquivo WIN.INI, localizado no diretório do Windows. Os comentários no
código anterior continuam explicando o processo que realiza essas modificações.
Como obter informações da impressora
156
Esta seção ilustra como você pode apanhar informações sobre um dispositivo de impressão, como suas
características físicas (número de bandejas, tamanhos de papel aceitos etc.), além dos recursos da impressora para desenhar texto e gráficos.
Você pode precisar de informações sobre uma impressora em particular por vários motivos. Por
exemplo, você precisa saber se a impressora aceita um determinado recurso. Um exemplo comum é determinar se a impressora ativa aceita o uso de banding (faixas). Banding é um processo que pode melhorar a velocidade de impressão e os requisitos de espaço em disco para impressoras com memória limita-
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
da. Para usar o banding, você precisa fazer chamadas à API específicas desse recurso. Em uma impressora
que não aceita o recurso, essas chamadas não funcionariam. Portanto, você pode primeiro determinar se
a impressora aceitará o uso de banding (e usá-lo, se possível); caso contrário, você poderá evitar as chamadas à API relacionadas ao banding.
GetDeviceCaps( ) e DeviceCapabilities( )
A função GetDeviceCaps( ) da API do Win32 permite obter informações sobre dispositivos como impressoras, plotadoras, telas etc. Em geral, esses são dispositivos que possuem um contexto de dispositivo.
Você usa GetDeviceCaps( ) informando-lhe uma alça para o contexto de dispositivo e um índice que especifica as informações que você deseja recuperar.
DeviceCapabilities( ) é específico para impressoras. Na verdade, as informações obtidas de DeviceCapabilities( ) são fornecidas pelo driver de uma impressora especificada. Use DeviceCapabilities( ) fornecendo-lhe strings de identificação do dispositivo de impressão e também um índice que especifica os dados que você deseja obter. Às vezes, são necessárias duas chamadas a DeviceCapabilities( ) para apanhar
certos dados. A primeira delas é feita para determinar quanta memória você precisa alocar para os dados
a serem recuperados. A segunda chamada armazena os dados no bloco de memória que você alocou. Esta
seção explica como fazer isso.
Uma coisa que você precisa saber é que a maior parte dos recursos de desenho que não são aceitos
por uma determinada impressora ainda funcionarão se você os utilizar. Por exemplo, quando GetDeviceCaps( ) ou DeviceCapabilities( ) indica que BitBlt( ), StretchBlt( ) ou a impressão de fontes TrueType não
é aceita, você ainda poderá usar qualquer uma dessas funções; a GDI simulará essas funções para você.
Observe, no entanto, que a GDI não pode simular BitBlt( ) em um dispositivo que não aceita pixels com
varredura por rastreio; BitBlt( ) sempre falhará em uma plotadora que utiliza caneta, por exemplo.
Programa de exemplo de informações da impressora
A Figura 10.8 mostra o formulário principal para o programa de exemplo. O programa contém oito páginas, cada qual listando diferentes recursos da impressora selecionada na caixa de combinação no alto.
FIGURA 10.8
O formulário principal para o exemplo de informações da impressora.
Declarando a função DeviceCapabilitiesA
Se você tentar usar a função DeviceCapabilities( ) definida em Windows.pas, não poderá executar seu programa, pois essa função não está definida na GDI32.DLL, como Windows.pas indica. Em vez disso, essa função
na GDI32.DLL é DeviceCapabilitiesEx(<|>). Entretanto, mesmo que você defina o protótipo dessa função da
maneira a seguir, a função não funcionará conforme esperado, retornando resultados incorretos:
157
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function DeviceCapabilitiesEx(pDevice, pPort: Pchar; fwCapability: Word;
pOutput: Pchar; DevMode: PdeviceMode):
Integer; stdcall; external ‘Gdi32.dll’;
Acontece que duas funções – DeviceCapabilitiesA( ) para strings ANSI e DeviceCapabilitiesW( ) para
strings largos – são definidas em WINSPOOL.DRV, que é a interface do spooler de impressão do Win32. Essa é
a função correta para se usar, conforme indica o CD da MSDN (Microsoft Developer’s Network). A definição correta para o protótipo da função, usada no programa de exemplo da Listagem 10.8 (ver na próxima seção) é a seguinte:
function DeviceCapabilitiesA(pDevice, pPort: Pchar; fwCapability: Word;
pOutput: Pchar; DevMode: PdeviceMode):
Integer; stdcall; external ‘winspool.drv’;
Observe que essa declaração pode ser encontrada em WINSPOOL.PAS, no Delphi 5.
Funcionalidade do programa de exemplo
A Listagem 10.8 (mostrada no final desta seção) contém o código-fonte para o programa de exemplo,
Printer Information. O manipulador de evento OnCreate do formulário principal simplesmente preenche
a caixa de combinação com a lista de impressoras disponíveis no sistema. O manipulador de evento
OnChange da caixa de combinação é o ponto central da aplicação, onde são chamados os métodos para recuperar informações de impressora.
A primeira página no formulário General Data contém informações gerais sobre o dispositivo de
impressão. Você verá que o nome de dispositivo da impressora, o driver e o endereço da porta são obtidos com uma chamada ao método TPrinter.GetPrinter( ). Esse método também recupera uma alça para a
estrutura TDeviceMode da impressora selecionada atualmente. Essa informação é então incluída na página
General Data. Para recuperar a versão do driver de impressora, você usa a função DeviceCapabilitiesA( ) e
passa o índice DC_DRIVER. O restante do manipulador de evento PrinterComboChange chama as várias rotinas
para preencher as caixas de listagem nas diversas páginas do formulário principal.
O método GetBinNames( ) ilustra como a função DeviceCapabilitiesA deve ser usada para recuperar os
nomes de bandeja para a impressora selecionada. Esse método primeiro apanha a quantidade de nomes
de bandeja disponíveis, chamando DeviceCapabilitiesA, passando o índice DC_BINNAMES e passando nil como
parâmetros pOutput e DevMode. O resultado dessa chamada de função especifica quanta memória deverá ser
alocada para conter os nomes de bandeja. De acordo com a documentação de DeviceCapabilitiesA, cada
nome de bandeja é definido como um array de 24 caracteres. Definimos um tipo de dados TBinName da seguinte forma:
TBinName = array[0..23] of char;
Também definimos um array de TBinName:
TBinNames = array[0..0] of TBinName;
Esse tipo é usado para converter um ponteiro como um array de tipos de dados TBinName. Para acessar um elemento em algum índice do array, você precisa desativar a verificação de intervalo, pois esse array é definido para ter um intervalo de 0..0, conforme ilustrado no método GetBinNames( ). Os nomes de
bandeja são incluídos na caixa de listagem apropriada.
Essa mesma técnica de determinar a quantidade de memória necessária e alocar essa memória dinamicamente também é usada nos métodos GetDevCapsPaperNames( ) e GetResolutions( ).
Os métodos GetDuplexSupport( ), GetCopies( ) e GetEMFStatus( ) usam a função DeviceCapabilitiesA( )
para retornar um valor da informação solicitada. Por exemplo, o código a seguir determina se a impressora selecionada aceita impressão duplex retornando um valor 1, se a impressão duplex for aceita, ou 0,
em caso contrário:
158
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DeviceCapabilitiesA(Device, Port, DC_DUPLEX, nil, nil);
mir:
Além disso, a instrução a seguir retorna o número máximo de cópias que o dispositivo pode impri-
DeviceCapabilitiesA(Device, Port, DC_COPIES, nil, nil);
Os outros métodos usam a função GetDeviceCaps( ) para determinar os vários recursos do dispositivo
selecionado. Em alguns casos, GetDeviceCaps( ) retorna o valor específico solicitado. Por exemplo, a instrução a seguir retorna a largura, em milímetros, do dispositivo de impressora:
GetDeviceCaps(Printer.Handle, HORZSIZE);
Em outros casos, GetDeviceCaps( ) retorna um valor inteiro cujos bits são isolados para determinar
um recurso qualquer. Por exemplo, o método GetRasterCaps( ) primeiro apanha o valor inteiro que contém os campos dos bits isolados:
RCaps := GetDeviceCaps(Printer.Handle, RASTERCAPS);
Depois, para determinar se a impressora aceita banding, você precisa isolar o campo RC_BANDING realizando uma operação AND cujo resultado iguale o valor de RC_BANDING:
(RCaps and RC_BANDING) = RC_BANDING
Essa avaliação é passada para uma das funções auxiliadoras, BoolToYesNoStr( ), que retorna a string
Yes ou No, com base no resultado da avaliação. Outros campos são isolados da mesma forma. Essa mesma
técnica é usada em outras áreas, onde os campos com máscaras de bits são retornados das funções GetDeviceCaps( ) e DeviceCapabilitiesA( ), como no método GetTrueTypeInfo( ).
Você encontrará bastante documentação sobre as duas funções, DeviceCapabilitiesA( ) e GetDeviceCaps( ), na ajuda da API do Win32.
Listagem 10.7 Programa de exemplo Printer Information
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls, ExtCtrls;
type
TMainForm = class(TForm)
pgcPrinterInfo: TPageControl;
tbsPaperTypes: TTabSheet;
tbsGeneralData: TTabSheet;
lbPaperTypes: TListBox;
tbsDeviceCaps: TTabSheet;
tbsRasterCaps: TTabSheet;
tbsCurveCaps: TTabSheet;
tbsLineCaps: TTabSheet;
tbsPolygonalCaps: TTabSheet;
tbsTextCaps: TTabSheet;
lvGeneralData: TListView;
lvCurveCaps: TListView;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
159
Listagem 10.7 Continuação
Splitter1: TSplitter;
lvDeviceCaps: TListView;
lvRasterCaps: TListView;
pnlTop: TPanel;
cbPrinters: TComboBox;
lvLineCaps: TListView;
lvPolyCaps: TListView;
lvTextCaps: TListView;
procedure FormCreate(Sender: TObject);
procedure cbPrintersChange(Sender: TObject);
private
Device, Driver, Port: array[0..255] of char;
ADevMode: THandle;
public
procedure GetBinNames;
procedure GetDuplexSupport;
procedure GetCopies;
procedure GetEMFStatus;
procedure GetResolutions;
procedure GetTrueTypeInfo;
procedure GetDevCapsPaperNames;
procedure GetDevCaps;
procedure GetRasterCaps;
procedure GetCurveCaps;
procedure GetLineCaps;
procedure GetPolyCaps;
procedure GetTextCaps;
end;
var
MainForm: TMainForm;
implementation
uses
Printers, WinSpool;
const
NoYesArray: array[Boolean] of String = (‘No’, ‘Yes’);
type
// Tipos para conter nomes de bandeja
TBinName = array[0..23] of char;
// Onde for usado, defina $R- para evitar erro
TBinNames = array[0..0] of TBinName;
// Tipos para conter nomes de papel
TPName = array[0..63] of char;
// Onde for usado, defina $R- para evitar erro
TPNames = array[0..0] of TPName;
160
// Tipos para conter resoluções
TResolution = array[0..1] of integer;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.7 Continuação
// Onde for usado, defina $R- para evitar erro
TResolutions = array[0..0] of TResolution;
// Tipo para conter array de tamanhos de página (tipos word)
TPageSizeArray = Array[0..0] of word;
var
Rslt: Integer;
{$R *.DFM}
(*
function BoolToYesNoStr(aVal: Boolean): String;
// Retorna a “YES” ou “NO”, com base no valor Booleano
begin
if aVal then
Result := ‘Yes’
else
Result := ‘No’;
end;
*)
procedure AddListViewItem(const aCaption, aValue: String; aLV: TListView);
// Este método é usado para incluir um TListItem em TListView, aLV
var
NewItem: TListItem;
begin
NewItem := aLV.Items.Add;
NewItem.Caption := aCaption;
NewItem.SubItems.Add(aValue);
end;
161
procedure TMainForm.GetBinNames;
var
BinNames: Pointer;
i: integer;
begin
{$R-} // Verificação de intervalo deve ser desativada aqui.
// Primeiro determina quantos nomes de bandeja estão disponíveis.
Rslt := DeviceCapabilitiesA(Device, Port, DC_BINNAMES, nil, nil);
if Rslt > 0 then
begin
{ Cada nome de bandeja possui 24 bytes. Portanto, reserva Rslt*24
bytes para conter os nomes de bandeja. }
GetMem(BinNames, Rslt*24);
try
// Agora apanha os nomes de bandeja no bloco de memória alocado.
if DeviceCapabilitiesA(Device, Port, DC_BINNAMES, BinNames, nil) = -1 then
raise Exception.Create(‘DevCap Error’);
// Inclui informações na caixa de listagem apropriada.
AddListViewItem(‘BIN NAMES’, EmptyStr, lvGeneralData);
for i := 0 to Rslt - 1 do
begin
AddListViewItem(Format(‘
Bin Name %d’, [i]),
StrPas(TBinNames(BinNames^)[i]), lvGeneralData);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
161
Listagem 10.7 Continuação
end;
finally
FreeMem(BinNames, Rslt*24);
end;
end;
{$R+} // Ativa novamente a verificação de intervalo.
end;
procedure TMainForm.GetDuplexSupport;
begin
{ Esta função usa DeviceCapabilitiesA para determinar se o dispositivo de
impressão aceita ou não a impressão duplex. }
Rslt := DeviceCapabilitiesA(Device, Port, DC_DUPLEX, nil, nil);
AddListViewItem(‘Duplex Printing’, NoYesArray[Rslt = 1], lvGeneralData);
end;
procedure TMainForm.GetCopies;
begin
{ Esta função determina quantas cópias o dispositivo pode ser definido
para imprimir se o resultado não for maior do que 1, quando a lógica
de impressão deverá ser executada várias vezes. }
Rslt := DeviceCapabilitiesA(Device, Port, DC_COPIES, nil, nil);
AddListViewItem(‘Copies that printer can print’, InttoStr(Rslt), lvGeneralData);
end;
procedure TMainForm.GetEMFStatus;
begin
// Esta função determina se o dispositivo aceita metafiles avançados.
Rslt := DeviceCapabilitiesA(Device, Port, DC_EMF_COMPLIANT, nil, nil);
AddListViewItem(‘EMF Compliant’, NoYesArray[Rslt=1], lvGeneralData);
end;
procedure TMainForm.GetResolutions;
var
Resolutions: Pointer;
i: integer;
begin
{$R-} // A verificação de intervalo deve ser desativada.
// Determina a quantidade de resoluções disponíveis.
Rslt := DeviceCapabilitiesA(Device, Port, DC_ENUMRESOLUTIONS, nil, nil);
if Rslt > 0 then begin
{ Aloca a memória para conter as diferentes resoluções representadas
por pares de inteiros, por exemplo, 300, 300 }
GetMem(Resolutions, (SizeOf(Integer)*2)*Rslt);
try
// Apanha as diferentes resoluções.
if DeviceCapabilitiesA(Device, Port, DC_ENUMRESOLUTIONS,
Resolutions, nil) = -1 then
Raise Exception.Create(‘DevCaps Error’);
// Inclui informações de resolução na caixa de listagem apropriada.
AddListViewItem(‘RESOLUTION CONFIGURATIONS’, EmptyStr, lvGeneralData);
162
for i := 0 to Rslt - 1 do
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.7 Continuação
begin
AddListViewItem(‘
Resolution Configuration’,
IntToStr(TResolutions(Resolutions^)[i][0])+
‘ ‘+IntToStr(TResolutions(Resolutions^)[i][1]), lvGeneralData);
end;
finally
FreeMem(Resolutions, SizeOf(Integer)*Rslt*2);
end;
end;
{$R+} // Ativa novamente a verificação de intervalo.
end;
procedure TMainForm.GetTrueTypeInfo;
begin
// Apanha os recursos de fonte TrueType do dispositivo representado
// com máscaras de bit.
Rslt := DeviceCapabilitiesA(Device, Port, DC_TRUETYPE, nil, nil);
if Rslt < > 0 then
{ Agora isola os recursos individuais de TrueType e indica o resultado
na caixa de listagem apropriada. }
AddListViewItem(‘TRUE TYPE FONTS’, EmptyStr, lvGeneralData);
with lvGeneralData.Items do
begin
AddListViewItem(‘
Prints TrueType fonts as graphics’,
NoYesArray[(Rslt and DCTT_BITMAP) = DCTT_BITMAP], lvGeneralData);
AddListViewItem(‘
Downloads TrueType fonts’,
NoYesArray[(Rslt and DCTT_DOWNLOAD) = DCTT_DOWNLOAD], lvGeneralData);
AddListViewItem(‘
Downloads outline TrueType fonts’,
NoYesArray[(Rslt and DCTT_DOWNLOAD_OUTLINE) = DCTT_DOWNLOAD_OUTLINE],
lvGeneralData);
AddListViewItem(‘
Substitutes device for TrueType fonts’,
NoYesArray[(Rslt and DCTT_SUBDEV) = DCTT_SUBDEV], lvGeneralData);
end;
end;
procedure TMainForm.GetDevCapsPaperNames;
{ Este método apanha os tipos de papel disponíveis em uma impressora
selecionada a partir da função DeviceCapabilitiesA. }
var
PaperNames: Pointer;
i: integer;
begin
{$R-} // A verificação de intervalo deve ser desativada.
lbPaperTypes.Items.Clear;
// Primeiro apanha a quantidade de nomes de papel disponíveis.
Rslt := DeviceCapabilitiesA(Device, Port, DC_PAPERNAMES, nil, nil);
if Rslt > 0 then begin
{ Agora aloca o array de nomes de papel. Cada nome de papel possui 64
bytes. Portanto, aloca Rslt*64 de memória. }
GetMem(PaperNames, Rslt*64);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
163
Listagem 10.7 Continuação
try
// Apanha a lista de nomes no bloco de memória alocado.
if DeviceCapabilitiesA(Device, Port, DC_PAPERNAMES,
PaperNames, nil) = - 1 then
raise Exception.Create(‘DevCap Error’);
// Inclui nomes de papel na caixa de listagem apropriada.
for i := 0 to Rslt - 1 do
lbPaperTypes.Items.Add(StrPas(TPNames(PaperNames^)[i]));
finally
FreeMem(PaperNames, Rslt*64);
end;
end;
{$R+} // Ativa novamente a verificação de intervalo.
end;
164
procedure TMainForm.GetDevCaps;
{ Este método apanha vários recursos do dispositivo de impressão selecionado
usando a função GetDeviceCaps. Consulte a ajuda on-line da API para saber
o significado de cada um desses itens. }
begin
with lvDeviceCaps.Items do
begin
Clear;
AddListViewItem(‘Width in millimeters’,
IntToStr(GetDeviceCaps(Printer.Handle, HORZSIZE)), lvDeviceCaps);
AddListViewItem(‘Height in millimeter’,
IntToStr(GetDeviceCaps(Printer.Handle, VERTSIZE)), lvDeviceCaps);
AddListViewItem(‘Width in pixels’,
IntToStr(GetDeviceCaps(Printer.Handle, HORZRES)), lvDeviceCaps);
AddListViewItem(‘Height in pixels’,
IntToStr(GetDeviceCaps(Printer.Handle, VERTRES)), lvDeviceCaps);
AddListViewItem(‘Pixels per horizontal inch’,
IntToStr(GetDeviceCaps(Printer.Handle, LOGPIXELSX)), lvDeviceCaps);
AddListViewItem(‘Pixels per vertical inch’,
IntToStr(GetDeviceCaps(Printer.Handle, LOGPIXELSY)), lvDeviceCaps);
AddListViewItem(‘Color bits per pixel’,
IntToStr(GetDeviceCaps(Printer.Handle, BITSPIXEL)), lvDeviceCaps);
AddListViewItem(‘Number of color planes’,
IntToStr(GetDeviceCaps(Printer.Handle, PLANES)), lvDeviceCaps);
AddListViewItem(‘Number of brushes’,
IntToStr(GetDeviceCaps(Printer.Handle, NUMBRUSHES)), lvDeviceCaps);
AddListViewItem(‘Number of pens’,
IntToStr(GetDeviceCaps(Printer.Handle, NUMPENS)), lvDeviceCaps);
AddListViewItem(‘Number of fonts’,
IntToStr(GetDeviceCaps(Printer.Handle, NUMFONTS)), lvDeviceCaps);
Rslt := GetDeviceCaps(Printer.Handle, NUMCOLORS);
if Rslt = -1 then
AddListViewItem(‘Number of entries in color table’, ‘ > 8’, lvDeviceCaps)
else AddListViewItem(‘Number of entries in color table’,
IntToStr(Rslt), lvDeviceCaps);
AddListViewItem(‘Relative pixel drawing width’,
IntToStr(GetDeviceCaps(Printer.Handle, ASPECTX)), lvDeviceCaps);
AddListViewItem(‘Relative pixel drawing height’,
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.7 Continuação
IntToStr(GetDeviceCaps(Printer.Handle, ASPECTY)), lvDeviceCaps);
AddListViewItem(‘Diagonal pixel drawing width’,
IntToStr(GetDeviceCaps(Printer.Handle, ASPECTXY)), lvDeviceCaps);
if GetDeviceCaps(Printer.Handle, CLIPCAPS) = 1 then
AddListViewItem(‘Clip to rectangle‘, ‘Yes’, lvDeviceCaps)
else AddListViewItem(‘Clip to rectangle’, ‘No’, lvDeviceCaps);
end;
end;
procedure TMainForm.GetRasterCaps;
{ Este método apanha os diversos recursos de rastreio da impressora selecionada
usando a função GetDeviceCaps com o índice RASTERCAPS. Consulte a ajuda
on-line para obter informações sobre cada recurso. }
var
RCaps: Integer;
begin
with lvRasterCaps.Items do
begin
Clear;
RCaps := GetDeviceCaps(Printer.Handle, RASTERCAPS);
AddListViewItem(‘Banding’,
NoYesArray[(RCaps and RC_BANDING) = RC_BANDING], lvRasterCaps);
AddListViewItem(‘BitBlt Capable’,
NoYesArray[(RCaps and RC_BITBLT) = RC_BITBLT], lvRasterCaps);
AddListViewItem(‘Supports bitmaps > 64K’,
NoYesArray[(RCaps and RC_BITMAP64) = RC_BITMAP64], lvRasterCaps);
AddListViewItem(‘DIB support’,
NoYesArray[(RCaps and RC_DI_BITMAP) = RC_DI_BITMAP], lvRasterCaps);
AddListViewItem(‘Floodfill support’,
NoYesArray[(RCaps and RC_FLOODFILL) = RC_FLOODFILL], lvRasterCaps);
AddListViewItem(‘Windows 2.0 support’,
NoYesArray[(RCaps and RC_GDI20_OUTPUT) = RC_GDI20_OUTPUT], lvRasterCaps);
AddListViewItem(‘Palette based device’,
NoYesArray[(RCaps and RC_PALETTE) = RC_PALETTE], lvRasterCaps);
AddListViewItem(‘Scaling support’,
NoYesArray[(RCaps and RC_SCALING) = RC_SCALING], lvRasterCaps);
AddListViewItem(‘StretchBlt support’,
NoYesArray[(RCaps and RC_STRETCHBLT) = RC_STRETCHBLT], lvRasterCaps);
AddListViewItem(‘StretchDIBits support’,
NoYesArray[(RCaps and RC_STRETCHDIB) = RC_STRETCHDIB], lvRasterCaps);
end;
end;
procedure TMainForm.GetCurveCaps;
{ Este método apanha os vários recursos de curva da impressora selecionada
usando a função GetDeviceCaps com o índice CURVECAPS. Consulte a ajuda
on-line para obter informações sobre cada recurso. }
var
CCaps: Integer;
begin
with lvCurveCaps.Items do
begin
Clear;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
165
Listagem 10.7 Continuação
CCaps := GetDeviceCaps(Printer.Handle, CURVECAPS);
AddListViewItem(‘Curve support’,
NoYesArray[(CCaps and CC_NONE) = CC_NONE], lvCurveCaps);
AddListViewItem(‘Circle support’,
NoYesArray[(CCaps and CC_CIRCLES) = CC_CIRCLES], lvCurveCaps);
AddListViewItem(‘Pie support’,
NoYesArray[(CCaps and CC_PIE) = CC_PIE], lvCurveCaps);
AddListViewItem(‘Chord arc support’,
NoYesArray[(CCaps and CC_CHORD) = CC_CHORD], lvCurveCaps);
AddListViewItem(‘Ellipse support’,
NoYesArray[(CCaps and CC_ELLIPSES) = CC_ELLIPSES], lvCurveCaps);
AddListViewItem(‘Wide border support’,
NoYesArray[(CCaps and CC_WIDE) = CC_WIDE], lvCurveCaps);
AddListViewItem(‘Styled border support’,
NoYesArray[(CCaps and CC_STYLED) = CC_STYLED], lvCurveCaps);
AddListViewItem(‘Round rectangle support’,
NoYesArray[(CCaps and CC_ROUNDRECT) = CC_ROUNDRECT], lvCurveCaps);
end;
end;
procedure TMainForm.GetLineCaps;
{ Este método apanha os vários recursos de desenho de linha da impressora
selecionada usando a função GetDeviceCaps com o índice LINECAPS. Consulte
a ajuda on-line para obter informações sobre cada recurso. }
var
LCaps: Integer;
begin
with lvLineCaps.Items do
begin
Clear;
LCaps := GetDeviceCaps(Printer.Handle, LINECAPS);
AddListViewItem(‘Line support’,
NoYesArray[(LCaps and LC_NONE) = LC_NONE], lvLineCaps);
AddListViewItem(‘Polyline support’,
NoYesArray[(LCaps and LC_POLYLINE) = LC_POLYLINE], lvLineCaps);
AddListViewItem(‘Marker support’,
NoYesArray[(LCaps and LC_MARKER) = LC_MARKER], lvLineCaps);
AddListViewItem(‘Multiple marker support’,
NoYesArray[(LCaps and LC_POLYMARKER) = LC_POLYMARKER], lvLineCaps);
166
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.7 Continuação
AddListViewItem(‘Wide line support’,
NoYesArray[(LCaps and LC_WIDE) = LC_WIDE], lvLineCaps);
AddListViewItem(‘Styled line support’,
NoYesArray[(LCaps and LC_STYLED) = LC_STYLED], lvLineCaps);
AddListViewItem(‘Wide and styled line support’,
NoYesArray[(LCaps and LC_WIDESTYLED) = LC_WIDESTYLED], lvLineCaps);
AddListViewItem(‘Interior support’,
NoYesArray[(LCaps and LC_INTERIORS) = LC_INTERIORS], lvLineCaps);
end;
end;
procedure TMainForm.GetPolyCaps;
{ Este método apanha os vários recursos de polígono da impressora selecionada
usando a função GetDeviceCaps com o índice POLYGONALCAPS. Consulte a
a ajuda on-line para obter informações sobre cada recurso. }
var
PCaps: Integer;
begin
with lvPolyCaps.Items do
begin
Clear;
PCaps := GetDeviceCaps(Printer.Handle, POLYGONALCAPS);
AddListViewItem(‘Polygon support’,
NoYesArray[(PCaps and PC_NONE) = PC_NONE], lvPolyCaps);
AddListViewItem(‘Alternate fill polygon support’,
NoYesArray[(PCaps and PC_POLYGON) = PC_POLYGON], lvPolyCaps);
AddListViewItem(‘Rectangle support’,
NoYesArray[(PCaps and PC_RECTANGLE) = PC_RECTANGLE], lvPolyCaps);
AddListViewItem(‘Winding-fill polygon support’,
NoYesArray[(PCaps and PC_WINDPOLYGON) = PC_WINDPOLYGON], lvPolyCaps);
AddListViewItem(‘Single scanline support’,
NoYesArray[(PCaps and PC_SCANLINE) = PC_SCANLINE], lvPolyCaps);
AddListViewItem(‘Wide border support’,
NoYesArray[(PCaps and PC_WIDE) = PC_WIDE], lvPolyCaps);
AddListViewItem(‘Styled border support’,
NoYesArray[(PCaps and PC_STYLED) = PC_STYLED], lvPolyCaps);
AddListViewItem(‘Wide and styled border support’,
NoYesArray[(PCaps and PC_WIDESTYLED) = PC_WIDESTYLED], lvPolyCaps);
AddListViewItem(‘Interior support’,
NoYesArray[(PCaps and PC_INTERIORS) = PC_INTERIORS], lvPolyCaps);
end;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
167
Listagem 10.7 Continuação
procedure TMainForm.GetTextCaps;
{ Este método apanha os vários recursos de desenho de texto da impressora
selecionada usando a função GetDeviceCaps com o índice TEXTCAPS. Consulte
a ajuda on-line para obter informações sobre cada recurso. }
var
TCaps: Integer;
begin
with lvTextCaps.Items do
begin
Clear;
TCaps := GetDeviceCaps(Printer.Handle, TEXTCAPS);
AddListViewItem(‘Character output precision’,
NoYesArray[(TCaps and TC_OP_CHARACTER) = TC_OP_CHARACTER], lvTextCaps);
AddListViewItem(‘Stroke output precision’,
NoYesArray[(TCaps and TC_OP_STROKE) = TC_OP_STROKE], lvTextCaps);
AddListViewItem(‘Stroke clip precision’,
NoYesArray[(TCaps and TC_CP_STROKE) = TC_CP_STROKE], lvTextCaps);
AddListViewItem(‘90 degree character rotation’,
NoYesArray[(TCaps and TC_CR_90) = TC_CR_90], lvTextCaps);
AddListViewItem(‘Any degree character rotation’,
NoYesArray[(TCaps and TC_CR_ANY) = TC_CR_ANY], lvTextCaps);
AddListViewItem(‘Independent scale in X and Y direction’,
NoYesArray[(TCaps and TC_SF_X_YINDEP) = TC_SF_X_YINDEP], lvTextCaps);
AddListViewItem(‘Doubled character for scaling’,
NoYesArray[(TCaps and TC_SA_DOUBLE) = TC_SA_DOUBLE], lvTextCaps);
AddListViewItem(‘Integer multiples only for character scaling’,
NoYesArray[(TCaps and TC_SA_INTEGER) = TC_SA_INTEGER], lvTextCaps);
AddListViewItem(‘Any multiples for exact character scaling’,
NoYesArray[(TCaps and TC_SA_CONTIN) = TC_SA_CONTIN], lvTextCaps);
AddListViewItem(‘Double weight characters’,
NoYesArray[(TCaps and TC_EA_DOUBLE) = TC_EA_DOUBLE], lvTextCaps);
AddListViewItem(‘Italicized characters’,
NoYesArray[(TCaps and TC_IA_ABLE) = TC_IA_ABLE], lvTextCaps);
AddListViewItem(‘Underlined characters’,
NoYesArray[(TCaps and TC_UA_ABLE) = TC_UA_ABLE], lvTextCaps);
AddListViewItem(‘Strikeout characters’,
NoYesArray[(TCaps and TC_SO_ABLE) = TC_SO_ABLE], lvTextCaps);
168
AddListViewItem(‘Raster fonts’,
NoYesArray[(TCaps and TC_RA_ABLE) = TC_RA_ABLE], lvTextCaps);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 10.7 Continuação
AddListViewItem(‘Vector fonts’,
NoYesArray[(TCaps and TC_VA_ABLE) = TC_VA_ABLE], lvTextCaps);
AddListViewItem(‘Scrolling using bit-block transfer’,
NoYesArray[(TCaps and TC_SCROLLBLT) = TC_SCROLLBLT], lvTextCaps);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Armazena os nomes de impresora na caixa de combinação.
cbPrinters.Items.Assign(Printer.Printers);
// Exibe a impressora padrão na caixa de combianção.
cbPrinters.ItemIndex := Printer.PrinterIndex;
// Chama o evento OnChange da caixa de combinação
cbPrintersChange(nil);
end;
procedure TMainForm.cbPrintersChange(Sender: TObject);
begin
Screen.Cursor := crHourGlass;
try
// Preenche caixa de combinação com impressoras disponíveis
Printer.PrinterIndex := cbPrinters.ItemIndex;
with Printer do
GetPrinter(Device, Driver, Port, ADevMode);
// Preenche a página geral com informações da impressora
with lvGeneralData.Items do
begin
Clear;
AddListViewItem(‘Port’, Port, lvGeneralData);
AddListViewItem(‘Device’, Device, lvGeneralData);
Rslt := DeviceCapabilitiesA(Device, Port, DC_DRIVER, nil, nil);
AddListViewItem(‘Driver Version’, IntToStr(Rslt), lvGeneralData);
end;
// As funções a seguir utilizam a função GetDeviceCapabilitiesA.
GetBinNames;
GetDuplexSupport;
GetCopies;
GetEMFStatus;
GetResolutions;
GetTrueTypeInfo;
// As funções a seguir utilizam a
GetDevCapsPaperNames;
GetDevCaps;
// Preenche página
GetRasterCaps; // Preenche página
GetCurveCaps; // Preenche página
GetLineCaps;
// Preenche página
GetPolyCaps;
// Preenche página
GetTextCaps;
// Preenche página
função GetDeviceCaps.
Device Caps.
Raster Caps.
Curve Caps.
Line Caps.
Polygonal Caps.
Text Caps.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
169
Listagem 10.7 Continuação
finally
Screen.Cursor := crDefault;
end;
end;
end.
Resumo
Este capítulo ensina as técnicas que você precisa conhecer para programar qualquer tipo de impressão
personalizada, desde a impressão mais simples até as técnicas mais avançadas. Você também aprendeu
uma metodologia que pode ser aplicada a qualquer tarefa de impressão. Além do mais, aprendeu sobre a
estrutura TDeviceMode e como realizar tarefas de impressão comuns. Você usará mais desse conhecimento
nos próximos capítulos, onde criará métodos de impressão ainda mais poderosos.
170
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Transporte
para Delphi 5
CAPÍTULO
15
NE STE CAP ÍT UL O
l
Novo no Delphi 5
l
Migração do Delphi 4
l
Migração do Delphi 3
l
Migração do Delphi 2
l
Migração do Delphi 1
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 5 — 2ª PROVA
Se você estiver passando para o Delphi 5 e vindo de uma versão anterior, este capítulo foi escrito para
você. A primeira seção do capítulo discute sobre os aspectos envolvidos na passagem de qualquer versão
do Delphi para o Delphi 5. Na segunda, terceira e quarta seções, você aprenderá sobre as diferenças sutis
entre as diversas versões de 32 bits do Delphi e como levar em consideração essas diferenças enquanto
passa suas aplicações para o Delphi 5. A quarta seção deste capítulo irá ajudar aqueles que estão migrando das aplicações de 16 bits do Delphi 1.0 para o mundo de 32 bits do Delphi 5. Embora a Borland tenha
feito um esforço concentrado para garantir que seu código seja compatível entre as versões, é de se entender que algumas mudanças devam ser feitas em nome do progresso, e certas situações exigem mudanças no código para que as aplicações sejam compiladas e executadas corretamente na versão mais recente
do Delphi.
Novo no Delphi 5
Em geral, quanto mais recente for a versão do Delphi de onde você está vindo, mais fácil será migrar para
o Delphi 5. No entanto, esteja você vindo do Delphi 1, 2, 3 ou 4, esta seção contém informações necessárias à atualização para o Delphi 5.
Qual versão?
Embora a maioria do código do Delphi possa ser compilada em todas as versões do compilador, em alguns casos as diferenças na linguagem ou na VCL exigem que você escreva algo ligeiramente diferente
para conseguir uma determinada tarefa em cada versão do produto. Ocasionalmente, você pode ter de
compilar para várias versões do Delphi a partir de um código básico. Para essa finalidade, cada versão do
compilador Delphi contém uma definição (define) condicional VERxxx para a qual você pode testar no seu
código-fonte. Como o Borland C++Builder também vem com novas versões do compilador do Delphi,
estas seções também contêm essa definição condicional. A Tabela 15.1 mostra as definições condicionais
para as diversas versões do compilador Delphi.
Tabela 15.1 Definições condicionais para versões do compilador
Produto
Definição condicional
Delphi 1
VER80
Delphi 2
VER90
C++Builder 1
VER95
Delphi 3
VER100
C++Builder 3
VER110
Delphi 4
VER120
C++Builder 4
VER120
Delphi 5
VER130
Usando essas definições, o código-fonte que você precisa escrever a fim de compilar para diferentes
versões do compilador se pareceria com este:
172
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
{$IFDEF VER80}
O código do Delphi 1 entra aqui
{$ENDIF}
{$IFDEF VER90}
O código do Delphi 2 entra aqui
{$ENDIF}
{$IFDEF VER95}
O código do C++Builder 1 entra aqui
{$ENDIF}
{$IFDEF VER100}
O código do Delphi 3 entra aqui
{$ENDIF}
{$IFDEF VER110}
O código do C++Builder 3 entra aqui
{$ENDIF}
{$IFDEF VER120}
O código do Delphi 4 e do C++Builder 4 entra aqui
{$ENDIF}
{$IFDEF VER130}
O código do Delphi 5 entra aqui
{$ENDIF}
NOTA
Se você estiver questionando por que o compilador Delphi 1.0 é considerado versão 8, o Delphi 2 a versão
9 etc., isso ocorre porque o Delphi 1.0 é considerado a versão 8 do compilador Pascal da Borland. A última
versão do Turbo Pascal foi 7.0, e o Delphi é a evolução dessa linha de produtos.
Assim como você terá de lidar com as diferenças na linguagem e na VCL entre as versões do Delphi, também terá de lidar com as diferenças na API do Windows. Se você tiver de lidar com diferenças
nas APIs de 16 e 32 bits a partir de uma única base de código, poderá aproveitar as definições adicionais voltadas para essa finalidade. O compilador Delphi de 16 bits define WINDOWS, enquanto os compiladores Delphi do Win32 definem WIN32. O exemplo de código a seguir demonstra como aproveitar essas
definições:
{$IFDEF WINDOWS}
O código específico do Windows de 16 bits entra aqui
{$ENDIF}
{$IFDEF WIN32}
O código específico do Win32 entra aqui
{$ENDIF}
Unidades, componentes e pacotes
As unidades compiladas do Delphi 5 (arquivos DCU) diferem das unidades das versões anteriores do
Delphi (e do C++Builder). Você precisa ter o código-fonte para quaisquer unidades usadas na sua aplicação a fim de montar sua aplicação sob qualquer versão em particular do Delphi. Isso, é claro, significa
que você não poderá usar quaisquer componentes usados na sua aplicação – seus próprios componentes
ou componentes de terceiros – a menos que tenha o código-fonte para esses componentes. Se você não tiver o código-fonte para um determinado componente de terceiros, entre em contato com seu fornecedor
para obter uma versão do componente específica da sua versão do Delphi.
173
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
NOTA
A questão de versão do compilador e versão do arquivo da unidade não é uma situação nova, e também
existe entre as versões de arquivo-objeto do compilador C++. Se você distribui (ou compra) componentes
sem o código-fonte, precisa entender que está distribuindo/comprando um arquivo binário específico da
versão do compilador, que provavelmente precisará ser revisado para acompanhar as versões subseqüentes do compilador.
Além do mais, a questão de versão da DCU não é necessariamente um aspecto apenas do compilador.
Mesmo que o compilador não fosse alterado de uma versão para outra, as mudanças e melhorias na VCL
básica provavelmente ainda exigiriam que as unidades fossem recompiladas a partir do código-fonte.
O Delphi 3 também introduziu os pacotes, a idéia de várias unidades armazenadas em um único arquivo binário. A partir do Delphi 5, a biblioteca de componentes tornou-se uma coleção de pacotes, em
vez de uma única DLL imensa com a biblioteca de componentes. Assim como as unidades, os pacotes não
são compatíveis entre as versões do produto, e por isso você terá de recriar seus pacotes para o Delphi 5,
e terá de entrar em contato com os fornecedores dos seus componentes de terceiros para obter pacotes
atualizados.
Migração do Delphi 4
Existem apenas alguns aspectos de migração ao levar suas aplicações do Delphi 4 para o Delphi 5. Em
muitos casos, você pode simplesmente carregar seu projeto no Delphi 5 e pressionar a tecla no compilador. No entanto, se você tiver problemas, esta seção discutirá os “redutores de velocidade” com os quais
irá se deparar ao fazer as coisas funcionarem no Delphi 5.
Problemas com o IDE
Os problemas com o IDE provavelmente serão os primeiros que você encontrará ao migrar suas aplicações. Aqui estão alguns dos problemas que você poderá encontrar no seu caminho:
l
l
l
Os arquivos de símbolo (RSM) do depurador do Delphi 4 não são compatíveis com o Delphi 5.
Você saberá que está tendo esse problema quando encontrar a mensagem “Error reading
symnbol file” (erro lendo arquivo de símbolos). Se isso acontecer, o reparo é simples: monte a
aplicação novamente.
Agora, o Delphi 5 tem como padrão armazenar os arquivos no modo de texto. Se você tiver de
manter a compatibilidade com DFM das versões anteriores do Delphi, terá de salvar os arquivos
de formulário em binário. Isso pode ser feito desmarcando-se New Form As Text (novo formulário como texto) na página Preferences (preferências) da caixa de diálogo Environment Options
(opções de ambiente).
A criação de código na importação ou geração de bibliotecas de tipos foi alterada. Além de algumas mudanças pequenas, o novo gerador foi melhorado para permitir que você mapeie nomes
de símbolos; você pode personalizar o mapeamento de nomes de símbolos entre a biblioteca de
tipos e o Pascal editando o arquivo tlibimp.sym. Para obter instruções, consulte o tópico “Mapping Symbol Names in the Type Library” (mapeamento de nomes de símbolos na biblioteca de
tipos” na ajuda on-line.
Problemas com a RTL
174
O único problema que você provavelmente encontrará aqui trata da definição da palavra de controle da
unidade de ponto flutuante (FPU) nas DLLs. Nas versões anteriores do Delphi, as DLLs definiriam a palavra de controle da FPU, mudando assim o valor estabelecido pela aplicação host. Agora, o código de
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
partida da DLL não define mais a palavra de controle da FPU. Se você precisar definir a palavra de controle para assegurar algum comportamento específico pela FPU, poderá fazê-lo manualmente usando a
função Set8087CW( ) da unidade System.
Problemas com a VCL
Existem diversos problemas relacionados à VCL que você poderá encontrar, mas a maioria envolve algumas edições simples como uma maneira de aprontar seu projeto para o Delphi 5. Veja uma lista desses
problemas:
l
O tipo das propriedades que representam um índice para uma lista de imagens foi alterado de Integer para o tipo TImageIndex. TImageIndex é um Integer firmemente tipificado na unidade ImgList como:
TImageIndex = type Integer;
Isso só deverá causar problemas nos casos em que uma combinação exata de tipo for importante,
como na passagem de parâmetros var.
l
TCustomTreeview.CustomDrawItem( ) possui um novo parâmetro var chamado PaintImages, do tipo
Boolean. Se a sua aplicação substituir esse método, você terá de incluir esse parâmetro para que
ele seja compilado no Delphi 5.
l
l
A variável CoInitFlags em ComObj, que mantém os flags passados para CoInitializeEx( ) na unidade
ComServ, foi mudada para dar suporte apropriado à inicialização de servidores COM multithreaded. Agora, os flags COINIT_MULTITHREADED ou COINIT_APARTMENTTHREADED serão incluídos quando for
apropriado.
Se estiver chamando menus pop-up em resposta a mensagens WM_RBUTTONUP ou eventos OnMouseUp,
você poderá apresentar menus pop-up “duplos” ou nenhum menu pop-up ao compilar com o
Delphi 5. O Delphi 5 agora usa a mensagem de menu WM_CONTEXT para chamar os menus pop-up.
Problemas de desenvolvimento para Internet
Se você estiver desenvolvendo aplicações com suporte para Internet, temos algumas más notícias e outras boas:
l
l
O componente TWebBrowser, que encapsula o controle ActiveX do Microsoft Internet Explorer,
substituiu o componente THTML de Netmsters. Embora o controle TWebBrowser tenha muito mais recursos, você precisa reescrever muita coisa se usou THTML, pois a interface é totalmente diferente.
Se você não quiser reescrever seu código, poderá retornar ao controle antigo importando o arquivo HTML.OCX a partir do diretório \Info\Extras\NetManage, no CD-ROM do Delphi 5.
Os pacotes agora são aceitos quando se cria DLLs ISAPI e NSAPI. Você pode aproveitar esse
novo suporte substituindo HTTPApp na sua cláusula uses pelo WebBroker.
Problemas de banco de dados
Existem alguns problemas de banco de dados que poderão surgir quando você migrar para o Delphi 5.
Estes envolvem alguma renomeação de símbolos existentes e a nova arquitetura do MIDAS:
l
l
O tipo do evento TDatabase.OnLogin foi renomeado de TLoginEvent para TDatabaseLoginEvent. Isso
provavelmente não causará problemas, mas você poderá ter dificuldades se estiver criando e
atribuindo algo para OnLogin no código.
As rotinas globais FMTBCDToCurr( ) e CurrToFMTBCD( ) foram substituídas pelas novas rotinas BCDToCurr e CurrToBCD (e os métodos protegidos correspondentes em TDataSet foram substituídos pelo
método protegido e não-documentado DataConvert).
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
175
l
O MIDAS sofreu algumas mudanças significativas entre o Delphi 4 e 5. Consulte o Capítulo 32,
para obter informações sobre as mudanças, novos recursos e como transportar suas aplicações
em MIDAS para o Delphi 5.
Migração do Delphi 3
Embora não haja muitos problemas de compatibilidade entre o Delphi 3 as versões mais recentes, os poucos problemas que existem podem ser potencialmente mais problemáticos do que o transporte de qualquer outra versão anterior do Delphi para a seguinte. A maioria desses problemas gira em torno dos novos tipos e do comportamento alterado de certos tipos existentes.
Inteiros de 32 bits não sinalizados
O Delphi 4 introduziu o tipo LongWord, que é um inteiro de 32 bits não-sinalizado. Nas versões anteriores
do Delphi, o maior tipo de inteiro era um inteiro de 32 bits sinalizado. Por causa disso, muitos dos tipos
que você esperaria que fossem não-sinalizados, como DWORD, UINT, HResult, HWND, HINSTANCE e outros tipos de
alça, foram definidos simplesmente como Integers. No Delphi 4 em diante, esses tipos são redefinidos
como LongWords. Além disso, o tipo Cardinal, que anteriormente era uma tipo de subfaixa de 0..MaxInt, agora também é um LongWord. Embora toda essa coisa de LongWord não cause problemas na maioria das circunstâncias, existem alguns casos problemáticos que você precisa saber:
l
Integer e LongWord não são compatíveis em termos de parâmetro var. Portanto, você não pode passar um LongWord em um parâmetro var Integer, e vice-versa. O compilador indicará um erro nesse
caso, e você terá de mudar o tipo do parâmetro ou da variável, ou então forçar a mudança (typecast) para contornar o problema.
l
Constantes literais com o valor de $80000000 a $FFFFFFFF são consideradas LongWords. Você precisa
forçar a mudança de tal literal para um Integer se quiser atribuí-la a um tipo Integer. Veja um
exemplo:
var
I: Integer;
begin
I := Integer($FFFFFFFF);
l
De modo semelhante, qualquer literal com um valor negativo está fora do intervalo de um LongWord, e você terá de forçar a mudança para atribuir um literal negativo para um LongWord. Veja um
exemplo:
var
L: LongWord;
begin
L := LongWord(-1);
l
Se você misturar inteiros sinalizados e não-sinalizados em operações aritméticas e de comparação, o compilador promoverá automaticamente cada operando para Int64 a fim de realizar a aritmética ou comparação. Isso pode causar alguns bugs muito difíceis de se localizar. Considere o
código a seguir:
var
I: Integer;
D: DWORD;
begin
I := -1;
D := $FFFFFFFF;
if I = D then FazAlgumaCoisa;
176
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
No Delphi 3, FazAlgumaCoisa seria executado, pois -1 e $FFFFFFFF são o mesmo valor quando contidos em um Integer. No entanto, como o Delphi 4 em diante promove cada operando para Int64 a fim de
realizar a comparação mais precisa, o código gerado acaba comparando $FFFFFFFFFFFFFFFF a
$00000000FFFFFFFF, que definitivamente não é o que pretendíamos. Nesse caso, FazAlgumaCoisa não seria
executado.
DICA
O compilador no Delphi 4 em diante gera diversas novas sugestões, advertências e erros que lidam com
esse tipo de problema de compatibilidade e promoções implícitas de tipo. Não se esqueça de ativar as sugestões (hints) e as advertências (warnings) ao compilar, para permitir que o compilador o ajude a escrever
um código limpo.
Inteiro de 64 bits
O Delphi 4 também introduziu um novo tipo chamado Int64, que é um inteiro sinalizado de 64 bits. Esse
novo tipo agora é usado na RTL e na VCL, onde for apropriado. Por exemplo, as funções padrão Trunc( )
e Round( ) agora retornam Int64, e existem novas versões de IntToStr( ), IntToHex( ) e funções relacionadas
que tratam de Int64.
O tipo Real
A partir do Delphi 4, o tipo Real tornou-se um alias para o tipo Double. Nas versões anteriores do Delphi e
do Turbo Pascal, Real era um tipo de ponto flutuante de seis bytes. Isso não deverá impor qualquer problema para o seu código, a menos que você tenha Reals gravados em algum armazenamento externo
(como em um file ou record) com uma versão anterior ou que tenha um código que dependa da organização do Real na memória. Você pode forçar Real para ser o antigo tipo de 6 bytes incluindo a diretiva
{$REALCOMPATIBILITY ON} nas unidades em que você deseja usar o comportamento anterior. Se tudo o que
você precisa fazer é forçar um número limitado de instâncias do tipo Real a usarem o comportamento antigo, poderá usar o tipo Real48 em seu lugar.
Migração do Delphi 2
Você notará que um alto grau de compatibilidade entre o Delphi 2 e as versões seguintes significa uma
transição tranqüila para uma versão mais atualizada do Delphi. No entanto, foram feitas algumas mudanças desde o Delphi 2, tanto na linguagem quanto na VCL, e você precisará estar ciente disso para migrar para a versão mais recente e tirar todo o proveito do seu poder.
Mudanças nos tipos Booleanos
A implementação dos tipos Booleanos do Delphi 2 (Boolean, ByteBool, WordBool, LongBool) ditava que True
era o valor ordinal 1 e False era o valor ordinal 0. Para fornecer uma melhor compatibilidade com a API
do Win32, as implementações de ByteBool, WordBool e LongBool mudaram ligeiramente; o valor ordinal de
True agora é -1 ($FF, $FFFF e $FFFFFFFF, respectivamente). Observe que não houve qualquer mudança no
tipo Boolean. Essas mudanças possuem o potencial para causar problemas no seu código – mas somente se
você depender dos valores ordinais desses tipos. Por exemplo, considere a seguinte declaração:
var
A: array[LongBool] of Integer;
177
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Este código não causa qualquer problema no Delphi 2; ele declara um array[False..True] (ou [0..1])
of Integer, com um total de três elementos. Entretanto, no Delphi 3 em diante, essa declaração pode causar alguns resultados bastante inesperados. Como True é definido como $FFFFFFFF para LongBool, a declaração é transformada em array[0..$FFFFFFFF] of Integer, ou um array de 4 bilhões de Integers! Para evitar
esse problema, use o tipo Boolean como índice do array.
Ironicamente, essa mudança foi necessária porque um grande número de controles ActiveX e contêineres de controle (como o Visual Basic) testa BOOLs verificando o valor -1 em vez de testar um valor
igual ou diferente de zero.
DICA
Para ajudar a garantir a transportabilidade e evitar bugs, nunca escreva um código como este:
if BoolVar = True then ...
Em vez disso, sempre teste os tipos Boolean da seguinte forma:
if BoolVar then ...
ResourceString
Se a sua aplicação utiliza recursos de string, não deixe de usar ResourceStrings, conforme descrito no Capítulo 2. Embora isso não melhore a eficiência da sua aplicação em termos de tamanho ou velocidade,
tornará a tradução da linguagem mais fácil. ResourceStrings e o tópico relacionado de DLLs de recursos
são necessários para que se escreva aplicações exibindo diferentes strings de linguagem, mas todas rodando no mesmo pacote básico da VCL.
Mudanças na RTL
Várias mudanças feitas na biblioteca de runtime (RTL) após o Delphi 2 podem causar problemas quando
você migrar suas aplicações. Primeiramente, o significado da variável global HInstance mudou ligeiramente: HInstance contém a alça de instância da DLL, EXE ou pacote atual. Use a nova variável global MaintInstance quando quiser obter a alça de instância da aplicação principal.
A segunda mudança significativa pertence à global IsLibrary. No Delphi 2, você poderia verificar o
valor de IsLibrary para determinar se o seu código estava executando dentro do contexto de uma DLL ou
EXE. No entanto, IsLibrary não tem ciência do pacote, e por isso você não pode mais depender de IsLibrary para ser exato, dependendo se for chamada de um EXE, DLL ou um módulo dentro de um pacote.
Em vez disso, você precisa usar a global ModuleIsLib, que retorna True quando chamada dentro do contexto de uma DLL ou de um pacote. Você pode usar isso em combinação com a global ModuleIsPackage para
distinguir entre uma DLL e um pacote.
TCustomForm
A VCL do Delphi 3 introduziu uma nova classe entre TScrollingWinControl e TForm, chamada TCustomForm.
Por si só, isso não deverá impor um problema quando você migrar suas aplicações do Delphi 2; no entanto, se você tiver algum código que manipule instâncias de TForm, pode ter de atualizá-lo de modo que manipule TCustomForms em vez de TForms. Alguns dos exemplos são as chamadas para GetParentForm( ), ValidParentForm( ) e qualquer uso da classe TDesigner.
178
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
ATENÇÃO
A semântica para GetParentForm( ), ValidParentForm( ) e outros métodos da VCL que retornam ponteiros
Parent mudou ligeiramente desde o Delphi 2. Essas rotinas agora podem retornar nil, embora seu componente tenha um contexto da janela pai para desenhar. Por exemplo, quando o seu componente é encapsulado como um controle ActiveX, ele pode ter uma ParentWindow, mas não um controle Parent. Isso significa
que você precisa observar um código do Delphi 2 que faça isto:
with GetParentForm(xx) do ...
GetParentForm( ) agora pode retornar nil, dependendo de como seu componente está contido.
GetChildren( )
Criadores de componentes, saibam que a declaração de TComponent.GetChildren( ) mudou para o seguinte:
procedure GetChildren(Proc: TGetChildProc; Root: TComponent); dynamic;
O novo parâmetro Root mantém o proprietário da raiz do componente – ou seja, o componente obtido percorrendo-se a cadeia de proprietários de componente até Owner ser nil.
Servidores de Automation
O código necessário para a automação mudou bastante a partir do Delphi 2. O Capítulo 23 descreve o
processo de criação de servidores de Automation no Delphi 5. Em vez de descrever aqui os detalhes das
diferenças, basta dizer que você nunca deverá misturar o estilo de criação de servidores de Automation
do Delphi 2 com o estilo mais recente encontrado no Delphi 3 em diante.
No Delphi 2, a automação é facilitada pela infra-estrutura fornecida nas unidades OleAuto e Ole2. Essas
unidades estão presentes nas verões seguintes do Delphi apenas por compatibilidade, e você não deverá
usá-las para novos projetos. Agora, a mesma funcionalidade é fornecida nas unidades ComObj, ComServ e ActiveX. Você nunca deverá misturar as unidades anteriores com as mais recentes no mesmo projeto.
Migração do Delphi 1
A maioria das mudanças necessárias quando se passa das aplicações do Delphi 1 para uma versão mais recente é necessária por causa da natureza da programação sob um novo sistema operacional. Outras mudanças são necessárias por causa de melhorias na VCL e na linguagem Object Pascal. Algumas pessoas
prefeririam que as aplicações do Delphi 1 simplesmente rodassem sem modificação no Delphi 5. Se essa
for a sua opinião, lembre-se de que às vezes é preciso deixar um pouco o que ficou para trás para seguir
adiante. Você deverá descobrir que cada nova versão do Delphi consegue um equilíbrio excelente em relação a isso.
Além de explicar o que você precisa saber para migrar as aplicações do Delphi 1, esta seção também
oferece informações sobre a otimização do seu projeto para o Delphi de 32 bits e a manutenção do código compatível tanto com o Delphi de 16 quanto o de 32 bits.
Strings e caracteres
Em resposta a pedidos de clientes por uma string mais flexível, a Borland introduziu um novo tipo de
string no Delphi 2, conhecido como AnsiString. Entre outros benefícios, AnsiString aceita a criação de
strings com tamanho praticamente ilimitado. O Delphi 2 também introduziu novos tipos de string terminados com caracteres e nulos, para dar suporte total à internacionalização das aplicações usando o formato de duplo byte Unicode. O Delphi 3 levou ainda mais adiante o suporte para duplo byte, com a in-
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
179
trodução do tipo WideString. De longe, os problemas mais comuns que provavelmente surgirão na migração a partir do Delphi 1 são aqueles que lidam com o uso e a manipulação de strings.
Novos tipos de caractere
As strings, é claro, são compostas de caracteres, e por isso é importante que você entenda o comportamento dos tipos de caractere no Delphi 4 antes de aprender sobre os novos tipos de string. A mudança
mais importante até este ponto da linguagem é o novo tipo de caractere WideChar, introduzido no Delphi 2
para dar suporte aos tipos de caractere e string Unicode (ou “largos”). Além de WideChar, o Delphi 3 introduziu um novo nome de tipo, AnsiChar, que especifica um caracter normal de único byte.
O tipo AnsiChar é o mesmo que o tipo Char do Delphi 1. Ele é um valor de um byte que pode conter
qualquer um dos 256 valores. use AnsiChar apenas quando souber que o valor em questão terá sempre o
tamanho de um byte.
Use WideChar para um valor de caractere com o tamanho de dois bytes. WideChar existe por compatibilidade com o padrão de caractere Unicode, adotado pela API do Win32 para dar suporte à strings no idioma local. Devido ao seu tamanho de dois bytes, um Widechar pode conter qualquer um dos 65.536 valores possíveis, o suficiente até para os maiores alfabetos.
NOTA
A finalidade do Unicode é dar suporte ao uso de strings no idioma local para todo o sistema (e por toda a
rede global) sem perda de informações. Existem conjuntos de caracteres de um byte para a maioria dos
idiomas (exceto os idiomas orientais), mas a conversão entre os conjuntos de caracteres de idioma nem
sempre é reversível, de modo que a conversão gera perda de informações. O Unicode resolve isso eliminando a necessidade de converter entre codificações de conjunto de caracteres. Ele também focaliza o problema do idioma oriental oferecendo um conjunto de caracteres básico muito grande para codificar idiomas do tipo figuras-por-palavra, como o chinês.
Naturalmente, o tipo char ainda é válido no Delphi. Atualmente, os tipos char e AnsiChar são equivalentes. No entanto, a Borland reserva o direito de mudar a definição de char para WideChar em alguma versão futura do Delphi; você nunca deverá depender do tamanho de um char no seu código – sempre use
SizeOf( ) para determinar o tamanho real.
Novos tipos de string
Aqui estão listados os tipos de string aceitos no Delphi 5:
l
AnsiString (também conhecido como long string e huge string) é o novo tipo de string default para
o Object Pascal. Ele é composto de caracteres AnsiChar e permite tamanhos de até 1GB. Esse tipo
de string também é compatível com strings terminados em nulo. Ele é sempre alocado dinamicamente e gerenciado permanentemente.
l
ShortString é sinônimo do tipo de string default no Delphi 1.0. A capacidade de ShoftString é limi-
tada a 255 caracteres.
l
WideString é composto de caracteres WideChar e, assim como AnsiString, é alocado automaticamen-
te e gerenciado permanentemente. O Capítulo 2 contém uma discussão completa sobre esse este
e outros tipos de string.
l
PAnsiChar
l
PWideChar
é um ponteiro para uma string AnsiChar terminada em nulo.
é um ponteiro para uma string terminada em nulo com caracteres WideChar, compondo
uma string Unicode, ou de duplo byte.
180
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
é um ponteiro para uma string de Char terminada em nulo, que é totalmente compatível
com strings no estilo do C, usados nas funções da API do Windows. Esse tipo não mudou desde a
versão 1.0, e atualmente é definido como PAnsiChar.
PChar
Por default, as strings definidas no Delphi 2 em diante são AnsiStrings. Portanto, se você declarar
uma string, como vemos aqui, o compilador considera que você está criando uma AnsiString:
S: String;
// S é uma AnsiString
Como alternativa, você pode fazer com que variáveis declaradas como String sejam, em vez disso,
do tipo ShortString, usando a diretiva de compilador $H. Quando o valor da diretiva de compilador $H for
negativo, as variáveis String serão ShortStrings; quando o valor da diretiva for positivo (o default), as variáveis String serão AnsiStrings. O código a seguir demonstra esse comportamento:
var
{$H-}
S1: String;
{$H+}
S2: String;
// S1 é uma ShortString
// S2 é uma AnsiString
A exceção à regra de $H é que uma String declarada com um tamanho explícito (limitado a um máximo de 255 caracteres) é sempre uma ShortString:
var
S: String[63];
// Uma ShortString de até 63 caracteres
ATENÇÃO
Cuidado ao passar strings declarados em unidades com $H+ para funções e procedimentos definidos em
unidades com $H-, e vice-versa. Esses tipos de erros podem introduzir nas suas aplicações alguns bugs difíceis de serem localizados.
Definindo o tamanho da string
No Delphi 1, você poderia definir o tamanho de uma string atribuindo um valor ao byte 0, como mostramos aqui:
S[0] := 23;
{ define o byte de tamanho da string curta }
Isso era possível porque o tamanho máximo de uma string curta (255) poderia ser armazenado no
byte inicial. Como o tamanho máximo de uma string longa é 1GB, o tamanho obviamente não pode caber em um byte; o tamanho, portanto, é armazenado de modo diferente. Devido a esse problema, o
Delphi 2 introduziu um novo procedimento padrão, chamado SetLength( ), que você deverá usar para
definir o tamanho de uma string. SetLength( ) é definido da seguinte forma:
procedure SetLength(var S: String; NewLength: Integer);
SetLength( ) pode ser usado com strings curtas e longas. Se você quiser manter um conjunto de códi-
go-fonte para projetos de 16 bits do Delphi 1 e projeto do Delphi de 32 bits, poderá definir uma função
da seguinte forma, para os projetos de 16 bits do Delphi 1:
SetLength( ),
181
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
{$IFDEF WINDOWS}
{ para os projetos de 16 bits do Delphi 1 }
procedure SetLength(var S: String; NewLength: Integer);
begin
S[0] := Char(NewLength);
end;
{$ENDIF}
DICA
Para obter mais informações sobre o layout físico do tipo AnsiString, consulte o Capítulo 2.
Strings alocadas dinamicamente
No Delphi 1, é possível usar variáveis do tipo PString para implementar strings alocadas dinamicamente
alocando memória com o procedimento padrão NewStr( ), GetMem( ) ou AllocMem( ). No Delphi de 32 bits,
visto que strings longas são automaticamente alocadas de forma dinâmica a partir do heap, não é preciso
usar essas técnicas. Mude suas referências a PString para String (o código que cria e libera memória dinamicamente). Você também precisa remover qualquer desreferência da variável PString que apareça no
seu código. Considere o seguinte bloco de código em Delphi 1:
var
S1, S2: PString;
begin
S1 := AllocMem(SizeOf(S1^));
S1^ := ‘Give up the rock.’;
S2 := NewStr(S1^);
FreeMem(S1, SizeOf(S1^));
Edit1.Text := S2^;
DisposeStr(S2);
end;
Este código pode ser bastante simplificado (e otimizado) simplesmente aproveitando-se as strings
longas, como vemos aqui:
var
S1, S2: string;
begin
S1 := ‘Give up the rock.’;
S2 := S1;
Edit1.Text := S2;
end;
Indexando strings como arrays
Às vezes, você deseja acessar um certo caractere em uma string indexando a string como um array. Por
exemplo, a linha de código a seguir define o quinto caractere na string como A:
S[5] := ‘A’;
182
Esse tipo de operação ainda é perfeitamente legítima com strings longas, mas há um detalhe. Como
as strings longas são alocadas dinamicamente, você precisa garantir que o tamanho da string é maior ou
igual ao elemento de caractere que você tenta indexar. Por exemplo, o código a seguir não é válido:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
var
S: string;
begin
S[5] := ‘A’;
end;
// o espaço para S ainda não foi alocado!!
Entretanto, este código é perfeitamente válido:
var
S: string;
begin
S := ‘Hello’;
S[5] := ‘A’;
end;
// aloca espaço suficiente para a string
Este código também é válido:
var
S: string;
begin
SetLength(S, 5);
S[5] := ‘A’;
end;
// aloca 5 caracteres para S
ATENÇÃO
Você não pode assumir que um índice de caracter para uma string é a mesma coisa que o deslocamento de
byte na string. Por exemplo, WideStringvVar[5] acessa o quinto caractere (no deslocamento de byte 10).
Strings de terminação nula
Ao chamar as funções da API do Windows 3.1 no Delphi 1, os programadores precisavam estar cientes
da diferença entre o tipo String do Pascal e o tipo PChar no estilo do C (a string de terminação nula usada
no Windows). Strings longas facilitam bastante a chamada de funções da API do Win32. Elas são alocadas no heap e com certeza são terminadas com um caractere nulo. Por esses motivos, você pode simplesmente usar typecast em uma variável de string longa quando tiver de usá-la como um PChar terminado em
nulo em uma chamada de função da API do Win32. Imagine que você tenha o procedimento Foo( ), definido da seguinte forma:
procedure Foo(P: PChar);
No Delphi 1, você normalmente chamaria essa função da seguinte forma:
var
S: string;
P: PChar;
begin
S := ‘Hello world’;
P := AllocMem(255);
StrPCopy(P, S);
Foo(P);
FreeMem(P, 255);
end;
{ string curta do Pascal }
{ string de terminação nula }
{
{
{
{
{
inicializa S }
aloca P }
copia S para P }
chama Foo com P }
libera P }
Usando uma versão de 32 bits do Delphi, você pode chamar Foo( ) usando uma variável de string
longa com a seguinte sintaxe:
183
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
var
S: string;
// uma string longa termina com nulo
begin
S := ‘Hello World’;
Foo(PChar(S));
// totalmente compatível com o tipo PChar
end;
Isso significa que você pode otimizar seu código de 32 bits removendo os buffers temporários desnecessários para conter strings de terminação nula.
Strings de terminação nula como buffers
Um uso comum para as variáveis PChar é como um buffer a ser passado a uma função da API que preenche
o string de buffer com informações. Um exemplo clássico é a função GetWindowsDirectory( ) da API, definida na API do Win32 da seguinte forma:
function GetWindowsDirectory(lpBuffer: PChar; uSize: UINT): UINT;
Se o seu objetivo é armazenar o diretório do Windows em uma variável de string, um atalho comum
no Delphi 1 é passar o endereço do primeiro elemento da string como vemos a seguir:
var
S: string
begin
GetWindowsDirectory(@S[1], 254);
S[0] := Chr(StrLen(@S[1]));
end;
{ 254 = espaço para nulo }
{ ajusta o tamanho }
Essa técnica não funciona com strings longas por dois motivos. Primeiro, como já dissemos, você
precisa dar à string um tamanho inicial antes que qualquer espaço seja alocado. Segundo, visto que uma
string longa já é um ponteiro para o espaço no heap, o uso do operador @ efetivamente passa um ponteiro
de um ponteiro de um caracter – definitivamente não era o que você pretendia! Com strings longas, essa
técnica é facilitada forçando-se a string para um PChar:
var
S: string;
begin
SetLength(S, MAX_PATH + 1);
// aloca espaço
GetWindowsDirectory(PChar(S), MAX_PATH);
Setlength(S, StrLen(PChar(S))); // ajusta o tamanho
end;
PChars como strings
Visto que as strings longas podem ser usadas como PChars, é justo que o contrário seja verdadeiro. Strings
de terminação nula são compatíveis em termos de atribuição com strings longas. No Delphi 1, o código a
seguir exige uma chamada a StrPCopy( ):
var
S: string;
PL PChar;
begin
P := StrNew(‘Object Pascal’);
S := StrPas(P);
StrDispose(P);
end;
184
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Agora, você pode realizar a mesma coisa com uma atribuição simples, usando strings longas:
var
S: string;
P: PChar;
begin
P := StrNew(‘Object Pascal’);
S := P;
StrDispose(P);
end;
Da mesma forma, você também pode passar strings de terminação nula para funções e procedimentos que esperam receber parâmetros String. Suponha que o procedimento Bar( ) seja definido desta maneira:
procedure Bar(S: string);
Você pode chamar Bar( ) usando um PChar da seguinte maneira:
var
P: PChar;
begin
P := StrNew(‘Hello’);
Bar(P);
StrDispose(P);
end;
No entanto, essa técnica não funciona com procedimentos e funções que aceitam Strings por referência. Suponha que o procedimento Bar( ) fosse definido desta maneira:
procedure Bar(var S: string);
O código do exemplo que acabamos de apresentar não poderia ser usado para chamar Bar( ). Em
vez disso, você precisa definir uma string temporária para passar para Bar( ), como vemos aqui:
var
P: PChar;
TempStr: string;
begin
P := StrNew(‘Hello’);
TempStr := P;
Bar(TempStr);
StrDispose(P);
P := PChar(TempStr);
end;
O Delphi 2 introduziu um procedimento padrão chamado SetString( ), que permite copiar apenas
uma parte de um PChar em uma variável de string. SetString( ) tem a vantagem de trabalhar tanto com
strings longas quanto curtas. A definição de SetString( ) é a seguinte:
procedure SetString(var S: string; Buffer: PChar; Len: Integer);
185
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
ATENÇÃO
Seja cauteloso ao atribuir uma variável string a uma variável PChar quando a duração da variável PChar for
maior do que a da string. Visto que a string será desalocada quando sair do escopo, a variável PChar
apontará para lixo depois que a string sair do escopo. O código a seguir ilustra o problema:
var
P: PChar;
procedure Bar(var P: PChar);
var
S: String;
begin
S := ‘Olá Mundo’;
P := PChar(S);
// P é válido aqui
end;
// S está liberado aqui
procedure Foo;
begin
Bar(P);
ShowMessage(P);
end;
// PERIGO! P agora é inválida
Tamanho e intervalo da variável
Outro aspecto que pode surgir quando você migra seu código do Delphi é que alguns tipos mudam de tamanho (e portanto de intervalo) quando passam dos ambientes de 16 para 32 bits. As Tabelas 15.2 e 15.3
mostram as diferenças com relação a esses tipos.
Tabela 15.2 Diferenças no tamanho de variável
Tipo
Tamanho em 16 bits
Tamanho em 32 bits
Integer
Dois bytes
Quatro bytes
Cardinal
Dois bytes
Quatro bytes
String
256 bytes
Quatro bytes
Tabela 15.3 Diferenças no intervalo de variável
Tipo
Intervalo em 16 bits
Intervalo em 32 bits
Integer
-32.768..32.767
-2.147.483.648..2.147.483.647
Cardinal
0..65.536
Quatro bytes
String
255 caracteres
1GB de caracteres
Em sua maior parte, esses novos tamanhos de variável não possuem efeito sobre as suas aplicações.
Nas áreas em vez você depende do tamanhos em cada tipo, não se esqueça de usar a função SizeOf( ).
Além disso, se você tiver gravado qualquer um desses tipos em arquivos binários ou BLOBs no Delphi de
186 16 bits, deverá levar em consideração a mudança no tamanho ao ler esses dados de volta com o Delphi de
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
32 bits. Para essa finalidade, a Tabela 15.4 indica quais tipos do Delphi de 32 bits compartilham compatibilidade binária com os tipos do Delphi 1.
Tabela 15.4 Compatibilidade de tipo de variável
Tipo no Delphi de 16 bits
Tipo compatível no Delphi de 32 bits
Integer
SmallInt
Cardinal
Word
String
ShortString
Alinhamento de registro
Por default no Delphi de 32 bits, os registros são preenchidos de modo que fiquem alinhados corretamente; os dados de 32 bits (como Integer) são alinhados em limites de 32 bits (DWORD), e os dados de 16 bits
(como Word) são alinhados em endereços que são múltiplos exatos de 16.
type
TX = record
B: Byte;
L: Longint;
end;
NOTA
Os dados são alinhados em limites a fim de otimizar o desempenho do processador quando estiver acessando a memória.
Com as configurações default do compilador, a função SizeOf(TX) do Delphi 1 retorna 5; sob o Delphi 2 em diante, SizeOf(TX) retorna 8. Isso normalmente não é um problema; no entanto, pode ser um
problema se você não usar SizeOf( ) para determinar o tamanho do registro no seu código ou se tiver registros gravados em um arquivo binário.
O motivo para o Delphi de 32 bits alinhar elementos do registro em limites de DWORD é que isso permite que o compilador gere um código mais otimizado. Se houver um motivo para você querer impedir
esse comportamento, poderá usar o novo modificador Packed na declaração do tipo:
type
TX = packed record
B: Byte;
L: Longint;
end;
Com TX definido como um packed record, como no código apresentado, SizeOf(TX) agora retorna 5
no Delphi 2 em diante. Usando a diretiva $A- do compilador, você pode passar o default para registros
empacotados.
187
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DICA
Se você puder reordenar os campos de um registro para fazer com que os campos comecem em seus limites naturais (por exemplo, reordenar quatro campos de Byte de modo que todos venham antes de um campo Integer, em vez de dois em cada lado do Integer), poderá obter o máximo do empacotamento sem sofrer o custo do desempenho com dados não alinhados.
Matemática de 32 bits
Um problema muito mais sutil com relação ao tamanho da variável é que o compilador Delphi de 32 bits
realiza automaticamente uma matemática otimizada de 32 bits sobre todos os operandos em uma expressão (o Delphi 1 usava a matemática de 16 bits). Considere o seguinte código em Object Pascal:
var
L: longint;
w1, w2: word;
begin
w1 := $FFFE;
w2 := 5;
L := w1 + w2;
end;
Sob o Delphi 1.0, o valor de L no final dessa rotina é 3, pois o cálculo de w1 + w2 é armazenado como
um valor de 16 bits, e a operação gera a quebra do resultado.No Delphi 3, o valor de L no final dessa rotina é $10003, pois o cálculo w1 + w2 é realizado usando matemática de 32 bits. A repercussão da nova funcionalidade é que, se você usar e depender da lógica de verificação de intervalo para apanhar “erros” como
estes no Delphi 1.0, terá de usar algum outro método para localizar esses erros nas versões de 32 bits do
Delphi, pois o erro de verificação de intervalo não ocorrerá.
O tipo TDateTime
Para manter a compatibilidade com o OLE e com a API do Win32, o valor zero de uma variável TDateTime
mudou. Os valores de data começam em 00/00/0000 no Delphi 1; eles começam em 12/30/1899 no Delphi
de 32 bits. Embora essa mudança não afete as datas armazenadas em um campo de banco de dados, ela
afetará as datas binárias armazenadas em um arquivo binário ou campo BLOB do banco de dados.
Finalização da unidade
O Delphi 1 contém um procedimento chamado AddExitProc( ) e um ponteiro chamado ExitProc, que permitem definir um procedimento como contendo “código de saída” para uma determinada unidade. Sob
o Delphi de 32 bits, o processo de inclusão de um procedimento de saída em uma unidade é bastante simplificado com o acréscimo da seção finalization da unidade. Visado como um par para a seção initialization, o código em uma seção finalization com certeza será chamado quando a aplicação for fechada.
Embora esse tipo de mudança não seja necessária para a compilação de sua aplicação sob o Delphi de 32
bits, o código se torna muito mais claro.
NOTA
A conversão de ExitProcs para blocos finalization é obrigatória para pacotes. Os pacotes podem ser carregados e descarregados dinamicamente várias vezes durante o projeto, e ExitProcs não são chamadas
quando um pacote é descarregado dinamicamente pelo IDE. Portanto, seu código de limpeza precisa entrar nas seções finalization.
188
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Considere a seguinte seção inicialization e código de saída:
procedure MyExitProc;
begin
MyGlobalObject.Free;
end;
initialization
AddExitProc(MyExitProc);
MyGlobalObject := TGlobalObject.Create;
end.
aqui:
Esse código pode ser simplificado usando-se a seção finalization no Delphi de 32 bits, como vemos
initialization
MyGlobalObject := TGlobalObject.Create;
finalization
MyGlobalObject.Free;
end.
Linguagem Assembly
Como a linguagem Assembly depende bastante da plataforma para a qual é escrita, a linguagem Assembly
de 16 bits embutida nas aplicações do Delphi 1 não funcionará no Delphi de 32 bits. Você precisa reescrever tais rotinas usando a linguagem Assembly de 32 bits.
Além disso, certas interrupções podem não ser aceitas no Win32. Um exemplo de interrupções que
não são mais aceitas no Win32 é o conjunto de funções da DPMI (DOS Protected Mode Interface), fornecidas sob a interrupção $31. Em alguns casos, as funções e os procedimentos da API do Win32 tomam o
lugar das interrupções (as novas funções de I/O de arquivo do Win32 são um exemplo). Se a sua aplicação utiliza interrupções, consulte a documentação do Win32 para verificar as alternativas para o seu caso
específico.
Além do mais, o código hexadecimal em linha não é mais aceito no compilador Delphi de 32 bits. Se
você tiver quaisquer rotinas que usam código em linha, substitua-os pelas rotinas em linguagem
Assembly de 32 bits.
Convenções de chamada
O Delphi 1 pode usar a convenção de chamada cdecl ou pascal para passar parâmetros e limpar a pilha
nas chamadas de função e procedimento. A convenção de chamada default para o Delphi 1 é pascal.
O Delphi 2 introduziu diretivas representando duas novas convenções de chamada: register e
stdcall. A convenção de chamada register é o default para o Delphi 2 e 3, oferecendo um desempenho
mais rápido. Esse método exige que os três primeiros parâmetros de 32 bits sejam passados nos registradores eax, edx e ecx, respectivamente. Os parâmetros restantes usam a convenção de chamada pascal. A
convenção de chamada stdcall é uma mistura de pascal e cdecl, pois os parâmetros são passados usando a
convenção cdecl, mas a pilha é limpa usando a convenção pascal.
O Delphi 3 introduziu uma nova diretiva de procedimento chamada safecall. safecall acompanha a
convenção stdcall para a passagem de parâmetros e também permite que os erros COM sejam tratados
de uma maneira mais apropriada ao Delphi. A maioria das funções COM retorna valores HRESULT como
erros, enquanto o modo preferido de tratamento de erros no Delphi é através do uso do tratamento estruturado de exceções. Quando você chama uma função safecall a partir do Delphi, o valor de retorno
HRESULT da função é convertido para uma exceção que você mesmo poderá tratar. Ao implementar uma
função safecall no Delphi, quaisquer exceções levantadas na função serão convertidas para um valor
HRESULT, que é retornado a quem chamou.
189
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
NOTA
Embora as funções e os procedimentos na API de 16 bits do Windows usem a convenção de chamada pascal, as funções e os procedimentos da API do Win32 utilizam a convenção stdcall. Conseqüentemente, se
você tiver quaisquer funções de callback no seu código, estas também usam a convenção de chamada
stdcall. Considere a seguinte callback, idealizada para uso com a função EnumWindows( ) da API sob o
Windows de 16 bits:
function EnumWindowsProc(Handle: hwnd; lParam: Longint): BOOL; export;
Ela é definida da seguinte forma para o Windows de 32 bits:
function EnumWindowsProc(Handle: hwnd; lParam: Longint): BOOL; stdcall;
DLLs (Dynamic Link Libraries)
A criação e o uso de DLLs funciona da mesma maneira no Delphi de 32 bits e no Delphi 1, embora existam algumas pequenas diferenças. Algumas delas estão listadas a seguir:
l
l
l
Devido ao modelo de memória plano do Win32, a diretiva export (necessária para funções de
callback e da DLL no Delphi 1) é desnecessária nas versões seguintes. Ela é simplesmente ignorada pelo compilador.
Se você estiver escrevendo uma DLL que pretende compartilhar com os executáveis escritos com
outras ferramentas de desenvolvimento, é bom usar a diretiva stdcall para alcançar o máximo de
compatibilidade.
O modo preferido de exportar funções em uma DLL do Win32 é por nome (e não por valor ordinal). O exemplo a seguir exporta funções por ordinal, ao modo do Delphi 1:
function AlgumaFunção: integer; export;
begin
.
.
.
end;
procedure AlgumProcedimento; export;
begin
.
.
.
end;
exports
AlgumaFunção index 1,
AlgumProcedimento index 2;
Aqui estão as mesmas funções exportadas por nome, ao modo do Delphi de 32 bits:
190
function AlgumaFunção: integer; stdcall;
begin
.
.
.
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
procedure AlgumProcedimento; stdcall;
begin
.
.
.
end;
exports
AlgumaFunção name ‘AlgumaFunção’,
AlgumProcedimento name ‘AlgumProcedimento’;
l
l
l
Os nomes exportados diferenciam maiúsculas de minúsculas. Você precisa usar as letras corretas
ao importar funções por nome e quando chamar GetProcAddress( ).
Ao importar uma função ou procedimento e especificar o nome da biblioteca depois da diretiva
externa, a extensão do arquivo pode ser incluída. Se nenhuma extensão for especificada, .DLL
será considerado por default.
Sob o Windows 3.x, uma DLL na memória possui apenas um segmento de dados compartilhado
por todas as instâncias da DLL. Portanto, se as aplicações A e B carregarem a DLL C, as mudanças feitas nas variáveis globais da DLL C a partir da aplicação A serão visíveis à aplicação B, e o
inverso também é verdadeiro.
DICA
Sob o Win32, cada DLL recebe seu próprio segmento de dados, de modo que as mudanças feitas nos dados globais da DLL em um programa não são visíveis a outro programa.
Veja no Capítulo 9 mais informações sobre o comportamento de DLLs sob o Win32.
Mudanças no sistema operacional Windows
Em diversas áreas, as mudanças na arquitetura de 32 bits do Windows podem ter um impacto sobre o código escrito no Delphi. Estas incluem as mudanças resultantes do modelo de memória de 32 bits, mudanças nos formatos dos recursos, recursos sem suporte e mudanças na própria API do Windows.
Espaço de endereços de 32 bits
O Win32 oferece um espaço de endereço plano de 4GB para a sua aplicação. O termo plano (ou flat) significa que todos os registradores de segmento contêm o mesmo valor e que a definição de um ponteiro é
um deslocamento dentro desse espaço de 4GB. Por causa disso, qualquer código nas suas aplicações do
Delphi 1 que dependa do conceito de um ponteiro consistindo em um seletor e deslocamento deve ser
reescrito para acomodar a nova arquitetura.
Os elementos da biblioteca de runtime do Delphi 1 a seguir são específicos de ponteiro de 16 bits, e
não estão na biblioteca de runtime do Delphi de 32 bits: DSeg, SSeg, CSeg, Seg, Ofs e SPtr.
Devido ao modo em que o Win32 utiliza um arquivo de paginação no disco rígido para simular a
RAM sob demanda, as funções MemAvail( ) e MaxAvail( ) do Delphi 1.0 não são mais úteis para o cálculo
da memória disponível. Se você tiver que obter essa informação no Delphi de 32 bits, use a função GetHeapStatus( ) da RTL do Delphi, de é definida da seguinte forma:
funcion GetHeapStatus: THeapStatus;
O registro THeapStatus foi elaborado
para fornecer informações (em bytes) sobre o status do heap
para o seu processo. Esse registro é definido da seguinte forma:
191
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
type
THeapStatus = record
TotalAddrSpace: Cardinal;
TotalUncommitted: Cardinal;
TotalCommitted: Cardinal;
TotalAllocated: Cardinal;
TotalFree: Cardinal;
FreeSmall: Cardinal;
FreeBig: Cardinal;
Unused: Cardinal;
Overhead: Cardinal;
HeapErrorCode: Cardinal;
end;
NOTA
Novamente, devido à natureza do Win32 ser tal que a quantidade de memória “livre” possui pouco significado, a maioria dos usuários achará que o campo TotalAllocated (que indica quanta memória de heap foi
alocada pelo processo atual) é mais útil para fins de depuração.
Para obter mais informações sobre os detalhes internos do sistema operacional Win32, consulte o Capítulo 3.
Recursos de 32 bits
Se você possuir quaisquer recursos (arquivos RES ou DCR) que vincule à sua aplicação ou que use com
um componente, deverá criar versões de 32 bits desses arquivos antes que possa usá-los com o Delphi de
32 bits. Normalmente, essa é uma simples questão de usar o Image Editor incluído ou um editor de recursos separado (como o Resource Workshop) para salvar o arquivo de recursos em um formato compatível com 32 bits.
Controles VBX
Já que a Microsoft não tem mais suporte para controles VBX (que são inerentemente controles de 16
bits) nos aplicativos de 32 bits para Windows 95 e Windows NT, eles não são aceitos no Delphi de 32
bits. Os controles ActiveX (OCXs) efetivamente substituem os controles VBX nas plataformas de 32 bits.
Se você quiser migrar uma aplicação do Delphi 1.0 que utiliza controles VBX, entre em contato com o
fornecedor do VBX para obter um controle ActiveX de 32 bits equivalente.
Mudanças nas funções da API do Windows
Algumas APIs ou recursos do Windows mudaram do Windows 3.1 para o Win32. Algumas funções da
API de 16 bits não existem mais no Win32, algumas funções são obsoletas mas continuam a existir por
questão de compatibilidade e algumas aceitam parâmetros diferentes ou retornam tipos ou valores diferentes. As Tabelas 15.5 e 15.6 listam essas funções. Para obter a documentação completa sobre essas funções, consulte a ajuda on-line da API do Win32, que vem com o Delphi.
192
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela 15.5 Funções obsoletas da API do Windows 3.x
Função do Windows 3.x
Substituta no Win32
OpenComm( )
CreateFile( )
CloseComm( )
CloseHandle( )
FlushComm( )
PurgeComm( )
GetCommError( )
ClearCommError( )
ReadComm( )
ReadFile( )
WriteComm( )
WriteFile( )
UngetCommChar( )
N/D
DlgDirSelect( )
DlgDirSelectEx( )
DlgDirSelectComboBox( )
DlgDirSelectComboBoxEx( )
GetBitmapDimension( )
GetBitmapDimensionEx( )
SetBitmapDimension( )
SetBitmapDimensionEx( )
GetBrushOrg( )
GetBrushOrgEx( )
GetAspectRatioFilter( )
GetAspectRatioFilterEx( )
GetTextExtent( )
GetTextExtentPoint( )
GetViewportExt( )
GetViewportExtEx( )
GetViewportOrg( )
GetViewportOrgEx( )
GetWindowExt( )
GetWindowExtEx( )
GetWindowOrg( )
GetWindowOrgEx( )
OffsetViewportOrg( )
OffsetViewportOrgEx( )
OffSetWindowOrg( )
OffSetWindowOrgEx( )
ScaleViewportExt( )
ScaleViewportExtEx( )
ScaleWindowExt( )
ScaleWindowExtEx( )
SetViewportExt( )
SetViewportExtEx( )
SetViewportOrg( )
SetViewportOrgEx( )
SetWindowExt( )
SetWindowExtEx( )
SetWindowOrg( )
SetWindowOrgEx( )
GetMetafileBits( )
GetMetafileBitsEx( )
SetMetafileBits( )
SetMetafileBitsEx( )
GetCurrentPosition( )
GetCurrentPositionEx( )
MoveTo( )
MoveToEx( )
DeviceCapabilities( )
DeviceCapabilitiesEx( )
DeviceMode( )
DeviceModeEx( )
ExtDeviceMode( )
ExtDeviceModeEx( )
FreeSelector( )
N/A
AllocSelector( )
N/A
ChangeSelector( )
N/A
GetCodeInfo( )
N/A
GetCurrentPDB( )
GetCommandLine( ) e/ou GetEnvironmentStrings( )
GlobalDOSAlloc( )
N/A
GlobalDOSFree( )
N/A
SwitchStackBack( )
N/A
193
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela 15.5 Continuação
Função do Windows 3.x
Substituta no Win32
SwitchStackTo( )
N/A
GetEnvironment( )
(Funções de I/O de arquivo do Win32)
SetEnvironment( )
(Funções de I/O de arquivo do Win32)
ValidateCodeSegments( )
N/A
ValidateFreeSpaces( )
N/A
GetInstanceData( )
N/A
GetKBCodePage( )
N/A
GetModuleUsage( )
N/A
Yeld( )
WaitMessage( ) e/ou Sleep( )
AccessResource( )
N/A
AllocResource( )
N/A
SetResourceHandler( )
N/A
AllocDSToCSAlias( )
N/A
GetCodeHandle( )
N/A
LockData( )
N/A
UnlockData( )
N/A
GlobalNotify( )
N/A
GlobalPageLock( )
VirtualLock( )
Tabela 15.6 Funções em compatibilidade com a API Win32
Função do Windows 3.x
Substituta no Win32
DefineHandleTable( )
N/D
MakeProcInstance( )
N/D
FreeProcInstance( )
N/D
GetFreeSpace( )
GlobalMemoryStatus( )
GlobalCompact( )
N/D
GlobalFix( )
N/D
GlobalUnfix( )
N/D
GlobalWire( )
N/D
GlobalUnwire( )
N/D
LocalCOmpact( )
N/D
LocalShrink( )
N/D
LockSegment( )
N/D
UnlockSegment( )
N/D
SetSwapAreaSize( )
N/D
194
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Projetos concorrentes de 16 e 32 bits
Esta seção oferece algumas orientações para o desenvolvimento de projetos que são compilados sob o
Delphi 1 de 16 bits ou em qualquer versão de 32 bits do Delphi. Embora você possa seguir as instruções
esboçadas neste capítulo para obter compatibilidade de código-fonte, aqui estão outras indicações para
ajudá-lo no seu trabalho:
l
l
l
l
A condicional WINDOWS é definida pelo compilador sob o Delphi 1; a condicional WIN32 é definida
sob as versões de 32 bits do Delphi. Você pode usar essas definições para realizar uma compilação condicional com as diretivas {$IFDEF WINDOWS} e {$IFDEF WIN32}.
Evite o uso de qualquer componente ou recurso no Delphi de 32 bits que não seja aceito pelo Windows 3.1 ou Delphi 1 se você quiser recompilar com o Delphi 1 para uma aplicação de 16 bits. Por
exemplo, evite o uso de componentes do Win95 e recursos como multithreading, que não estão
disponíveis no Windows 3.1. O modo mais fácil de garantir a compatibilidade com os projetos visados para o Delphi 1 e as versões de 32 bits do Delphi é desenvolver o projeto no Delphi 1 e recompilá-lo com o Delphi de 32 bits para alcançar um desempenho otimizado em 32 bits.
Tenha cuidado com as diferenças entre as APIs. Se você tiver de usar um procedimento ou função da API que esteja implementado de modo diferente nas duas plataformas, não se esqueça de
usar as definições condicionais WINDOWS e WIN32.
Cada nova versão geralmente acrescenta mais propriedades ou diferentes propriedades daquelas
encontradas na versão anterior. Isso significa, por exemplo, que quando os componentes da versão 5 são salvos em um arquivo DFM, essas novas propriedades também são escritas. Embora
normalmente seja possível “ignorar” os erros que ocorrem quando se carrega projetos com essas
propriedades no Delphi 1, uma solução mais favorável é manter dois conjuntos separados de arquivos DFM, um para cada plataforma.
Win32s
Outra opção para se aproveitar um único código básico no Windows de 16 e de 32 bits é tentar
executar sua aplicação Delphi de 32 bits sob o Win32s. Win32s é um acréscimo ao Windows 3.x, que
permite que um subconjunto da API do Win32 funcione no Windows de 16 bits. Uma grande desvantagem desse método é que muitos recursos do Win32, como threads, não estão disponíveis no Win32s
(isso impede que você use o Borland Database Engine nessa circunstância, pois o BDE utiliza threads).
Se você escolher esse caminho, também deverá se lembrar de que o Win32s não é uma plataforma
aceita oficialmente para o Delphi de 32 bits, e por isso você estará sozinho se as coisas não funcionarem exatamente como esperava.
Resumo
Armado com as informações oferecidas neste capítulo, você poderá migrar seus projetos tranqüilamente
de qualquer versão anterior do Delphi para o Delphi 5. Além disso, com um pouco de trabalho, você poderá manter projetos que funcionem em várias versões do Delphi.
195
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Aplicações MDI
CAPÍTULO
16
NE STE CAP ÍT UL O
l
Criação de uma aplicação MDI
l
Trabalho com menus
l
Técnicas variadas de MDI
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 6 — 2ª PROVA
A Multiple Document Interface (interface de documentos múltiplos), também conhecida como MDI, foi
introduzida no Windows 2.0, no programa de planilha eletrônica Microsoft Excel. A MDI deu aos usuários do Excel a capacidade de trabalhar com mais de uma planilha ao mesmo tempo. Outros usuários da
MDI foram os programas Program Manager (Gerenciador de Programas) e File Manager (Gerenciador
de Arquivos) do Windows 3.1. O Borland Pascal for Windows é outra aplicação MDI.
Durante o desenvolvimento do Windows 95, muitos programadores tinham a impressão de que a
Microsoft iria eliminar os recursos de MDI. Para sua grande surpresa, a Microsoft manteve a MDI como
parte do Windows 95 e não falou mais nada sobre a sua intenção de livrar-se dela.
ATENÇÃO
A Microsoft reconheceu que a implementação da MDI do Windows possui falhas. Ela aconselhou aos programadores a não continuarem a criar aplicações no modelo MDI. Desde então, a Microsoft voltou a criar
aplicações no modelo da MDI, mas sem usar a implementação da MDI do Windows. Você ainda poderá
usar a MDI, mas fique sabendo que a implementação da MDI do Windows ainda possui falhas, e a Microsoft não possui planos para reparar esses problemas. O que apresentamos neste capítulo é uma implementação segura do modelo MDI.
O tratamento simultâneo de eventos entre vários formulários pode parecer difícil. Na programação
tradicional do Windows, você precisava conhecer bem a classe MDICLIENT do Windows, estruturas de dados
da MDI e as funções e mensagens adicionais específicas da MDI. Com o Delphi 5, a criação de aplicações
MDI ficou bastante simplificada. Quando você terminar este capítulo, terá uma base sólida para montar
aplicações MDI, que poderão ser facilmente expandidas para incluir outras técnicas mais avançadas.
Criação de uma aplicação MDI
Para criar aplicações MDI, você precisa ter familiaridade com os estilos de formulário fsMDIForm e
fsMDIChild, além de um pouco de metodologia de programação em MDI. As próximas seções apresentam
alguns cocneitos básicos referentes à MDI e mostram como a MDI funciona com formulários filho de
MDI especiais.
Entendendo os fundamentos da MDI
Para entender as aplicações MDI, primeiro você precisa entender como elas são construídas. A Figura
16.1 mostra uma aplicação MDI semelhante a uma que você criará neste capítulo.
FIGURA 16.1
A estrutura de uma aplicação MDI.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
197
Aqui estão as janelas envolvidas em uma aplicação MDI:
l
l
l
Janela de frame. A janela principal da aplicação. Ela possui título, barra de menu e menu do sistema. Os botões Minimizar, Maximizar e Fechar aparecem no seu canto superior direito. O espaço em branco dentro da janela do frame é conhecido como área do cliente, e é na realidade a
janela do cliente.
Janela do cliente. O gerenciador para aplicações MDI. A janela do cliente trata de todos os comandos específicos da MDI e gerencia as janelas filhas que residem em sua superfície – incluindo
o desenho das janelas MDI filhas. O cliente é criado automaticamente pela Visual Component
Library (VCL) quando você cria uma janela de frame.
Janelas filhas. As janelas MDI filhas são os seus documentos – arquivos de texto, planilhas, bitmaps (mapas de bits) e outros tipos de documento. As janelas filhas, assim como as janelas de frame, possuem título, menu do sistema, botões Minimizar, Maximizar e Fechar e, possivelmente,
um menu. É possível incluir um botão de ajuda em uma janela filha. O menu de uma janela filha é
combinado com o menu da janela de frame. As janelas filhas nunca movem para fora da área do
cliente.
O Windows 5 não exige que você se familiarize com as mensagens especiais da janela MDI. A janela
do cliente é responsável por gerenciar a funcionalidade da MDI, como as janelas filha em cascata e lado a
lado. Para colocar janelas filha em cascata usando o método tradicional, por exemplo, use a função SendMessage( ) da API do Windows para enviar a mensagem WM_MDICASCADE para a janela do cliente:
procedure TFrameForm.Cascade1Click(Sender: Tobject);
begin
SendMessage(ClientHandle, WM_MDICASCADE, 0, 0);
end;
No Delphi 5, basta chamar o método Cascade( ):
procedure TFrameForm.Cascade1Click(Sender: Tobject);
begin
cascade;
end;
As próximas seções mostram uma aplicação MDI completa cujas janelas MDI filhas possuem a funcionalidade de um editor de textos, um visualizador de arquivo do bitmap e um editor no formato rich
text. A finalidade dessa aplicação é mostrar como criar aplicações MDI cujas janelas filhas apresentam e
editam tipos diferentes de informações. Por exemplo, o editor de textos permite editar qualquer arquivo
baseado em texto. O editor de rich text permite editar arquivos de texto formatados como rich text
(.rtf). Finalmente, o visualizador de bitmap permite exibir qualquer arquivo de bitmap do Windows.
Também vamos mostrar como realizar algumas técnicas avançadas com MDI, usando a API do
Win32. Essas técnicas têm a ver principalmente com o gerenciamento de formulários MDI filhos em
uma aplicação baseada em MDI. Primeiro, discutiremos a criação dos formulários filhos e sua funcionalidade. Depois falaremos sobre o formulário principal.
O formulário filho
Como já dissemos, essa aplicação MDI contém três tipos de formulários filhos: TMdiEditForm, TMdiRTFForm e
TMdiBMPForm. Cada um desses três tipos descende de TMDIChildForm, que serve como classe básica. A próxima
seção descreve a classe básica TMDIChildForm. As seções seguintes explicam os três formulários filhos usados na aplicação MDI.
198
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
A classe básica TMDIChildForm
Os formulários filhos na aplicação MDI possuem alguma funcionalidade comum. Todos eles possuem o
mesmo menu File e sua propriedade FormStyle é definida como fsMDIChild. Além do mais, todos utilizam
um componente TToolBar. Derivando cada formulário filho de uma classe de formulário básica, você
pode evitar ter de redefinir esses valores para cada formulário. Definimos um formulário básico,
TMDIChildForm, conforme mostramos em MdiChildFrm.pas (consulte a Listagem 16.1).
Listagem 16.1 MdiChildFrm.pas: uma unidade definindo TMDIChildForm
unit MdiChildFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Menus, ComCtrls, ToolWin, ImgList;
type
TMDIChildForm = class(TForm)
(* Lista de componentes removida, consulte o código-fonte no CD. *)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure mmiExitClick(Sender: TObject);
procedure mmiCloseClick(Sender: TObject);
procedure mmiOpenClick(Sender: TObject);
procedure mmiNewClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormDeactivate(Sender: TObject);
end;
var
MDIChildForm: TMDIChildForm;
implementation
uses MainFrm, Printers;
{$R *.DFM}
procedure TMDIChildForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
{ Reatribui o pai da barra de ferramentas }
tlbMain.Parent := self;
{ Se for o último formulário filho apresentado, então torna visível
a barra de ferramentas do formulário principal }
if (MainForm.MDIChildCount = 1) then
MainForm.tlbMain.Visible := True
end;
procedure TMDIChildForm.mmiExitClick(Sender: TObject);
begin
MainForm.Close;
end;
199
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.1 Continuação
procedure TMDIChildForm.mmiCloseClick(Sender: TObject);
begin
Close;
end;
procedure TMDIChildForm.mmiOpenClick(Sender: TObject);
begin
MainForm.mmiOpenClick(nil);
end;
procedure TMDIChildForm.mmiNewClick(Sender: TObject);
begin
MainForm.mmiNewClick(nil);
end;
procedure TMDIChildForm.FormActivate(Sender: TObject);
begin
{ Quando o formulário se tornar ativo, oculta a barra de ferramentas do
formulário principal e atribui a barra de ferramentas deste formulário
filho ao formulário pai. Depois apresenta a barra de ferramentas deste
formulário filho. }
MainForm.tlbMain.Visible := False;
tlbMain.Parent := MainForm;
tlbMain.Visible := True;
end;
procedure TMDIChildForm.FormDeactivate(Sender:
begin
{ O formulário filho torna-se inativo quando
outro formulário filho é ativado. Oculta a
formulário para que a barra de ferramentas
fique visível. }
tlbMain.Visible := False;
end;
TObject);
é destruiído ou quando
barra de ferramentas deste
do próximo formulário
end.
NOTA
Observe que, por motivo de espaço, removemos as declarações de componente para a classe básica
TMdiChildForm do texto impresso.
200
TMDIChildForm contém manipuladores de evento para os itens de menu do seu menu principal, além
de alguns botões de ferramentas comuns. Na realidade, os botões de ferramentas estão simplesmente ligados ao manipulador de evento de seus itens de menu correspondentes. Alguns desses manipuladores
de evento chamam métodos no formulário principal. Por exemplo, observe que o manipulador de evento mmiNewClick( ) chama o manipulador de evento MainForm.mmiNewClick( ). TMainForm.mmiNewClick( ) contém
a funcionalidade para criar um novo formulário MDI filho. Você notará que existem outros manipuladores de evento, como mmiOpenClick( ) e mmiExitClick( ), que chamam os manipuladores de evento respectivos no formulário principal. Veremos a funcionalidade de TMainForm mais adiante, na seção “O formulário principal”.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Visto que cada filho MDI precisa ter a mesma funcionalidade, faz sentido colocar essa funcionalidade em uma classe básica da qual os formulários MDI filhos possam descender. Desse modo, os formulários MDI filhos não precisam definir esses mesmos métodos. Eles herdarão o menu principal e também
os componentes da barra de ferramentas que você encontra no formulário principal.
Observe no manipulador de evento TMDIChildForm.FormClose( ) que você define o parâmetro Action
como caFree para garnatir que a instância de TMDIChildForm será destruída quando fechada. Isso é feito porque os formulários MDI filhos não são fechados automaticamente quando você chama seu método Close( ). É preciso especificar, no manipulador de evento OnClose, o que deve ser feito com o formulário filho quando seu método Close( ) é chamado. O manipulador de evento OnClose do formulário filho passa
uma variável Action, do tipo TCloseAction, à qual você precisa atribuir um destes quatro valores possíveis:
l
caNone.
Não faz nada.
l
caHide.
Oculta o formulário, mas não o destrói.
l
caFree.
Libera o formulário.
l
caMinimize.
Minimiza o formulário (esse é o default).
é um tipo enumerado.
Quando um formulário é ativado, seu manipulador de evento OnActivate é chamado. Você precisa
realizar alguma lógica específica sempre que um formulário filho se torna ativo. Portanto, no manipulador de evento TMdiChildForm.FormActivate( ), você verá que tornamos invisível a barra de ferramentas do
formulário enquanto definimos a barra de ferramentas do formulário filho como visível. Também atribuímos o formulário principal como pai da barra de ferramentas do formulário filho, para que a barra de
ferramentas apareça no formulário principal e não no filho. Essa é uma das maneiras de dar uma barra de
ferramentas diferente ao formulário principal quando um tipo diferente de formulário filho MDI está
ativo. O manipulador de evento OnDeactivate simplesmente torna a barra de ferramentas do formulário filho invisível. Finalmente, o evento OnClose reatribui o formulário filho como pai da barra de ferramentas,
e se o formulário ativo for o único formulário filho, ele torna visível a barra de ferramentas do formulário principal. O efeito é que o formulário principal possui uma única barra de ferramentas com botões
que mudam dependendo do tipo do formulário filho ativo.
TCloseAction
O formulário do editor de textos
O formulário do editor de textos permite que o usuário carregue e edite qualquer arquivo de texto. Esse
formulário, TMdiEditForm. é herdado de TMdiChildForm. TMdiEditForm contém um componente TMemo alinhado
com a área do cliente.
TMdiEditForm também contém componentes TPrintDialog, TSaveDialog e TFontDialog.
TMdiEditForm não é um formulário criado automaticamente, e é removido da lista de formulários criados automaticamente na caixa de diálogo Project Options.
NOTA
Nenhum dos formulários do projeto MDI, exceto por TMainForm é criado automaticamente e, portanto, foram removidos da lista de formulários criados automaticamente. Esses formulários foram criados dinamicamente no código-fonte do projeto.
O código-fonte de TMdiEditForm aparece na Listagem 16.2.
201
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.2 MdiEditFrm.pas: uma unidade definindo TMdiEditForm
unit MdiEditFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Menus, ExtCtrls, Buttons, ComCtrls,
ToolWin, MdiChildFrm, ImgList;
type
TMdiEditForm = class(TMDIChildForm)
memMainMemo: TMemo;
SaveDialog: TSaveDialog;
FontDialog: TFontDialog;
mmiEdit: TMenuItem;
mmiSelectAll: TMenuItem;
N7: TMenuItem;
mmiDelete: TMenuItem;
mmiPaste: TMenuItem;
mmiCopy: TMenuItem;
mmiCut: TMenuItem;
mmiCharacter: TMenuItem;
mmiFont: TMenuItem;
N8: TMenuItem;
mmiWordWrap: TMenuItem;
N9: TMenuItem;
mmiCenter: TMenuItem;
mmiRight: TMenuItem;
mmiLeft: TMenuItem;
mmiUndo: TMenuItem;
N4: TMenuItem;
mmiBold: TMenuItem;
mmiItalic: TMenuItem;
mmiUnderline: TMenuItem;
PrintDialog: TPrintDialog;
{ Manipuladores de evento de arquivo }
procedure mmiSaveClick(Sender: TObject);
procedure mmiSaveAsClick(Sender: TObject);
{ Manipuladores de evento de edição }
procedure mmiCutClick(Sender: TObject);
procedure mmiCopyClick(Sender: TObject);
procedure mmiPasteClick(Sender: TObject);
procedure mmiDeleteClick(Sender: TObject);
procedure mmiUndoClick(Sender: TObject);
procedure mmiSelectAllClick(Sender: TObject);
202
{ Manipuladores de evento de caractere }
procedure CharAlignClick(Sender: TObject);
procedure mmiBoldClick(Sender: TObject);
procedure mmiItalicClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.2 Continuação
procedure mmiUnderlineClick(Sender: TObject);
procedure mmiWordWrapClick(Sender: TObject);
procedure mmiFontClick(Sender: TObject);
{ Manipuladores de evento de formulário }
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure mmiPrintClick(Sender: TObject);
public
{ Métodos definidos pelo usuário }
procedure OpenFile(FileName: String);
procedure SetButtons;
end;
var
MdiEditForm: TMdiEditForm;
implementation
uses Printers;
{$R *.DFM}
{ Manipuladores de evento de arquivo }
procedure TMdiEditForm.mmiSaveClick(Sender: TObject);
begin
inherited;
{ Se não houver um título, então ainda não existe um nome de arquivo.
Portanto, chama mmiSaveAsClick, pois ele apanha um nome de arquivo. }
if Caption = ‘’ then
mmiSaveAsClick(nil)
else begin
{ Salva no arquivo especificado pelo título (Caption) do formulário. }
memMainMemo.Lines.SaveToFile(Caption);
memMainMemo.Modified := false; // Falso porque o texto está salvo.
end;
end;
procedure TMdiEditForm.mmiSaveAsClick(Sender: TObject);
begin
inherited;
SaveDialog.FileName := Caption;
if SaveDialog.Execute then
begin
{ Define o título para o nome de arquivo especificado por SaveDialog1,
pois este pode ter mudado. }
Caption := SaveDialog.FileName;
mmiSaveClick(nil); // Salva o arquivo.
end;
end;
{ Manipuladores de evento de edição }
procedure TMdiEditForm.mmiCutClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
203
Listagem 16.2 Continuação
begin
inherited;
memMainMemo.CutToClipBoard;
end;
procedure TMdiEditForm.mmiCopyClick(Sender: TObject);
begin
inherited;
memMainMemo.CopyToClipBoard;
end;
procedure TMdiEditForm.mmiPasteClick(Sender: TObject);
begin
inherited;
memMainMemo.PasteFromClipBoard;
end;
procedure TMdiEditForm.mmiDeleteClick(Sender: TObject);
begin
inherited;
memMainMemo.ClearSelection;
end;
procedure TMdiEditForm.mmiUndoClick(Sender: TObject);
begin
inherited;
memMainMemo.Perform(EM_UNDO, 0, 0);
end;
procedure TMdiEditForm.mmiSelectAllClick(Sender: TObject);
begin
inherited;
memMainMemo.SelectAll;
end;
{ Manipuladores de evento de caractere }
procedure TMdiEditForm.CharAlignClick(Sender: TObject);
begin
inherited;
mmiLeft.Checked := false;
mmiRight.Checked := false;
mmiCenter.Checked := false;
{ TAlignment é definido pela VCL como:
TAlignment = (taLeftJustify, taRightJustify, taCenter);
Portanto, cada um dos itens do menu contém a propriedade Tag apropriada,
cujo valor representa um dos valores de TAlignment: 0, 1, 2 }
204
{ Se o menu chamou este manipulador de evento, define-o como marcado e
define o alinhamento para o memo }
if Sender is TMenuItem then
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.2 Continuação
begin
TMenuItem(Sender).Checked := true;
memMainMemo.Alignment := TAlignment(TMenuItem(Sender).Tag);
end
{ Se um TToolButton do formulário principal chamou este manipulador de
evento, define o alinhamento do memo e depois marca o TMenuItem
apropriado. }
else if Sender is TToolButton then
begin
memMainMemo.Alignment := TAlignment(TToolButton(Sender).Tag);
case memMainMemo.Alignment of
taLeftJustify:
mmiLeft.Checked := True;
taRightJustify: mmiRight.Checked := True;
taCenter:
mmiCenter.Checked := True;
end;
end;
SetButtons;
end;
procedure TMdiEditForm.mmiBoldClick(Sender: TObject);
begin
inherited;
if not mmiBold.Checked then
memMainMemo.Font.Style := memMainMemo.Font.Style + [fsBold]
else
memMainMemo.Font.Style := memMainMemo.Font.Style - [fsBold];
SetButtons;
end;
procedure TMdiEditForm.mmiItalicClick(Sender: TObject);
begin
inherited;
if not mmiItalic.Checked then
memMainMemo.Font.Style := memMainMemo.Font.Style + [fsItalic]
else
memMainMemo.Font.Style := memMainMemo.Font.Style - [fsItalic];
SetButtons;
end;
procedure TMdiEditForm.mmiUnderlineClick(Sender: TObject);
begin
inherited;
if not mmiUnderline.Checked then
memMainMemo.Font.Style := memMainMemo.Font.Style + [fsUnderline]
else
memMainMemo.Font.Style := memMainMemo.Font.Style - [fsUnderline];
SetButtons;
end;
procedure TMdiEditForm.mmiWordWrapClick(Sender: TObject);
begin
inherited;
with memMainMemo do
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
205
Listagem 16.2 Continuação
begin
WordWrap := not WordWrap;
{ Remove barras de rolagem se Memo1 tiver quebra de linha, pois não
são necessários. Caso contrário, cuida para que as barras de
rolagem estejam presentes. }
if WordWrap then
ScrollBars := ssVertical
else
ScrollBars := ssBoth;
mmiWordWrap.Checked := WordWrap;
end;
end;
procedure TMdiEditForm.mmiFontClick(Sender: TObject);
begin
inherited;
FontDialog.Font := memMainMemo.Font;
if FontDialog.Execute then
memMainMemo.Font := FontDialog.Font;
end;
{ Manipuladores de evento de formulário }
procedure TMdiEditForm.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
{ Este procedimento garante que o usuário salvou o conteúdo do memo se
ele foi modificado desde a última vez em que o arquivo foi salvo. }
const
CloseMsg = ‘’’%s’’ has been modified, Save?’;
var
MsgVal: integer;
FileName: string;
begin
inherited;
FileName := Caption;
if memMainMemo.Modified then
begin
MsgVal := MessageDlg(Format(CloseMsg, [FileName]), mtConfirmation, mbYesNoCancel, 0);
case MsgVal of
mrYes:
mmiSaveClick(Self);
mrCancel: CanClose := false;
end;
end;
end;
procedure TMdiEditForm.OpenFile(FileName: string);
begin
memMainMemo.Lines.LoadFromFile(FileName);
Caption := FileName;
end;
206
procedure TMdiEditForm.SetButtons;
{ Este procedimento garante que os itens de menu e botões no formulário
principal refletem com precisão as diversas configurações para o memo. }
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.2 Continuação
begin
mmiBold.Checked := fsBold in memMainMemo.Font.Style;
mmiItalic.Checked := fsItalic in memMainMemo.Font.Style;
mmiUnderLine.Checked := fsUnderline in memMainMemo.Font.Style;
tbBold.Down
tbItalic.Down
tbUnderline.Down
tbLAlign.Down
tbRAlign.Down
tbCAlign.Down
end;
:=
:=
:=
:=
:=
:=
mmiBold.Checked;
mmiItalic.Checked;
mmiUnderLine.Checked;
mmiLeft.Checked;
mmiRight.Checked;
mmiCenter.Checked;
procedure TMdiEditForm.mmiPrintClick(Sender: TObject);
var
i: integer;
PText: TextFile;
begin
inherited;
if PrintDialog.Execute then
begin
AssignPrn(PText);
Rewrite(PText);
try
Printer.Canvas.Font := memMainMemo.Font;
for i := 0 to memMainMemo.Lines.Count -1 do
writeln(PText, memMainMemo.Lines[i]);
finally
CloseFile(PText);
end;
end;
end;
end.
A maior parte dos métodos de TMdiEditForm são manipuladores de eventos para os diversos menus no
menu principal de TMdiEditForm, o mesmo menu herdado de TMdiChildForm. Observe também que os itens
de menu adicional foram incluídos ao menu principal e se aplicam especificamente a TMdiEditForm.
Você notará que não existem manipuladores de eventos para os itens de menu New, Open, Close e
Exit do menu File, pois já estão vinculados aos manipuladores de evento de TMdiChildForm.
Os manipuladores de eventos para os itens do menu Edit são todos métodos de única linha que interagem com o componente TMemo. Por exemplo, você poderá notar que os manipuladores de eventos mmiCutClick( ), mmiCopyClick( ) e mmiPasteClick( ) interagem com o Clipboard do Windows a fim de realizar as
operações recortar, copiar e colar. Os outros manipuladores de eventos de edição realizam diversas funções de edição no componente do memo que se referem à exclusão, limpeza e seleção de texto.
O menu Character aplica vários atributos de formatação ao memo.
Observe que armazenamos um valor exclusivo na propriedade Tag dos componentes de TToolButton
para definir o alinhamento de texto. Esse valor de Tag representa um valor no tipo enumerado TRAlignment. Esse valor é extraído do valor de Tag do componente TToolButton que chamou o manipulador de
evento para definir o alinhamento correto para o componente memo.
207
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Todos os itens de menu e botões da barra de ferramentas que definem alinhamento de texto estão
ligados ao manipulador de eventos CharAlignClick( ). É por isso que você precisa verificar e responder de
modo apropriado no manipulador de evento, dependendo de quem chamou o evento, TMenuItem ou TToolButton.
CharAlignClick( ) chama o método SetButtons( ), que define os diversos itens de menu e componentes de acordo, com base nos atributos do menu.
O manipulador de evento mmiWordWrapClick( ) simplesmente alterna o atributo de quebra de texto do
memo e depois a propriedade Checked para o item do menu. Esse método também especifica se o componente memo contém barras de rolagem, com base na sua capacidade de quebrar o texto.
O manipulador de evento mmiFontClick( ) chama um componente TFontDialog e aplica a fonte selecionada ao memo. Observe que, antes de iniciar o componente FontDialog, a propriedade Font é definida para
refletir a fonte do formulário, de modo que a fonte correta aparece na caixa de diálogo.
O manipulador de evento mmiSaveClick( ) chama o manipulador de evento mmiSaveAsClick( ) se não
houver um nome de arquivo. Esse é o caso em que o usuário cria um arquivo novo em vez de abrir um já
existente. Caso contrário, o conteúdo do memo será salvo no arquivo existente, especificado pela propriedade MdiEditForm.Caption. Observe que esse manipulador de evento também define memMainMemo.Modified como False. Modified é definido automaticamente como True sempre que o usuário muda o conteúdo de
um componente TMemo. No entanto, ele não é definido como False automaticamente sempre que seu conteúdo é salvo.
O método FormCloseQuery( ) é o manipulador de evento para o evento OnCloseQuery. Esse manipulador de evento avalia a propriedade memMainMemo.Modified quando o usuário tenta fechar o formulário. Se o
memo tiver sido modificado, o usuário é notificado e deve responder se deseja salvar o conteúdo do
memo.
O método público TMdiEditForm.OpenFile( ) carrega o arquivo especificado pelo parâmetro FileName e
coloca o conteúdo do arquivo na propriedade memMainMemo.Lens e depois define o Caption do formulário
para refletir esse nome de arquivo.
Isso completa a funcionalidade de TMdiEditForm. Os outros formulários são semelhantes em termos
de funcionalidade.
O formulário do editor de rich text
O editor de rich text permite que o usuário carregue e edite arquivos formatados como rich text. Esse
formulário, TMdiRtfForm, é derivado de TMDIChild. Ele contém um componente TRichEdit alinhado com a
área do cliente.
TMdiRtfForm e TMdiEditForm são praticamente idênticos, exceto que TMdiRtfForm contém um componente TRichEdit como seu editor; TMdiEditForm utiliza um componente TMemo. TMdiRtfForm difere do editor de
textos porque os atributos de texto aplicados ao componente TRichEdit afetam parágrafos ou o texto selecionado no componente TRichEdit; eles afetam o texto inteiro do componente TMemo.
O código-fonte para TMdiRtfForm aparece na Listagem 16.3.
Listagem 16.3 MdiRtfFrm.pas: uma unidade definidno TMdiRtfForm.
unit MdiRtfFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, MdiChildFrm, StdCtrls, ComCtrls,
ExtCtrls, Buttons, Menus, ToolWin, ImgList;
type
208
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.3 Continuação
TMdiRtfForm = class(TMDIChildForm)
reMain: TRichEdit;
FontDialog: TFontDialog;
SaveDialog: TSaveDialog;
mmiEdit: TMenuItem;
mmiSelectAll: TMenuItem;
N7: TMenuItem;
mmiPaste: TMenuItem;
mmiCopy: TMenuItem;
mmiCut: TMenuItem;
mmiCharacter: TMenuItem;
mmiFont: TMenuItem;
N8: TMenuItem;
mmiWordWrap: TMenuItem;
N9: TMenuItem;
mmiCenter: TMenuItem;
mmiRight: TMenuItem;
mmiLeft: TMenuItem;
mmiUndo: TMenuItem;
mmiDelete: TMenuItem;
N4: TMenuItem;
mmiBold: TMenuItem;
mmiItalic: TMenuItem;
mmiUnderline: TMenuItem;
{ Manipuladores de evento de arquivo }
procedure mmiSaveClick(Sender: TObject);
procedure mmiSaveAsClick(Sender: TObject);
{ Manipuladores de evento de edição }
procedure mmiCutClick(Sender: TObject);
procedure mmiCopyClick(Sender: TObject);
procedure mmiPasteClick(Sender: TObject);
procedure mmiDeleteClick(Sender: TObject);
procedure mmiUndoClick(Sender: TObject);
procedure mmiSelectAllClick(Sender: TObject);
{ Manipuladores de evento de caractere }
procedure CharAlignClick(Sender: TObject);
procedure mmiBoldClick(Sender: TObject);
procedure mmiItalicClick(Sender: TObject);
procedure mmiUnderlineClick(Sender: TObject);
procedure mmiWordWrapClick(Sender: TObject);
procedure mmiFontClick(Sender: TObject);
{ Manipuladores de evento de formulário }
procedure FormShow(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure reMainSelectionChange(Sender: TObject);
procedure mmiPrintClick(Sender: TObject);
public
{ Funções definidas pelo usuário. }
procedure OpenFile(FileName: String);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
209
Listagem 16.3 Continuação
function GetCurrentText: TTextAttributes;
procedure SetButtons;
end;
var
MdiRtfForm: TMdiRtfForm;
implementation
{$R *.DFM}
{ Manipuladores de evento de arquivo }
procedure TMdiRtfForm.mmiSaveClick(Sender: TObject);
begin
inherited;
reMain.Lines.SaveToFile(Caption);
end;
procedure TMdiRtfForm.mmiSaveAsClick(Sender: TObject);
begin
inherited;
SaveDialog.FileName := Caption;
if SaveDialog.Execute then
begin
Caption := SaveDialog.FileName;
mmiSaveClick(Sender);
end;
end;
{ Manipuladores de evento de edição }
procedure TMdiRtfForm.mmiCutClick(Sender: TObject);
begin
inherited;
reMain.CutToClipBoard;
end;
procedure TMdiRtfForm.mmiCopyClick(Sender: TObject);
begin
inherited;
reMain.CopyToClipBoard;
end;
procedure TMdiRtfForm.mmiPasteClick(Sender: TObject);
begin
inherited;
reMain.PasteFromClipBoard;
end;
210
procedure TMdiRtfForm.mmiDeleteClick(Sender: TObject);
begin
inherited;
reMain.ClearSelection;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.3 Continuação
procedure TMdiRtfForm.mmiUndoClick(Sender: TObject);
begin
inherited;
reMain.Perform(EM_UNDO, 0, 0);
end;
procedure TMdiRtfForm.mmiSelectAllClick(Sender: TObject);
begin
inherited;
reMain.SelectAll;
end;
{ Manipuladores de evento de caractere }
procedure TMdiRtfForm.CharAlignClick(Sender: TObject);
begin
inherited;
mmiLeft.Checked := false;
mmiRight.Checked := false;
mmiCenter.Checked := false;
{ Se um TMenuItem chamou este manipulador de evento, define sua
propriedade Checked como True e define o atributo para o parágrafo
ativo de RichEdit1. }
if Sender is TMenuItem then
begin
TMenuItem(Sender).Checked := true;
with reMain.Paragraph do
if mmiLeft.Checked then
Alignment := taLeftJustify
else if mmiRight.Checked then
Alignment := taRightJustify
else if mmiCenter.Checked then
Alignment := taCenter;
end
{ Se um dos botões de ferramenta do formulário principal chamou este
manipulador de evento, define o atributo para o parágrafo ativo de
reMain e define os itens do menu de alinhamento apropriadamente. }
else if Sender is TSpeedButton then
begin
reMain.Paragraph.Alignment :=
TAlignment(TSpeedButton(Sender).Tag);
case reMain.Paragraph.Alignment of
taLeftJustify: mmiLeft.Checked := True;
taRightJustify: mmiRight.Checked := True;
taCenter: mmiCenter.Checked := True;
end;
end;
SetButtons;
end;
procedure TMdiRtfForm.mmiBoldClick(Sender: TObject);
begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
211
Listagem 16.3 Continuação
inherited;
if not mmiBold.Checked then
GetCurrentText.Style := GetCurrentText.Style + [fsBold]
else
GetCurrentText.Style := GetCurrentText.Style - [fsBold];
end;
procedure TMdiRtfForm.mmiItalicClick(Sender: TObject);
begin
inherited;
if not mmiItalic.Checked then
GetCurrentText.Style := GetCurrentText.Style + [fsItalic]
else
GetCurrentText.Style := GetCurrentText.Style - [fsItalic];
end;
procedure TMdiRtfForm.mmiUnderlineClick(Sender: TObject);
begin
inherited;
if not mmiUnderline.Checked then
GetCurrentText.Style := GetCurrentText.Style + [fsUnderline]
else
GetCurrentText.Style := GetCurrentText.Style - [fsUnderline];
end;
procedure TMdiRtfForm.mmiWordWrapClick(Sender: TObject);
begin
inherited;
with reMain do
begin
{ Remove barras de rolagem se Memo1 tiver quebra de texto, pois não
são necessárias. Caso contrário, cuida para que as barras de
rolagem estejam presentes. }
WordWrap := not WordWrap;
if WordWrap then
ScrollBars := ssVertical
else
ScrollBars := ssNone;
mmiWordWrap.Checked := WordWrap;
end;
end;
procedure TMdiRtfForm.mmiFontClick(Sender: TObject);
begin
inherited;
FontDialog.Font.Assign(reMain.SelAttributes);
if FontDialog.Execute then
GetCurrentText.Assign(FontDialog.Font);
reMain.SetFocus;
end;
{ Manipuladores de evento de formulário }
212
procedure TMdiRtfForm.FormShow(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.3 Continuação
begin
inherited;
reMainSelectionChange(nil);
end;
procedure TMdiRtfForm.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
{ Este procedimento garante que o usuário salvou o conteúdo de reMain
se tiver sido modificado desde que o arquivo foi salvo pela última vez. }
const
CloseMsg = ‘’’%s’’ has been modified, Save?’;
var
MsgVal: integer;
FileName: string;
begin
inherited;
FileName := Caption;
if reMain.Modified then
begin
MsgVal := MessageDlg(Format(CloseMsg, [FileName]), mtConfirmation, mbYesNoCancel, 0);
case MsgVal of
mrYes: mmiSaveClick(Self);
mrCancel: CanClose := false;
end;
end;
end;
procedure TMdiRtfForm.reMainSelectionChange(Sender: TObject);
begin
inherited;
SetButtons;
end;
procedure TMdiRtfForm.OpenFile(FileName: String);
begin
reMain.Lines.LoadFromFile(FileName);
Caption := FileName;
end;
function TMdiRtfForm.GetCurrentText: TTextAttributes;
{ Este procedimento retorna os atributos de texto do parágrafo ativo ou
baseado no texto selecionado de reMain. }
begin
if reMain.SelLength > 0 then
Result := reMain.SelAttributes
else
Result := reMain.DefAttributes;
end;
procedure TMdiRtfForm.SetButtons;
{ Garante que os controles no formulário refletem os atributos atuais do
parágrafo, verificando os próprios atributos do parágrafo e definindo
os controles apropriadamente. }
begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
213
Listagem 16.3 Continuação
with reMain.Paragraph do
begin
mmiLeft.Checked := Alignment = taLeftJustify;
mmiRight.Checked := Alignment = taRightJustify;
mmiCenter.Checked := Alignment = taCenter;
end;
with reMain.SelAttributes do
begin
mmiBold.Checked := fsBold in Style;
mmiItalic.Checked := fsItalic in Style;
mmiUnderline.Checked := fsUnderline in Style;
end;
mmiWordWrap.Checked := reMain.WordWrap;
tbBold.Down
tbItalic.Down
tbUnderline.Down
tbLAlign.Down
tbRAlign.Down
tbCAlign.Down
end;
:=
:=
:=
:=
:=
:=
mmiBold.Checked;
mmiItalic.Checked;
mmiUnderline.Checked;
mmiLeft.Checked;
mmiRight.Checked;
mmiCenter.Checked;
procedure TMdiRtfForm.mmiPrintClick(Sender: TObject);
begin
inherited;
reMain.Print(Caption);
end;
end.
214
Assim como TMdiEditForm, a maioria dos métodos de TMdiRtfForm são manipuladores de eventos para
os diversos itens de menu e botões de ferramentas. Esses manipuladores de eventos são semelhantes aos
manipuladores de TMdiEditForm.
Os itens do menu File de TMdiRtfForm chamam os itens do menu File da classe básica de TMdiChildForm.
Lembre-se de que TMdiChildForm é o ancestral de TMdiRtfForm. Os manipuladores de evento, mmiSaveClick( )
e mmiSaveAsClick( ), chamam reMain.Lines.SaveToFile( ) para salvar o conteúdo de reMain.
Os manipuladores de eventos para os itens do menu Edit de TMdiRtfForm são métodos de única linha
semelhantes aos manipuladores de evento do menu Edit de TMdiEditForm. Os nomes de método são iguais
aos métodos de memo que realizam as mesmas operações.
Os itens do menu Character de TMdiRtfForm modificam o alinhamento de parágrafos ou texto selecionado dentro do componente TRichEdit (ao contrário do texto dentro do componente inteiro, como o comportamento com um componente TMemo). Se esses atributos são aplicados a um parágrafo ou ao texto selecionado depende do valor de retorno da função GetCurrentText( ). GetCurrentText( ) determina se algum
texto está selecionando, verificando o valor de TRichEdit.SelLength. Um valor zero indica que nenhum texto
está selecionado. A propriedade TRichEdit.SelAttributes refere-se a qualquer texto selecionado no componente TRichEdit. TRichEdit.DefAttributes refere-se ao parágrafo atual do componente TRichEdit.
O manipulador de evento mmiFontClick( ) permite que o usuário especifique os atributos de fonte
para um parágrafo. Observe que um parágrafo também pode se referir ao texto selecionado.
A quebra de texto é tratada da mesma forma que o componente TRichEdit e o componente TMemo no
editor de textos.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
O manipulador de evento TRichEdit.OnSelectionChange está disponível para permitir que o programador ofereça alguma funcionalidade sempre que a seleção do componente tiver sido alterada. Quando o
usuário mover o cursor de edição dentro do componente TRichEdit, o valor da propriedade SelStart do
componente mudará. Como essa ação faz com que o manipulador de evento OnSelectionChange seja chamado, foi incluído código para mudar o status dos vários componentes TMenuItem e TSpeedButton no formulário principal para refletir os atributos do texto à medida que o usuário rola o texto no componente
TRichEdit. Isso é necessário porque os atributos de texto no componente TRichEdit podem diferir; isso não
acontece com um componente TMemo, pois os atributos aplicados a um componente TMemo aplicam-se ao
componente inteiro.
Em questão de funcionalidade, o formulário do editor de rich text e o formulário do editor de textos são, em sua maior parte, muito semelhantes. A principal diferença é que o editor de rich text permite
que os usuários mudem os atributos de parágrafos separados ou texto selecionado; o editor de textos não
é capaz de fazer isso.
O visualizador de bitmap – o terceiro formulário MDI filho
O visualizador de bitmap permite que o usuário carregue e veja os arquivos de bitmap do Windows.
Assim como os dois outros formulários MDI filhos, o formulário do visualizador de bitmap, TMdiBmpForm,
é derivado da classe básica TMDIChildForm. Ele contém um componente TImage ajustado à área do cliente.
TMdiBmpForm contém apenas seu componente TMainMenu herdado. A Listagem 16.4 apresenta o código-fonte que define TMdiBmpForm.
Listagem 16.4 MdiBmpFrm.pas: uma unidade definindo TMdiBmpForm
unit MdiBmpFrm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs,
MdiChildFrm, ExtCtrls, Menus, Buttons, ComCtrls, ToolWin, ImgList;
type
TMdiBMPForm = class(TMDIChildForm)
mmiEdit: TMenuItem;
mmiCopy: TMenuItem;
mmiPaste: TMenuItem;
imgMain: TImage;
procedure mmiCopyClick(Sender: TObject);
procedure mmiPasteClick(Sender: TObject);
procedure mmiPrintClick(Sender: TObject);
public
procedure OpenFile(FileName: string);
end;
var
MdiBMPForm: TMdiBMPForm;
implementation
uses ClipBrd, Printers;
{$R *.DFM}
215
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.4 Continuação
procedure TMdiBMPForm.OpenFile(FileName: String);
begin
imgMain.Picture.LoadFromFile(FileName);
Caption := FileName;
end;
procedure TMdiBMPForm.mmiCopyClick(Sender: TObject);
begin
inherited;
ClipBoard.Assign(imgMain.Picture);
end;
procedure TMdiBMPForm.mmiPasteClick(Sender: TObject);
{ Este método copia o conteúdo do clipboard para imgMain }
begin
inherited;
// Copia conteúdo do clipboard para imgMain
imgMain.Picture.Assign(ClipBoard);
ClientWidth := imgMain.Picture.Width;
{ Ajusta a largura do cliente considerando barras de rolagem }
VertScrollBar.Range := imgMain.Picture.Height;
HorzScrollBar.Range := imgMain.Picture.Width;
end;
procedure TMdiBMPForm.mmiPrintClick(Sender: TObject);
begin
inherited;
with ImgMain.Picture.Bitmap do
begin
Printer.BeginDoc;
Printer.Canvas.StretchDraw(Canvas.ClipRect, imgMain.Picture.Bitmap);
Printer.EndDoc;
end; { with }
end;
end.
Não há tanto código para TMdiBmpForm quanto havia para os dois formatos anteriores. Os itens do menu
File chamam os manipuladores de eventos de TMdiChildForm assim como fazem os itens do menu File de TMdiEditForm e TMdiRtfForm. Os itens do menu Edit copiam e colam o bitmap usando o Clipboard do Windows, respectivamente, antes de chamar o método TImage.Picture.Assign( ) para atribuir os dados do Clipboard ao
componente TImage. O componente TImage reconhece os formatos CD_BITMAP e CF_PICTURE como bitmaps.
O Windows Clipboard
216
O Clipboard oferece o modo mais fácil para duas aplicações compartilharem informações. Ele
não é nada mais do que um bloco de memória global que o Windows mantém para qualquer aplicação acessar através de um conjunto específico de funções do Windows.
O Clipboard aceita vários formatos padrão, como texto, texto OEM, bitmaps e metafiles; ele também utiliza outros formatos especializados. Além disso, você pode estender o Clipboard para dar suporte aos formatos específicos da aplicação.
O Delphi 5 encapsula o Clipboard do Windows com a variável global Clipboard do tipo TClipBoard,
facilitando bastante o seu uso. A classe TClipBoard é explicada no Capítulo 17.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
O formulário principal
O formulário principal é o formulário com o qual o usuário trabalha inicialmente para criar ou alternar
entre os formulários MDI filhos. Esse formulário é corretamente chamado de MainForm, e serve como pai
para os formulários MDI filhos do editor de textos, visualizador de bitmap e editor de RTF.
TMainForm não é descendente de TMDIChildForm, como os outros formulários discutidos até este ponto
do capítulo. TMainForm possui o FormStyle de fsMDIForm (os outros três formulários herdaram o estilo
fsMDIChild de TMDIChild). TMainForm contém um componente TMainMenu e um componente TOpenDialog. TMainForm também contém uma barra de ferramentas que contém apenas um botão. O código-fonte de TMainForm aparece na Listagem 16.5.
Listagem 16.5 MdiMainForm.pas: unidade definindo TMainForm
unit MainFrm;
interface
uses
WinTypes, WinProcs, Classes, Graphics, Forms, Controls, Menus,
StdCtrls, Messages, Dialogs, SysUtils, ComCtrls,
ToolWin, ExtCtrls, Buttons, ImgList;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
OpenDialog: TOpenDialog;
mmiFile: TMenuItem;
mmiExit: TMenuItem;
N3: TMenuItem;
mmiOpen: TMenuItem;
mmiNew: TMenuItem;
mmiWindow: TMenuItem;
mmiArrangeIcons: TMenuItem;
mmiCascade: TMenuItem;
mmiTile: TMenuItem;
mmiCloseAll: TMenuItem;
tlbMain: TToolBar;
ilMain: TImageList;
tbFileOpen: TToolButton;
{ Manipuladores de evento de arquivo }
procedure mmiNewClick(Sender: TObject);
procedure mmiOpenClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
{ Manipuladores de evento de janela }
procedure mmiTileClick(Sender: TObject);
procedure mmiArrangeIconsClick(Sender: TObject);
procedure mmiCascadeClick(Sender: TObject);
procedure mmiCloseAllClick(Sender: TObject);
public
{ Métodos definidos pelo usuário }
procedure OpenTextFile(EditForm: TForm; Filename: string);
procedure OpenBMPFile(FileName: String);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
217
Listagem 16.5 Continuação
procedure OpenRTFFile(RTFForm: TForm; FileName: string);
end;
var
MainForm: TMainForm;
implementation
uses MDIBmpFrm, MdiEditFrm, MdiRtfFrm, FTypForm;
const
{ Define constantes representando
BMPExt
= ‘.BMP’; // Arquivo
TextExt
= ‘.TXT’; // Arquivo
RTFExt
= ‘.RTF’; // Arquivo
especificações de nome de arquivo }
de bitmap
de texto
em formato Rich Text
{$R *.DFM}
procedure TMainForm.mmiNewClick(Sender: TObject);
begin
{ Determina o tipo de arquivo que o usuário deseja abrir chamando a
função GetFileType. Chama o método apropriado com base no tipo de
arquivo informado. }
case GetFileType of
mrTXT: OpenTextFile(nil, ‘’); // Abre um arquivo de texto.
mrRTF: OpenRTFFile(nil, ‘’); // Abre um arquivo RTF.
mrBMP:
begin
{ Define o filtro default de OpenDialog1 para arquivos BMP. }
OpenDialog.FilterIndex := 2;
mmiOpenClick(nil);
end;
end;
end;
procedure TMainForm.mmiOpenClick(Sender: TObject);
var
Ext: string[4];
begin
{ Chama o método apropriado com base no tipo de arquivo selecionado em
OpenDialog1. }
if OpenDialog.Execute then
begin
{ Obtém a extensão do arquivo e a compara para determinar o tipo de
arquivo que o usuário está abrindo. Chama o método apropriado e
passa o nome do arquivo. }
Ext := ExtractFileExt(OpenDialog.FileName);
if CompareStr(UpperCase(Ext), TextExt) = 0 then
OpenTextFile(ActiveMDIChild, OpenDialog.FileName)
else if CompareStr(UpperCase(Ext), BMPExt) = 0 then
OpenBMPFile(OpenDialog.FileName)
else if CompareStr(UpperCase(Ext), RTFExt) = 0 then
OpenRTFFile(ActiveMDIChild, OpenDialog.FileName);
end;
218 end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.5 Continuação
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close;
end;
{ Manipuladores de evento de janela }
procedure TMainForm.mmiTileClick(Sender: TObject);
begin
Tile;
end;
procedure TMainForm.mmiArrangeIconsClick(Sender: TObject);
begin
ArrangeIcons;
end;
procedure TMainForm.mmiCascadeClick(Sender: TObject);
begin
Cascade;
end;
procedure TMainForm.mmiCloseAllClick(Sender: TObject);
var
i: integer;
begin
{ Fecha todos software formulários em ordem contrária à que aparece
na propriedade MDIChildren. }
for i := MdiChildCount - 1 downto 0 do
MDIChildren[i].Close;
end;
{ Métodos definidos pelo usuário }
procedure TMainForm.OpenTextFile(EditForm: TForm; FileName: string);
begin
{ Se EditForm for de um tipo TEditForm, então dá ao usuário a opção
de carregar conteúdo do arquivo neste formulário. Se não, cria uma
nova instância de TEditForm e carrega o arquivo nessa instância. }
if (EditForm <<|>> nil) and (EditForm is TMdiEditForm) then
if MessageDlg(‘Load file into current form?’, mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
begin
TMdiEditForm(EditForm).OpenFile(FileName);
Exit;
end;
{ Cria um novo TEditForm e chama seu método OpenFile( ). }
with TMdiEditForm.Create(self) do
if FileName <<|>> ‘’ then
OpenFile(FileName)
end;
procedure TMainForm.OpenRTFFile(RTFForm: TForm; FileName: string);
begin
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
219
Listagem 16.5 Continuação
{ Se RTFForm for de um tipo TRTFForm, então dá ao usuário a opção de
carregar o conteúdo do arquivo neste formulário. Se não, cria uma
nova instância de TRTFForm e carrega o arquivo nessa instância. }
if (RTFForm <<|>> nil) and (RTFForm is TMdiRTFForm) then
if MessageDlg(‘Load file into current form?’, mtConfirmation,
[mbYes, mbNo], 0) = mrYes then begin
(RTFForm as TMdiRTFForm).OpenFile(FileName);
Exit;
end;
{ Cria um novo TRTFForm e chama seu método OpenFile( ). }
with TMdiRTFForm.Create(self) do
if FileName < > ‘’ then
OpenFile(FileName);
end;
procedure TMainForm.OpenBMPFile(FileName: String);
begin
{ Cria uma nova instância de TBMPForm e carrega um arquivo BMP nela. }
with TMdiBmpForm.Create(self) do
OpenFile(FileName);
end;
end.
TMainForm utiliza outro formulário, FileTypeForm, do tipo TFileTypeForm. A Listagem 16.6 mostra o código-fonte desse formulário.
Listagem 16.6 A unidade FTypForm.pas definindo TFileTypeForm
unit FTypForm;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, ExtCtrls, Buttons;
const
mrTXT = mrYesToAll+1;
mrBMP = mrYesToAll+2;
mrRTF = mrYesToAll+3;
type
TFileTypeForm = class(TForm)
rgFormType: TRadioGroup;
btnOK: TButton;
procedure btnOkClick(Sender: TObject);
end;
220
var
FileTypeForm: TFileTypeForm;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.6 Continuação
function GetFileType: Integer;
implementation
function GetFileType: Integer;
{ Esta função retorna o tipo de arquivo selecionado pelo usuário, conforme
representado por uma das constantes definidas acima. }
begin
FileTypeForm := TFileTypeForm.Create(Application);
try
Result := FileTypeForm.ShowModal;
finally
FileTypeForm.Free;
end;
end;
{$R *.DFM}
procedure TFileTypeForm.btnOkClick(Sender: TObject);
begin
{ Retorna o resultado modal correto com base no tipo de arquivo
selecionado. }
case rgFormType.ItemIndex of
0: ModalResult := mrTXT;
1: ModalResult := mrRTF;
2: ModalResult := mrBMP;
end;
end;
end.
TFileTupeForm é usado para pedir do usuário um tipo de arquivo a ser criado. Esse formulário retorna
o ModalResult com base no botão de opção TRadioButton que o usuário selecionou para indicar o tipo do arquivo. A função GetFileType( ) cuida da criação, exibição e liberação da instância TFileTupeForm. Essa função retorna a propriedade TFileTupeForm.ModalResult. Esse formulário não é criado automaticamente, e foi
removido da lista de formulários criados automaticamente para o projeto.
A barra de ferramentas de TMainForm contém apenas um botão, que é usado para abrir o formulário
filho inicial. Quando um formulário filho é ativado, sua barra de ferramentas substitui a barra de ferramentas do formulário principal. Essa lógica é tratada pelo evento OnActivate do formulário filho. Os métodos públicos de TMainForm, OpenTextFile( ), OpenRTFFile( ) e OpenBMPFile( ), são chamados a partir do manipulador de evento TMainForm.mmiOpenClick( ), que é chamado sempre que o usuário seleciona o comando
File, Open do menu.
OpenTextFile( ) utiliza dois parâmetros: uma instância de TForm e um nome de arquivo. A instância de
TForm representa o formulário atualmente ativo para a aplicação. O motivo para passar essa instância de
TForm para o método OpenTextFile( ) é para que o método possa determinar se o TForm passado a ele é da
classe TMdiEditForm. Se for, é possível que o usuário esteja abrindo um arquivo de texto na instância de
TMdiEditForm existente, em vez de criar uma nova instância de TMdiEditForm. Se uma instância de TMdiEditForm for passada para esse método, o usuário deverá responder se deseja que o arquivo de texto seja colocado nesse parâmetro TForm. Se o usuário responder que não ou se o relatório TMdiEditForm for nil, uma
nova instância de TMdiEditForm será criada.
221
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
OpenRTFFile( ) opera da mesma forma que OpenTextFile( ), exceto que verifica se há uma classe
TRTFForm como formulário ativo representado pelo parâmetro TForm. A funcionalidade é a mesma.
OpenBMPFile( ) sempre considera que o usuário está abrindo um arquivo novo. Isso porque o TMdiBmpForm é apenas um visualizador, e não um editor. Se o formulário permitisse ao usuário editar uma imagem de bitmap, o método OpenBMPFile( ) funcionaria como OpenTextFile( ) e OpenRTFFile( ).
O manipulador de evento mmiNewClick( ) chama a função GetFileType( ) para apanhar um tipo de arquivo do usuário. Depois ele chama o método OpenXXXFile( ) apropriado com base no valor de retorno.
Se o arquivo for um arquivo .bmp, a propriedade OpenDialog.Filter será definida com o filtro BMP por default, e o método mmiCOpenClick será chamado, pois o usuário não está criando um novo arquivo .bmp, mas
estará abrindo um arquivo existente.
O manipulador de evento mmiOpenClick( ) chama OpenDialog e chama o método OpenXXXFile( ) apropriado. Observe que OpenTextFile( ) e OpenRTFFile( ) são passados à propriedade TMainForm.ActiveMDIChild
como primeiro parâmetro. ActiveMDIChild é o filho MDI que possui o foco atualmente. Lembre-se de que
esses dois métodos determinam se o usuário deseja abrir um arquivo em um formulário MDI filho já existente. Se não houver formulários ativos, ActiveMDIChild será nil. Se ActiveMDIChild estiver apontando para
um TMDIRTFForm e OpenTextFile( ) for chamado, OpenTextFile( ) ainda funcionará corretamente, devido a
esta instrução:
if (RTFForm < > nil) and (RTFForm is TMdiRTFForm) then
Essa instrução determina se ActiveMDIChild aponta para um TMdiRtfForm. Se não, um novo formulário
é criado.
O manipulador de evento mmiExitClick( ) chama TMainForm.Close( ); esse método não apenas fecha o formulário principal, mas também termina a aplicação. Se houver algum formulário filho aberto no momento
em que esse manipulador de evento for chamado, os formulários filhos também são fechados e destruídos.
Os manipuladores de eventos do menu Window são métodos de única linha que afetam o modo
como os formulários MDI filhos são orgnaizados na área do cliente do formulário principal. As Figuras
16.2 e 16.3 mostram formulários lado a lado (tiled) e em cascata (cascaded), respectivamente.
FIGURA 16.2
Formulários filhos lado a lado.
O método mmiArrangeIconsClick( ) simplesmente reorganiza os ícones na área do cliente do formulário principal, de modo que tenha um espaçamento igual e não se superponham.
O manipulador de evento mmiCloseAllClick( ) fecha todos os formulários MDI filhos abertos. O
loop que fecha os formulários filhos percorre todos os formulários em ordem inversa à que aparecem na
propriedade de array MDIChildren. A propriedade MDIChildren é uma propriedade de array com base zero,
contendo todos os filhos MDI ativos em uma aplicação. A propriedade MDIChildCount é o número de filhos
222 que estão ativos.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 16.3
Formulários filhos em cascata.
Isso termina a discussão sobre a funcionalidade da aplicação MDI. As próximas seções discutem algumas técnicas e alguns dos componentes usados com os diversos formulários da aplicação.
Trabalho com menus
O uso de menus em aplicações MDI não é mais difícil do que usá-los em qualquer outro tipo de aplicação. No entanto, existem algumas diferenças no modo como os menus funcionam em aplicações MDI.
As próximas seções mostram como uma aplicação MDI permite que seus formulários filhos compartilhem a mesma barra de menus usando um método chamado mesclagem de menu. Você também descobrirá como as aplicações não baseadas em MDI compartilham a mesma barra de menus.
Mesclando menus com aplicações MDI
Dê uma olhada no TMainMenu do TMdiEditForm. Com um clique duplo no ícone TMainMenu, você faz surgir o
editor de menus.
O menu principal de TMdiEditForm contém três itens de menu ao longo da barra de menus. Esses itens
são File, Edit e Character. Cada um desses itens de menu possui uma propriedade GroupIndex, que aparece
no Object Inspector quando você dá um clique no item do editor de menus. Observe que o item de menu
File possui um valor GoupIndex igual a 0. Os itens de menu Edit e Character possuem valores de GoupIndex
iguais a 1.
Observe que o menu principal de TMainForm possui dois itens de menu em sua barra de menus: File e
Window. Assim como em TMdiEditForm, o item de menu File de TMainForm possui um valor de GoupIndex igual
a 0. A propriedade GoupIndex do item de menu Windows, por outro lado, possui o valor 9.
Observe também que o menu File para TMainForm e o menu File para TMdiEditForm contêm itens diferentes no submenu. O menu File de TMdiEditForm possui mais itens de submenu do que o menu File de TMainForm.
A propriedade GroupIndex é importante porque permite que os menus dos formulários sejam “mesclados”. Isso significa que, quando o formulário principal ativar um formulário filho, o menu principal
do formulário filho será mesclado com o menu principal do formulário pai. A propriedade GroupIndex determina como os menus são classificados e quais menus do formulário principal são substituídos pelos
menus do formulário filho. Observe que a mesclagem de menus aplica-se apenas aos itens de menu na
barra de menus de um componente TMainMenu, e não aos seus submenus.
Sempre que uma propriedade GroupIndex do item de menu do formulário filho tiver o mesmo valor
da propriedade GroupIndex de um item de menu do formulário principal, o item do formulário filho substituirá o item de menu do formulário principal. Os outros menus são organizados na barra de menus na
ordem especificada pelas propriedades GroupIndex de todos os itens de menu combinados. Quando MdiE- 223
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
ditForm é o formulário ativo no projeto, os itens de menu que aparecem na barra de menus do formulário
principal são File, Edit, Character e Window, nessa mesma ordem. Observe que o menu File é o menu
File de TMdiEditForm, pois os dois menus File possuem valores da propriedade GroupIndex iguais a 0. Portanto, o menu File do TMdiChildForm substitui o menu File do TMainForm. A ordem desses menus reflete diretamente a ordem das propriedades GroupIndex para cada item de menu ao longo da barra de menus: 0, 1, 1, 9.
NOTA
Embora não estejamos usando isso aqui, existem certas regras de numeração que você deverá seguir para
que suas aplicações se integrem melhor à mesclagem de menus do OLE Container. Essas regras são explicadas no “Borland Delphi Library Reference Guide” (guia de referência da Borland para a biblioteca do Delphi”.
Esse comportamento é o mesmo com os outros formulários na aplicação MDI. Sempre que um formulário é ativado, a barra de menus principal é alterada para refletir a mesclagem de menus para o formulário principal e o formulário filho. Quando você executa o projeto, a barra de menus muda dependendo do formulário filho que esteja ativo no momento.
A mesclagem de menus com aplicações MDI é automática. Desde que os valores da propriedade
GroupIndex dos itens de menu estejam na ordem que você deseja, seus menus serão mesclados corretamente quando você chamar os formulários MDI filhos.
Para aplicações não baseadas em MDI, o processo também é muito fácil, mas exige uma etapa adicional. Mostramos um pequeno exemplo no Capítulo 4, com relação à mesclagem de menus em aplicações não baseadas em MDI. Naquela oportunidade, discutíamos sobre TNavStatForm. Entretanto, naquela
aplicação, baseamos a mesclagem em formulários filhos que eram realmente janelas filhas de um controle, e não do formulário principal, e tínhamos de chamar explicitamente as funções Merge( ) e Unmerge( ).
De um modo geral, para mesclar menus com aplicações não baseadas em MDI, esse processo não é automático, como acontece nas aplicações MDI. Você precisa definir a propriedade AutoMerge como True para
o TMainMenu no formulário cujos menus devem ser mesclados com os do formulário principal. Um exemplo de projeto que mostra a mesclagem de menus para formulários não baseados em MDI pode ser encontrado no projeto NonMDI.dpr, no CD que acompanha este livro.
Incluindo uma lista de documentos abertos ao menu
Para incluir uma lista de documentos abertos no menu Window, defina a propriedade WindowMenu do formulário principal como a instância do item de menu que deverá conter a lista de documentos abertos.
Por exemplo, a propriedade TMainForm.WindowMenu na aplicação de exemplo de MDI está definida como
mmiWindow, que se refere ao menu Window na barra de menus principal. A seleção que você escolhe para
essa propriedade precisa ser um item de menu que aparece na barra de menus – não pode ser um item de
submenu. A aplicação mostra uma lista de documentos abertos no menu Window.
Técnicas variadas de MDI
As próximas seções mostram várias técnicas comuns referentes a aplicações MDI.
Desenhando um bitmap na janela do cliente MDI
Ao criar uma aplicação MDI, você pode querer colocar uma imagem no fundo, como o logotipo de uma
empresa, sobre a área do cliente do formulário principal de uma aplicação MDI. Para formulários comuns (não baseados em MDI), esse procedimento é simples. Basta colocar um componente TImage no formulário, definir sua propriedade Align como alClient e pronto (volte ao visualizador de bitmap na aplicação de exemplo de MDI, anteriormente neste capítulo). A colocação de uma imagem no formulário prin224 cipal de uma aplicação MDI, no entanto, é algo diferente.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Lembre-se de que a janela do cliente de uma aplicação MDI é uma janela separada do formulário
principal. A janela do cliente possui muitas responsabilidades executando tarefas específicas da MDI, incluindo o desenho de janelas MDI filhas.
Pense nisso como se o formulário principal fosse uma janela transparente sobre a janela do cliente.
Sempre que você inclui componentes como TButton, TEdit e TImage sobre a área do cliente do formulário
principal, esses componentes são na realidade colocados na janela transparente do formulário principal.
Quando a janela do cliente realiza seu desenho das janelas filhas – ou formulários filhos –, os formulários
são desenhados abaixo dos componentes que aparecem no formulário principal, assim como ao colocar
um adesivo no vidro de uma moldura de foto (ver Figura 16.4).
FIGURA 16.4
Formulários clientes desenhados abaixo dos componentes do formulário principal.
Assim, como você pode desenhar na janela do cliente? Como o Delphi 5 não oferece um encapsulamento da janela do cliente na VCL, você precisa usar a API do Win32. O método usado é subclassificar a
janela do cliente e capturar a mensagem responsável por pintar o fundo da janela do cliente –
WM_ERASEBKGND. Lá, você controla o comportamento padrão e realiza seu próprio desenho personalizado.
O código a seguir vem do projeto MdiBknd.dpr no CD que acompanha este livro. O projeto é uma
aplicação MDI com um componente TImage que contém um bitmap. Pelo menu, você pode especificar
como desenhar a imagem na janela do cliente MDI – centralizada, lado a lado ou esticada, como vemos
respectivamente nas Figuras 16.5, 16.6 e 16.7.
FIGURA 16.5
A janela cliente MDI com uma imagem centralizada.
225
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 16.6
A janela cliente MDI com uma imagem lado a lado.
FIGURA 16.7
A janela cliente MDI com uma imagem esticada.
A Listagem 16.7 mostra o código da unidade que realiza o desenho.
Listagem 16.7 Desenhando imagens na janela cliente MDI
unit MainFrm;
interface
uses Windows, SysUtils, Classes, Graphics, Forms, Controls, Menus,
StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, JPeg;
226
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiFile: TMenuItem;
mmiNew: TMenuItem;
mmiClose: TMenuItem;
N1: TMenuItem;
mmiExit: TMenuItem;
mmiImage: TMenuItem;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.7 Continuação
mmiTile: TMenuItem;
mmiCenter: TMenuItem;
mmiStretch: TMenuItem;
imgMain: TImage;
procedure mmiNewClick(Sender: TObject);
procedure mmiCloseClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiTileClick(Sender: TObject);
private
FOldClientProc,
FNewClientProc: TFarProc;
FDrawDC: hDC;
procedure CreateMDIChild(const Name: string);
procedure ClientWndProc(var Message: TMessage);
procedure DrawStretched;
procedure DrawCentered;
procedure DrawTiled;
protected
procedure CreateWnd; override;
end;
var
MainForm: TMainForm;
implementation
uses MdiChildFrm;
{$R *.DFM}
procedure TMainForm.CreateWnd;
begin
inherited CreateWnd;
// Passa o método ClientWndProc para um procedimento de janela válido
FNewClientProc := MakeObjectInstance(ClientWndProc);
// Apanha um ponteiro para o procedimento de janela original
FOldClientProc := Pointer(GetWindowLong(ClientHandle, GWL_WNDPROC));
// Define ClientWndProc como novo procedimento de janela
SetWindowLong(ClientHandle, GWL_WNDPROC, LongInt(FNewClientProc));
end;
procedure TMainForm.DrawCentered;
{ Este procedimento centraliza a imagem na área do cliente do formulário. }
var
CR: TRect;
begin
GetWindowRect(ClientHandle, CR);
with imgMain do
BitBlt(FDrawDC, ((CR.Right - CR.Left) - Picture.Width) div 2,
((CR.Bottom - CR.Top) - Picture.Height) div 2,
Picture.Graphic.Width, Picture.Graphic.Height,
Picture.Bitmap.Canvas.Handle, 0, 0, SRCCOPY);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
227
Listagem 16.7 Continuação
procedure TMainForm.DrawStretched;
{ Este procedimento estica a imagem na área do cliente do formulário. }
var
CR: TRect;
begin
GetWindowRect(ClientHandle, CR);
StretchBlt(FDrawDC, 0, 0, CR.Right, CR.Bottom,
imgMain.Picture.Bitmap.Canvas.Handle, 0, 0,
imgMain.Picture.Width, imgMain.Picture.Height, SRCCOPY);
end;
procedure TMainForm.DrawTiled;
{ Este procedimento coloca a imagem lado a lado na área do cliente do
formulário. }
var
Row, Col: Integer;
CR, IR: TRect;
NumRows, NumCols: Integer;
begin
GetWindowRect(ClientHandle, CR);
IR := imgMain.ClientRect;
NumRows := CR.Bottom div IR.Bottom;
NumCols := CR.Right div IR.Right;
with imgMain do
for Row := 0 to NumRows+1 do
for Col := 0 to NumCols+1 do
BitBlt(FDrawDC, Col * Picture.Width, Row * Picture.Height,
Picture.Width, Picture.Height, Picture.Bitmap.Canvas.Handle,
0, 0, SRCCOPY);
end;
228
procedure TMainForm.ClientWndProc(var Message: TMessage);
begin
case Message.Msg of
// Captura mensagens WM_ERASEBKGND e faz o desenho na área do cliente
WM_ERASEBKGND:
begin
CallWindowProc(FOldClientProc, ClientHandle, Message.Msg, Message.wParam,
Message.lParam);
FDrawDC := TWMEraseBkGnd(Message).DC;
if mmiStretch.Checked then
DrawStretched
else if mmiCenter.Checked then
DrawCentered
else DrawTiled;
Message.Result := 1;
end;
{ Captura as mensagens de rolagem e garante que a área do cliente
será redesenhada chamando InvalidateRect. }
WM_VSCROLL, WM_HSCROLL:
begin
Message.Result := CallWindowProc(FOldClientProc, ClientHandle, Message.Msg,
Message.wParam, Message.lParam);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.7 Continuação
InvalidateRect(ClientHandle, nil, True);
end;
else
// Por default, chama o procedimento de janela original
Message.Result := CallWindowProc(FOldClientProc, ClientHandle, Message.Msg,
Message.wParam, Message.lParam);
end; { case }
end;
procedure TMainForm.CreateMDIChild(const Name: string);
var
MdiChild: TMDIChildForm;
begin
MdiChild := TMDIChildForm.Create(Application);
MdiChild.Caption := Name;
end;
procedure TMainForm.mmiNewClick(Sender: TObject);
begin
CreateMDIChild(‘NONAME’ + IntToStr(MDIChildCount + 1));
end;
procedure TMainForm.mmiCloseClick(Sender: TObject);
begin
if ActiveMDIChild <<|>> nil then
ActiveMDIChild.Close;
end;
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.mmiTileClick(Sender: TObject);
begin
mmiTile.Checked := false;
mmiCenter.Checked := False;
mmiStretch.Checked := False;
{ Define a propriedade Checked do item de menu que chamou este
manipulador de evento como Checked. }
if Sender is TMenuItem then
TMenuItem(Sender).Checked := not TMenuItem(Sender).Checked;
{ Redesenha a área do cliente do formulário. }
InvalidateRect(ClientHandle, nil, True);
end;
end.
Para pintar a imagem na janela do cliente da aplicação MDI, você precisa usar uma técnica chamada
subclassificação. A subclassificação é discutida no Capítulo 5. Para subclassificar a janela do cliente, você
precisa armazenar o procedimento de janela original da janela do cliente de modo que possa chamá-lo.
Também deve haver um ponteiro para o novo procedimento de janela. A variável de formulário FOldClientProc armazena o procedimento de janela original, e a variável FNewClientProc aponta para o novo procedimento de janela.
229
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
O procedimento ClientWndProc( ) é o procedimento ao qual FNewClientProc aponta. Na realidade,
como ClientWndProc( ) é um método de TMainForm, você precisa usar a função MakeObjectInstance( ) para retornar um ponteiro para um procedimento de janela criado a partir do método MakeObjectInstance( ),
conforme discutido no Capítulo 13.
O método TMainForm.CreateWnd( ) foi substituído quando a janela do cliente do formulário principal
foi subclassificada usando as funções da API do Win32 GetWindowLong( ) e SetWindowLong( ). ClientWndProc( ) é o novo procedimento de janela.
TMainForm contém três métodos privados: DrawCentered( ), DrawTiled( ) e DrawStretched( ). Cada um
desses métodos utiliza as funções da API do Win32 para realizar as rotinas de desenho da GDI para pintar o bitmap. As funções da API do Win32 são usadas porque o contexto de dispositivo da janela do cliente não é encapsulado por TCanvas, e você não pode usar normalmente os métodos embutidos do Delphi
5. Na realidade, é possível atribuir o contexto de dispositivo a uma propriedade TCanvas.Handle. Você teria de definir uma instância de TCanvas para poder fazer isso, mas é possível.
Você precisa capturar três mensagens para realizar o desenho no fundo: WM_ERASEBKGND, WM_VSCROLL e
WM_HSCROLL. A mensagem WM_ERASEBKGND é enviada a uma janela quando tiver de ser apagada. Esse é um momento oportuno para realizar o desenho especializado da imagem. No procedimento, você determina
qual procedimento de desenho deve ser chamado, com base no item de menu selecionado. As mensagens
WM_VSCROLL e WM_HSCROLL são capturadas para garantir que a imagem de segundo plano seja desenhada corretamente quando o usuário rolar o formulário principal. Finalmente, todas as outras mensagens são enviadas para o procedimento de janela original com esta instrução:
Message.Result := CallWindowProc(FOldClientProc, ClientHandle,
Message.Msg, Message.wParam, Message.lParam);
Este exemplo não apenas demonstra como você pode melhorar visualmente, mas também mostra
como você pode realizar o desenvolvimento em nível de API com técnicas não fornecidas pela VCL.
Criando um formulário MDI filho oculto
O Delphi 5 gera um erro se você tentar ocultar um formulário MDI filho usando uma instrução como esta:
ChildForm.Hide;
O erro indica que não é permitido ocultar um formulário MDI filho. O motivo para isso é que os
projetistas do Delphi descobriram que, na implementação da MDI do Windows, ocultar formulários
MDI filhos modifica a ordem z das janelas filhas. A menos que você seja extremamente cuidadoso quanto
ao uso dessa técnica, tentar ocultar um formulário MDI filho pode causar um grande problema para a
sua aplicação. Apesar disso, você poderá ter de ocultar um formulário filho. Existem duas maneiras de
ocultar formulários MDI filhos. Mas fique sabendo da anomalia e use essas técnicas com cuidado.
Uma forma de ocultar um formulário MDI filho é evitar totalmente que a janela do cliente desenhe o
formulário filho. Faça isso usando a função LockWindowUpdate( ) da API do Win32 para desativar o desenho
da janela do cliente MDI. Essa técnica é útil se você quiser criar um formulário MDI filho mas não quiser
mostrar esse formulário ao usuário, a menos que algum processo tenha sido completado com sucesso. Por
exemplo, esse processo poderia ser uma consulta ao banco de dados; se o processo falhar, você poderá liberar o formulário. A menos que você use algum método para ocultar o formulário, verá uma piscada na tela
quando o formulário for criado, antes de ter oportunidade para destruí-lo. A função LockWindowUpdate( ) desativa o desenho na tela de uma janela. Somente uma janela pode estar bloqueada em determinado momento. A passagem de 0 para LockWindowUpdate reativa o desenho na tela de desenho da janela.
O outro método para ocultar um formulário MDI filho é ocultá-lo realmente usando a função ShowWindow( ) da API do Win32. Você oculta o formulário especificando o flag SW_HIDE junto com a função.
Nesse caso, será preciso usar a função SetWindowPos( ) para restaurar a janela filha. Você pode usar essa
técnica para ocultar o formulário MDI filho se ele já tiver sido criado e apresentado ao usuário.
A Listagem 16.8 ilustra as técnicas que acabamos de descrever e é o formulário principal para o pro230 jeto MdiHide.dpr, no CD que acompanha este livro.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.8 Uma unidade mostrando técnicas para ocultar formulários MDI filhos
unit MainFrm;
interface
uses Windows, SysUtils, Classes, Graphics, Forms, Controls, Menus,
StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, ComCtrls, MdiChildFrm;
type
TMainForm = class(TForm)
mmMain: TMainMenu;
mmiFile: TMenuItem;
mmiNew: TMenuItem;
mmiClose: TMenuItem;
mmiWindow: TMenuItem;
N1: TMenuItem;
mmiExit: TMenuItem;
mmiHide: TMenuItem;
mmiShow: TMenuItem;
mmiHideForm: TMenuItem;
procedure mmiNewClick(Sender: TObject);
procedure mmiCloseClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiHideClick(Sender: TObject);
procedure mmiShowClick(Sender: TObject);
procedure mmiHideFormClick(Sender: TObject);
private
procedure CreateMDIChild(const Name: string);
public
HideForm: TMDIChildForm;
Hidden: Boolean;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.CreateMDIChild(const Name: string);
var
MdiChild: TMDIChildForm;
begin
MdiChild := TMDIChildForm.Create(Application);
MdiChild.Caption := Name;
end;
procedure TMainForm.mmiNewClick(Sender: TObject);
begin
CreateMDIChild(‘NONAME’ + IntToStr(MDIChildCount + 1));
end;
procedure TMainForm.mmiCloseClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
231
Listagem 16.8 Continuação
begin
if ActiveMDIChild <|> nil then
ActiveMDIChild.Close;
end;
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.mmiHideClick(Sender: TObject);
begin
if Assigned(HideForm) then
ShowWindow(HideForm.Handle, SW_HIDE);
Hidden := True;
end;
procedure TMainForm.mmiShowClick(Sender: TObject);
begin
if Assigned(HideForm) then
SetWindowPos(HideForm.handle, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE
or SWP_NOMOVE or SWP_SHOWWINDOW);
Hidden := False;
end;
procedure TMainForm.mmiHideFormClick(Sender: TObject);
begin
if not Assigned(HideForm) then
begin
if MessageDlg(‘Create Hidden?’, mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
LockWindowUpdate(Handle);
try
HideForm := TMDIChildForm.Create(Application);
HideForm.Caption := ‘HideForm’;
ShowMessage(‘Form created and hidden. Press OK to show form’);
finally
LockWindowUpdate(0);
end;
end
else begin
HideForm := TMDIChildForm.Create(Application);
HideForm.Caption := ‘HideForm’;
end;
end
else if not Hidden then
HideForm.SetFocus;
end;
end.
232
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
O projeto é uma aplicação MDI simples. O manipulador de evento mmiHideFormClick( ) cria um formulário filho que pode ser criado e ocultado pelo usuário depois que aparecer.
Quando mmiHideFormClick( ) é chamado, ele verifica se foi criada uma instância de THideForm, nesse
caso apresenta apenas a instância de THideForm, desde que não tenha sido ocultada pelo usuário. Se não
houver uma instância de THideForm presente, o usuário responderá se ela deverá ser criada e ocultada. Se o
usuário responder afirmativamente, o desenho na área do cliente será desativado antes que o formulário
seja criado. Se o desenho na janela do cliente não estiver desativado, o formulário aparece assim que for
criado. O usuário recebe então uma caixa de mensagem indicando que o formulário foi criado. Quando
o usuário fecha a caixa de mensagem, o desenho na janela do cliente é reativado e o formulário filho aparece, forçando a janela do cliente a ser novamente pintada. Você poderá modificar a caixa de mensagem,
dizendo ao usuário que o formulário foi criado com algum processo extenso, que exige que o formulário
filho seja criado mas não apresentado. Se o usuário escolher não criar o formulário como oculto, ele será
criado normalmente.
O segundo método utilizado para ocultar o formulário, depois que já tiver sido apresentado, chama
a função ShowWindow( ) da API do Win32 e passa a alça do formulário filho e o flag SW_HIDE. Isso efetivamente oculta o formulário. Para reapresentá-lo, chame a função SetWindowPos( ) da API do Win32, usando a alça do formulário filho e os flags especificados na listagem. SetWindowPos( ) é usado para mudar o tamanho da janela, a posição ou sua ordem z. Neste exemplo, SetWindowPos( ) é usado para reapresentar a
janela escondida definindo sua ordem z; nesse caso, especificando o flag HWND_TOP, a ordem z do formulário oculto é definida para que a janela fique no topo.
Minimizando, maximizando e restaurando todas as janelas MDI filhas
Constantemente você precisa realizar uma tarefa em todos os formulários MDI ativos no projeto. A mudança da propriedade WindowState do formulário é um exemplo típico de um processo a ser realizado em
cada instância de um formulário MDI filho. Essa tarefa é muito simples e só exige que você percorra os
formulários usando a propriedade de array MDIChildren do formulário principal. A propriedade MDIChildCount do formulário principal contém o número de formulários filho MDI ativos. A Listagem 16.9 mostra os manipuladores de eventos que minimizam, maximizam e restauram todas as janelas MDI filhas em
uma aplicação. Este projeto poderá ser encontrado no CD com o nome Min_Max.dpr.
Listagem 16.9 Minimizando, maximizando e restaurando todas as janelas MDI filhas
unit MainFrm;
interface
uses Windows, SysUtils, Classes, Graphics, Forms, Controls, Menus,
StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, ComCtrls;
type
TMainForm = class(TForm)
MainMenu1: TMainMenu;
mmiFile: TMenuItem;
mmiNew: TMenuItem;
mmiClose: TMenuItem;
mmiWindow: TMenuItem;
N1: TMenuItem;
mmiExit: TMenuItem;
mmiMinimizeAll: TMenuItem;
mmiMaximizeAll: TMenuItem;
mmiRestoreAll: TMenuItem;
procedure mmiNewClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
233
Listagem 16.9 Continuação
procedure mmiCloseClick(Sender: TObject);
procedure mmiExitClick(Sender: TObject);
procedure mmiMinimizeAllClick(Sender: TObject);
procedure mmiMaximizeAllClick(Sender: TObject);
procedure mmiRestoreAllClick(Sender: TObject);
private
{ Declarações privadas }
procedure CreateMDIChild(const Name: string);
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
uses MdiChildFrm;
{$R *.DFM}
procedure TMainForm.CreateMDIChild(const Name: string);
var
Child: TMDIChildForm;
begin
Child := TMDIChildForm.Create(Application);
Child.Caption := Name;
end;
procedure TMainForm.mmiNewClick(Sender: TObject);
begin
CreateMDIChild(‘NONAME’ + IntToStr(MDIChildCount + 1));
end;
procedure TMainForm.mmiCloseClick(Sender: TObject);
begin
if ActiveMDIChild <<|>> nil then
ActiveMDIChild.Close;
end;
procedure TMainForm.mmiExitClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.mmiMinimizeAllClick(Sender: TObject);
var
i: integer;
begin
for i := MDIChildCount - 1 downto 0 do
MDIChildren[i].WindowState := wsMinimized;
end;
234
procedure TMainForm.mmiMaximizeAllClick(Sender: TObject);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 16.9 Continuação
var
i: integer;
begin
for i := 0 to MDIChildCount - 1 do
MDIChildren[i].WindowState := wsMaximized;
end;
procedure TMainForm.mmiRestoreAllClick(Sender: TObject);
var
i: integer;
begin
for i := 0 to MDIChildCount - 1 do
MDIChildren[i].WindowState := wsNormal;
end;
end.
Resumo
Este capítulo mostrou como criar aplicações MDI no Delphi 5. Você também aprendeu algumas técnicas
avançadas específicas das aplicações MDI. Com a base adquirida neste capítulo, você deverá estar pronto para criar aplicações MDI com um toque profissional.
235
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Programação de
multimídia com Delphi
CAPÍTULO
18
NE STE CAP ÍT UL O
l
Criação de um Mídia Player simples
l
Uso de arquivos WAV em suas aplicações
l
Uso de vídeo
l
Suporte a dispositivo
l
Criação de um CD Player
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 7 — 2ª PROVA
O componente TMediaPlayer do Delphi é a prova de que as melhores coisas vêm em pequenos frascos. Disfarçado nesse pequeno componente, o Delphi encapsula grande parte da funcionalidade da interface de
controle de mídia (Media Control Interface, ou MCI) do Windows – a parte da API do Windows que oferece controle para dispositivos de multimídia.
O Delphi torna a programação de multimídia tão fácil que o tradicional e monótono programa
“Hello World” pode ser uma coisa do passado. Por que escrever Hello World na tela quando é quase tão fácil tocar um arquivo de som ou de vídeo que contenha suas saudações?
Neste capítulo, você aprenderá a escrever um mídia player simples porém poderoso, e até mesmo
construirá um CD Player de áudio totalmente funcional. Este capítulo explica os usos e nuances do componente TMedia Player. Naturalmente, seu computador precisa estar equipado com dispositivos de multimídia, como uma placa de som e um CD-ROM, para que este capítulo tenha qualquer utilidade real para
você.
Criação de um Mídia Player simples
A melhor forma de aprender é fazendo. Esta aplicação demonstra como você pode criar rapidamente um
Mídia Player incluindo componentes TMedia Player, TButton e TOpenDialog em um formulário. Esse formulário aparece na Figura 18.1.
FIGURA 18.1
O Mídia Player EasyMM.
Vamos explicar o funcionamento do Mídia Player EasyMM. Depois que você dá um clique em Button1,
a caixa de diálogo OpenDialog aparece, e você escolhe um arquivo através dela. O Mídia Player se prepara
para tocar o arquivo que você escolheu em OpenDialog. Depois, você pode dar um clique no botão Play no
Media Player para tocar o arquivo. O código a seguir pertence ao método OnClick do botão, e abre o Mídia Player com o arquivo escolhido:
procedure TMainForm.BtnOpenClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
MediaPlayer1.Filename := OpenDialog1.Filename;
MediaPlayer1.Open;
end;
end;
Esse código executa a caixa de diálogo OpenDialog1 e, se for escolhido um nome de arquivo, a propriedade FileName de OpenDialog1 é copiada para a propriedade FileName de MediaPlayer1. O método Open de MediaPlayer é então chamado para prepará-lo para tocar o arquivo.
Você também pode querer limitar os arquivos a navegar com a caixa de diálogo OpenDialog a somente arquivos de multimídia. TMediaPlayer aceita uma grande quantidade de tipos de dispositivo de multimídia, mas por enquanto, você só navegará por arquivos WAV, AVI e MIDI. Essa capacidade existe no
componente TOpenDialog, e você tira proveito dela selecionando OpenDialog1 no Object Inspector, escolhendo a propriedade Mask e dando um clique nas reticências à direita desse item para chamar o Filter Editor. Preencha os filtros *.WAV, *.AVI e *.MID , como mostra a Figura 18.2.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
237
FIGURA 18.2
O Filter Editor.
O projeto é salvo como EasyMM.dpr e a unidade principal como Main.pas. O Media Player agora está
pronto para rodar. Rode o programa e experimente-o usando um dos arquivos de multimídia no seu disco rígido. Outras pessoas poderão tê-lo convencido – ou talvez você mesmo tenha se convencido – de
que a programação de multimídia é difícil, mas agora você tem a prova real de que isso não é verdade.
Uso de arquivos WAV em suas aplicações
Arquivos WAV (pronuncia-se “uêiv”, e é uma abreviação de waveform, forma de onda) são o formato de
arquivo padrão para o compartilhamento de áudio no Windows. Como o nome sugere, os arquivos
WAV armazenam sons em um formato binário semelhante a uma onda matemática. A melhor coisa sobre
os arquivos WAV é que eles ganharam aceitação no setor, e você poderá encontrá-los em toda a parte. A
má notícia sobre arquivos WAV é que eles costumam ser muito grandes, e apenas alguns daqueles arquivos WAV do Homer Simpson podem ocupar uma fatia enorme do seu disco rígido.
O componente TMediaPlayer permite integrar facilmente os sons WAV nas suas aplicações. Conforme ilustramos, a execução de arquivos WAV no seu programa não é trabalhosa – basta dar a um componente TMediaPlayer um nome de arquivo, abri-lo e tocá-lo. Uma pequena capacidade de executar áudio
pode ser exatamente aquilo que suas aplicações precisam para passarem de interessantes para fantásticas.
Se você precisa apenas tocar arquivos WAV, então pode não precisa de tudo o mais que o componente TMediaPlayer oferece. Em vez disso, pode usar a função PlaySound( ) da API, encontrada na unidade
MMSystem. PlaySound( ) é definida da seguinte forma:
function PlaySound(pszSound: PChar; hmod: HMODULE;
fdwSound: DWORD): BOOL; stdcall;
PlaySound( ) tem a capacidade de tocar um arquivo WAV a partir de um arquivo, da memória ou de
um arquivo de recursos vinculado à aplicação. PlaySound( ) utiliza três parâmetros:
l
l
l
238
O primeiro parâmetro, pszSound, é uma variável PChar que representa um nome de arquivo, nome
de alias, nome de recurso, entrada do Registro, entrada da seção [sounds] do seu arquivo WIN.INI
ou um ponteiro para um som WAV localizado em alguma parte da memória.
O segundo parâmetro, hmod. representa a alça do arquivo executável que contém o recurso a ser
carregado. Esse parâmetro precisa ser zero, a menos que snd_Resource seja especificado no parâmetro fdwSound.
O terceiro parâmetro, fdwSound, contém flags que descrevem como o som deve ser tocado. Esses
flags contêm uma combinação de qualquer um dos seguintes valores:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Flag
Descrição
SND_APPLICATION
O som é tocado usando a associação específica de um aplicativo.
O parâmetro pszSound é um alias de evento do sistema no Registro ou no arquivo
WIN.INI. Não use esse flag com SND_FILENAME ou com SND_RESOURCE, pois são mutuamente exclusivos (não funcionam em conjunto).
O parâmetro pszSound é um identificador de som predefinido.
O parâmetro pszSound é um nome de arquivo.
Esse flag indica que, se o driver estiver ocupado, ele retornará imediatamente, sem tocar o som.
Todos os sons são interrompidos para a tarefa que chamou. Se pszSound não for zero,
todas as instâncias do som especificado são encerrados. Se pszSound for zero, todos os
sons chamados pela tarefa atual são encerrados. Você também precisa especificar a
alça de instância apropriada para encerrar os eventos SND_RESOURCE.
O parâmetro pszSound é um identificador de recurso. Quando você estiver usando esse
flag, o parâmetro hmod deverá conter a instância que contém o recurso especificado.
Toca o som em modo assíncrono e retorna da função quase imediatamente. Isso gera
o efeito de música em segundo plano.
Toca o som repetidamente até que você o faça parar (ou enlouqueça). SND_ASYNC também precisa ser especificado quando você usar esse flag.
Toca o som WAV na área da memória apontada pelo parâmetro pszSound.
Se o som não for encontrado PlaySound( ) retorna imediatamente sem tocar o som
default, conforme especificado no Registro.
Toca o som apenas se ainda não estiver tocando. PlaySound( ) retorna True se o som
for tocado e False se o som não for tocado. Se esse flag não for especificado, o
Win32 terminará qualquer som sendo tocado antes de tentar tocar o som especificado
em pszSound.
Toca o som em modo síncrono e não retorna da função até que o som termine de tocar.
SND_ALIAS
SND_ALIAS_ID
SND_FILENAME
SND_NOWAIT
SND_PURGE
SND_RESOURCE
SND_ASYNC
SND_LOOP
SND_MEMORY
SND_NODEFAULT
SND_NOSTOP
SND_SYNC
DICA
Para encerrar um som WAV atualmente tocando em modo assíncrono, chame PlaySound( ) e passe nil ou
zero em todos os parâmetros, da seguinte forma:
PlaySound(Nil, 0, 0); // encerra o WAV atualmente sendo tocado
Para encerrar até mesmo arquivos que não sejam de forma de onda para realizar uma determinada tarefa,
inclua o flag snd_Purge:
PlaySound(Nil, 0, snd_Purge); // encerra todo som tocando atualmente
NOTA
A API do Win32 ainda aceita a função sndPlaySound( ), que fazia parte da API do Windows 3.x. Essa função só é aceita por questão de compatibilidade de versão, e pode não estar mais disponível nas próximas
implementações da API do Win32. Use a função PlaySound( ) do Win32 em vez de sndPlaySound( ), por
questão de compatibilidade futura.
239
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Uso de vídeo
AVI (abreviação de Audio-Video Interleave, intercalação de áudio e vídeo) é um dos formatos de arquivo
mais comuns em uso para a troca de informações de áudio e vídeo simultaneamente. Realmente, você
verá alguns arquivos AVI no diretório \Runimage\Delphi50\Demos\Coolstuf do CD-ROM que contém sua cópia do Delphi 5.
Você pode usar o programa de multimídia que descrevemos anteriormente neste capítulo para exibir arquivos AVI. Basta selecionar um arquivo AVI quando OpenDialog1 for chamado e dar um clique no
botão Play. Observe que o arquivo AVI é apresentado em sua própria janela.
Mostrando o primeiro quadro
Você pode querer ver o primeiro quadro (ou frame) de um arquivo AVI em uma janela antes de realmente apresentar o arquivo. Isso gera um efeito tipo congelar a imagem. Para fazer isso depois de abrir o
componente TMedia Player, basta definir a propriedade Frames do TMediaPlayer como 1 e depois chamar o
método Step( ). A propriedade Frames diz ao TMediaPlayer quantos quadros devem ser movidos quando os
métodos Stop( ) e Back( ) forem chamados. Step( ) avança os quadros de TMediaPlayer e apresenta o quadro atual. Este é o código:
procedure TForm1.ButtonClick(Sender: TObject);
begin
if OpenDialog1.Execute then
with MediaPlayer1 do
begin
Filename := OpenDialog1.Filename;
Open;
Frames := 1;
Step;
Notify := True;
end;
end;
Usando a propriedade Display
Você pode atribuir um valor à propriedade Display de TMediaPlayer para fazer com que o arquivo AVI seja
exibido em uma janela específica, em vez de criar sua própria janela. Para fazer isso, você inclui um componente TPanel no seu Media Player, como mostra a Figura 18.3. Depois de incluir o painel, você pode
salvar o projeto em um diretório como DDGMPlay.dpr.
240 F I G U R A 1 8 . 3 A janela principal de DDGMPlay.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Dê um clique no botão de seta suspensa para a propriedade Display de MediaPlayer1 e observe que todos os componentes desse projeto aparecem na caixa de listagem. Defina o valor da propriedade Display
como Panel1.
Agora observe que, quando você executa o programa e seleciona um arquivo AVI para exibição, a
saída do arquivo AVI aparece no painel. Observe também que o arquivo AVI não ocupa a área inteira do
painel; o arquivo AVI possui um certo tamanho default já programado.
Usando a propriedade DisplayRect
DisplayRect é uma propriedade do tipo TRect que determina o tamanho da janela de saída do arquivo AVI.
Você pode usar a propriedade DisplayRect para fazer com que a saída do seu arquivo AVI se estique ou encurte até um certo tamanho. Se você quiser que o arquivo AVI ocupe toda a área de Panel1, por exemplo,
atribua o tamanho do painel a DisplayRect:
MediaPlayer1.DisplayRect := Rect(0, 0, Panel1.Width, Panel1:Height);
Você pode incluir essa linha de código no manipulador de evento OnClick para Button1, da seguinte
forma:
procedure TForm1.Button1Click(Sender: TObject);
begin
If OpenDialog1.Execute then begin
MediaPlayer1.Filename := OpenDialog1.Filename;
MediaPlayer1.Open;
MediaPlayer1.DisplayRect := Rect(0, 0, Panel1.Width, Panel1.Height);
end;
end;
ATENÇÃO
Você só pode definir a propriedade DisplayRect depois que o método Open( ) de TMediaPlayer for chamado.
Entendendo os eventos de TMediaPlayer
possui dois eventos exclusivos: OnPostClick e OnNotify.
O evento OnPostClick é muito semelhante a OnClick, mas este ocorre assim que o componente é acionado, enquanto OnPostClick é executado apenas depois que ocorrer alguma ação causada por um clique.
Se você der um clique no botão Play de TMediaPlayer em runtime, por exemplo, um evento OnClick será gerado, mas um evento OnPostClick só será gerado depois que o dispositivo de mídia acabar de tocar.
O evento OnNotify é um pouco mais interessante. O evento OnNotify é executado sempre que o TMediaPlayer termina um método de controle de mídia (como Back, Close, Eject, Next, Open, Pause, PauseOnly, Play,
Previous, Resume, Rewind, StartRecording, Step ou Stop) e apenas quando a propriedade Notify de TMediaPlayer
estiver definida como True. Para ilustrar o funcionamento de OnNotify, inclua um manipulador para esse
evento no projeto DDGMPlay. No método manipulador do evento, você gera uma caixa de diálogo de mensagem que aparecerá após a execução de um comando:
DisplayRect
procedure TForm1.MediaPlayerNotify(Sender: TObject);
begin
MessageDlg(‘Media controle method executed’, mtInformation, ]mbOk], 0);
end;
Não se esqueça de definir também a propriedade Notify como True no manipulador do evento
de Button1 depois de abrir o Mídia Player:
241
OnClick
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
procedure TForm1.Button1Click(Sender: TObject);
begin
If OpenDialog1.Execute then
with MediaPlayer1 do
begin
Filename := OpenDialog1.Filename;
Open;
DisplayRect := Rect(0, 0, Panel1.Width, Panel1.Height);
Notify := True;
end;
end;
DICA
Observe que você moveu o código que trata de MediaPlayer1 para dentro de uma construção with..do.
Conforme você aprendeu nos capítulos anteriores, essa construção oferece vantagens de clareza e desempenho do código, em relação a simplesmente qualificar cada nome de propriedade e método.
Exibindo o código-fonte para DDGMPlay
No momento, você já deve conhecer os fundamentos de como tocar arquivos WAV e AVI. As Listagens
18.1 e 18.2 mostram o código-fonte completo para o projeto DDGMPlay.
Listagem 18.1 O código-fonte para DDGMPlay.dpr
program DDGMPlay;
uses
Forms,
Main in ‘MAIN.PAS’ {MainForm};
{$R *.RES}
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
Listagem 18.2 O código-fonte para Main.pas
unit Main;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, MPlayer, ExtCtrls;
type
TMainForm = class(TForm)
MediaPlayer1: TMediaPlayer;
OpenDialog1: TOpenDialog;
Button1: TButton;
242
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 18.2 Continuação
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
procedure MediaPlayer1Notify(Sender: TObject);
private
{ Declarações privadas }
public
{ Declarações públicas }
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.Button1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
with MediaPlayer1 do
begin
Filename := OpenDialog1.Filename;
Open;
DisplayRect := Rect(0, 0, Panel1.Width, Panel1.Height);
Notify := True;
end;
end;
procedure TMainForm.MediaPlayer1Notify(Sender: TObject);
begin
MessageDlg(‘Media control method executed’, mtInformation, [mbOk], 0);
end;
end.
Suporte a dispositivo
TMediaPlayer tem suporte para uma grande variedade de dispositivos de mídia aceitos pela MCI. O tipo de
dispositivo que TMediaPlayer controla é determinado por sua propriedade DeviceType. A Tabela 18.1 descreve os diferentes valores da propriedade DeviceType.
Tabela 18.1 Valores da propriedade DeviceType de TMediaPlayer
Valor do tipo de dispositivo
Dispositivo de mídia
dtAutoSelect
O TMediaPlayer deve selecionar automaticamente o tipo de dispositivo correto
com base no nome do arquivo a ser tocado.
dtAVIVideo
Arquivo AVI. Esses arquivos possuem a extensão AVI e contêm som e vídeo em
movimento.
dtCDAudio
Um CD de áudio tocado na unidade de CD-ROM do computador.
dtDAT
Um aparelho de fita de áudio digital (DAT) conectado ao PC.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
243
Tabela 18.1 Valores da propriedade DeviceType de TMediaPlayer
Valor do tipo de dispositivo
Dispositivo de mídia
dtDigitalVideo
Um dispositivo de vídeo digital, como uma câmera de vídeo digital.
dtMMMovie
Um formato de filme de multimídia.
dtOther
Um formato de multimídia não especificado.
dtOverlay
Um dispositivo de overlay de vídeo.
dtScanner
Um scanner conectado ao PC.
dtSequencer
Um dispositivo seqüenciador capaz de tocar arquivos MIDI. Os arquivos MIDI
normalmente terminam com .MID ou .RMI.
dtVCR
Um gravador de videocassete (VCR) conectado ao PC.
dtVideodisc
Um aparelho de vídeo-disco conectado ao PC.
dtWaveAudio
Um arquivo de áudio WAV. Esses arquivos possuem a extensão WAV.
Embora você possa ver que TMediaPlayer aceita muitos formatos, este capítulo focaliza principalmente os formatos WAV, AVI e CD de áudio, pois são os mais comuns no Windows.
NOTA
O componente TMediaPlayer é um descendente de TWinControl, o que significa que pode ser facilmente encapsulado como um controle ActiveX através dos assistentes do Delphi 5. Um possível benefício de se fazer
isso é a capacidade de incorporar um Mídia Player em uma página da Web para estender suas páginas
com multimídia personalizada. Além do mais, com algumas linhas de JavaScript ou VBScript, você poderia
oferecer um CD Player para qualquer um na Internet ou na sua intranet que esteja rodando um navegador
no Windows.
Criação de um CD Player
Você aprenderá sobre os pontos mais detalhados do componente TMediaPlayer criando um CD Player de
áudio completo. A Figura 18.4 mostra o formulário principal dessa aplicação, que é chamada CDPlayer.
dpr no CD que acompanha este livro. A unidade principal desse formulário se chama CDMain.pas.
244
FIGURA 18.4
O formulário principal do CD Player de áudio.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
A Tabela 18.2 mostra as propriedades importantes a serem definidas para os componentes contidos
no formulário principal do CD Player.
Tabela 18.2 Propriedades importantes para os componentes do CD Player
Componente
Propriedade
Valor
mpCDPlayer
DeviceType
dtAudioCD
sbTrack1-sbTrack20
Caption
‘1’ – ‘20’
sbTrack1-sbTrack20
Tag
1 – 20
Exibindo uma tela de abertura (splash)
Quando o CD Player é iniciado, ele gasta alguns segundos para ser carregado, e pode levar mais alguns
segundos até que o componente TMediaPlayer seja inicializado depois de chamar o método Open( ). Essa espera desde o momento em que o usuário dá um clique no ícone do Explorer até o momento em que ele
realmente vê o programa normalmente deixa uma dúvida na mente: “o programa vai começar ou não
vai?”. Esse atraso é causado pelo tempo que o Windows leva para carregar seu subsistema de multimídia,
que ocorre quando TMediaPlayer é aberto. Para evitar esse problema, você pode dar ao programa de CD
Player uma tela de abertura (ou splash) que aparece assim que o programa é iniciado. A tela de abertura
diz aos usuários que, sim, o programa irá começar a qualquer momento – ele só está levando algum tempo para ser carregado; portanto divirta-se por enquanto com esta telinha.
A primeira etapa na criação de uma tela de abertura é criar um formulário que você deseja usar
como tela de abertura. Em geral, você deseja que esse formulário contenha um painel, mas não uma borda ou uma barra de título; isso lhe dá uma aparência tridimensional, como se estivesse flutuando. No painel, coloque um ou mais componentes TLabel e talvez um componente TImage apresentando um bitmap ou
um ícone.
A tela de abertura do CD Player aparece na Figura 18.5, e a unidade, Splash.pas pode ser vista na Listagem 18.3.
FIGURA 18.5
O formulário de tela de abertura do CD Player.
Listagem 18.3 O código-fonte de Splash.pas
unit Splash;
interface
uses Windows, Classes, Graphics, Forms, Controls, StdCtrls,
ExtCtrls;
type
TSplashScreen = class(TForm)
StatusPanel: TPanel;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
245
Listagem 18.3 Continuação
var
SplashScreen: TSplashScreen;
implementation
{$R *.DFM}
begin
{ Como a tela de abertura é apresentada antes que a tela principal
seja criada, ela deve ser criada antes da tela principal. }
SplashScreen := TSplashScreen.Create(Application);
SplashScreen.Show;
SplashScreen.Update;
end.
Ao contrário de um formulário normal, a tela de abertura é criada e apresentada na seção initialization de sua unidade. Visto que a seção initialization para todas as unidades é executada antes do bloco
de programa principal no arquivo DPR, esse formulário aparece antes que a parte principal do programa
seja executada.
ATENÇÃO
Não use Application.CreateForm( ) para criar a instância do seu formulário de tela de abertura. Na primeira
vez que Application.CreateForm( ) é chamado em uma aplicação, o Delphi cria esse formulário no formulário da aplicação principal. Seria “realmente ruim” criar sua tela de abertura no formulário principal.
Iniciando o CD Player
Crie um manipulador de evento para o método OnCreate do formulário. Nesse método, você abre e inicializa o programa CD Player. Primeiro, chame o método Open( ) de CDPlayer. Open( ) verifica se o sistema é
capaz de tocar CDs de áudio e depois inicializa o dispositivo. Se Open( ) falhar, ele gerará uma exceção do
tipo EMCIDeviceError. No evento de uma exceção abrindo o dispositivo, você deverá terminar a aplicação.
Veja o código:
try
mpCDPlayer.Open;
{ Abre o dispositivo CD Player }
except
{ Se houver um erro, o sistema pode não conseguir tocar CDs. }
on EMCIDeviceError do
begin
MessageDlg(‘Error Initializing CD Player. Program will now exit.’,
mtError, [mbOk], 0);
Application.Terminate;
end;
end;
NOTA
O modo preferido de encerrar uma aplicação em Delphi é chamando o método Close( ) do formulário
principal ou chamando Application.Terminate.
246
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Depois de abrir o CDPlayer, você deverá definir sua propriedade EnableButtons para garantir que os
botões apropriados serão ativados para o dispositivo. No entanto, quais botões serão ativados depende
do estado atual do dispositivo de CD. Se um CD já estiver tocando quando você chamar Open( ), por
exemplo, obviamente o botão Play não deverá estar ativado. Para realizar uma verificação do status atual
do dispositivo de CD, você pode inspecionar a propriedade Mode de CDPlayer. A propriedade Mode, que possui todos os seus valores possíveis muito bem indicados na ajuda on-line, oferece informações a respeito
de se um dispositivo de CD está atualmente tocando, parado, em pausa, buscando etc. Nesse caso, sua
preocupação é apenas se o dispositivo está parado, em pausa ou tocando. O código a seguir ativa os botões apropriados:
case mpCDPlayer.Mode of
mpPlaying: mpCDPlayer.EnabledButtons := PlayButtons;
mpStopped, mpPaused: mpCDPlayer.EnabledButtons := StopButtons;
end;
A seguir, apresentamos o código-fonte completo para o método TMainForm.FormCreate( ). Observe
que você faz chamadas para vários métodos depois de abrir o CDPlayer com sucesso. A finalidade desses
métodos é atualizar os vários aspectos da aplicação CD Player, como o número de faixas no CD atual e a
posição da faixa atual. (Esses métodos são descritos com maiores detalhes neste capítulo.) Veja o código:
procedure TMainForm.FormCreate(Sender: TObject);
{ Este método é chamado quando o formulário é criado. Ele abre e
inicializa o player. }
begin
try
mpCDPlayer.Open;
// Abre o dispositivo de CD Player.
{ Se um CD já estiver tocando na partida, mostra status. }
if mpCDPlayer.Mode = mpPlaying then
LblStatus.Caption := ‘Playing’;
GetCDTotals;
// Mostra total de tempo e faixas no CD atual
ShowTrackNumber; // Mostra faixa atual
ShowTrackTime;
// Mostra os minutos e segundos da faixa atual
ShowCurrentTime; // Mostra a posição atual do CD
ShowPlayerStatus; // Atualiza o status do CD Player
except
{ Se houver um erro, o sistema pode não conseguir tocar CDs. }
on EMCIDeviceError do
begin
MessageDlg(‘Error Initializing CD Player. Program will now exit.’,
mtError, [mbOk], 0);
Application.Terminate;
end;
end;
{ Verifica modo atual do CD-ROM e ativa os botões apropriados. }
case mpCDPlayer.Mode of
mpPlaying: mpCDPlayer.EnabledButtons := PlayButtons;
mpStopped, mpPaused: mpCDPlayer.EnabledButtons := StopButtons;
end;
SplashScreen.Release; // Fecha e libera a tela de abertura
end;
Observe que a última linha do código neste método fecha o formulário da tela de abertura. O evento OnCreate do formulário principal geralmente é o melhor lugar para fazer isso.
247
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Atualizando as informações do CD Player
Enquanto o dispositivo de CD está tocando, você pode atualizar as informações em CDPlayerForm usando
um componente TTimer. Toda vez que ocorre um evento de timer, você pode chamar os métodos de atualização necessários, como mostramos no método OnCreate do formulário, para garantir que os dados continuem atualizados. Dê um clique duplo em Timer1 para gerar uma estrutura de método para seu evento
OnTimer. Veja o código-fonte que você utiliza para esse evento:
procedure TMainForm.tmUpdateTimerTimer(Sender: TObject);
{ Este método é o núcleo do CD Player. Ele atualiza todas as informações
a cada intervalo do timer. }
begin
if mpCDPlayer.EnabledButtons = PlayButtons then
begin
mpCDPlayer.TimeFormat := tfMSF;
ggDiskDone.Progress := (mci_msf_minute(mpCDPlayer.Position) * 60 +
mci_msf_second(mpCDPlayer.Position));
mpCDPlayer.TimeFormat := tfTMSF;
ShowTrackNumber; // Mostra núm. faixa em que o CD player se encontra
ShowTrackTime;
// Mostra tempo total da faixa atual
ShowCurrentTime; // Mostra tempo decorrido na faixa atual
end;
end;
Observe que, além de chamar os diversos métodos de atualização, este método também atualiza o
controle DiskDoneGauge para a quantidade de tempo decorrido no CD atual. Para obter o tempo decorrido,
o método muda a propriedade TimeFormat de CDPlayer para tfMSF e apanha o valor de minuto e segundo na
propriedade Position, usando as funções mci_msf_Minute( ) e mci_msf_Second( ). Isso precisa de mais alguma
explicação.
TimeFormat
A propriedade TimerFormat de um componente MediaPlayer determina como os valores das propriedades
StartPos, Length, Position, Start e EndPos deverão ser interpretados. A Tabela 18.3 lista os valores possíveis
para TimeFormat. Esses valores representam informações compactadas em uma variável do tipo Logint.
Tabela 18.3 Valores para a propriedade TMediaPlayer.TimeFormat
Valor
Formato de armazenamento de tempo
tfBytes
Número de bytes
Quadros
Horas, minutos e segundos
Tempo e milissegundos
Minutos, segundos e quadros
Número de amostras
Horas, minutos, segundos e quadros com base em 24 quadros por segundo
Horas, minutos, segundos e quadros com base em 25 quadros por segundo
Horas, minutos, segundos e quadros com base em 30 quadros por segundo
Horas, minutos, segundos e quadros com base em 24 quadros drop por segundo
Faixas, minutos, segundos e quadros
tfFrames
tfHMS
tfMilliseconds
tfMSF
tfSamples
tfSMPTE24
tfSMPTE25
tfSMPTE30
tfSMPTE30Drop
tfTMSF
248
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Rotinas de conversão de tempo
A API do Windows oferece rotinas para descobrir informações de tempo dos diferentes formatos em pacote mostrados na Tabela 18.4. Formato em pacote significa que diversos valores de dados estão empacotados (codificados) em um valor Longint. Essas funções estão localizadas em MMSystem.dll; por isso, não
se esqueça de incluir MMSystem na sua cláusula uses quando tiver de usar as funções.
Tabela 18.4 Funções para desempacotar formatos de tempo de multimídia
Função
Trabalha com
Retorna
mci_HMS_Hour( )
tfHMS
Horas
mci_HMS_Minute( )
tfHMS
Minutos
mci_HMS_Second( )
tfHMS
Segundos
mci_MSF_Frame( )
tfMSF
Quadros
mci_MSF_Minute( )
tfMSF
Minutos
mci_MSF_Second( )
tfMSF
Segundos
mci_TMSF_Frame( )
tfTMSF
Quadros
mci_TMSF_Minute( )
tfTMSF
Minutos
mci_TMSF_Second( )
tfTMSF
Segundos
mci_TMSF_Track( )
tfTMSF
Faixas
Métodos para atualizar o CD Player
Como vimos anteriormente neste capítulo, são utilizados vários métodos para ajudar a manter atualizadas as informações apresentadas pelo CD Player. A finalidade principal de cada um desses métodos é atualizar os labels na parte superior do formulário do CD Player e atualizar os medidores na parte do meio
desse formulário.
GetCDTotals( )
O objetivo do método GetCDTotals( ), mostrado no código a seguir, é apanhar a extensão e o número total
de faixas no CD atual. Essa informação é então usada para atualizar vários labels e DiskDoneGauge. Esse código também chama o método AdjustSpeedButtons( ), que ativa o mesmo número de botões de faixas correspondente ao número de faixas existentes. Observe que esse método também utiliza TimeFormat e as rotinas de conversão de tempo que vimos anteriormente.
procedure TMainForm.GetCDTotals;
{ Este método apanha o total de tempo e faixas do CD e os apresenta. }
var
TimeValue: longint;
begin
mpCDPlayer.TimeFormat := tfTMSF;
// define formato de hora
TimeValue := mpCDPlayer.Length;
// apanha tamanho do CD
TotalTracks := mci_Tmsf_Track(mpCDPlayer.Tracks); // apanha total de faixas
TotalLengthM := mci_msf_Minute(TimeValue); // apanha tamanho total em min.
TotalLengthS := mci_msf_Second(TimeValue); // apanha tamanho total em seg.
{ define texto do label Total Tracks }
LblTotTrk.Caption := TrackNumToString(TotalTracks);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
249
{ define texto do label Total Time }
LblTotalLen.Caption := Format(MSFormatStr, [TotalLengthM, TotalLengthS]);
{ inicializa medidor }
ggDiskDone.MaxValue := (TotalLengthM * 60) + TotalLengthS;
{ ativa o número correto de botões indicativos das faixas }
AdjustSpeedButtons;
end;
ShowCurrentTime( )
O método ShowCurrentTime( ) aparece no código a seguir. Esse método foi elaborado para obter o número
de minutos e segundos da faixa atualmente sendo tocada, além de atualizar os controles necessários.
Aqui, você também utiliza as rotinas de conversão de tempo fornecidas por MMSystem:
procedure TMainForm.ShowCurrentTime;
{ Este método apresenta o tempo atual dentro da faixa atual. }
begin
{ Minutos dentro desta faixa }
m := mci_Tmsf_Minute(mpCDPlayer.Position);
{ Segundos dentro desta faixa }
s := mci_Tmsf_Second(mpCDPlayer.Position);
{ atualiza label de tempo da faixa }
LblTrackTime.Caption := Format(MSFormatStr, [m, s]);
{ atualiza medidor da faixa }
ggTrackDone.Progress := (60 * m) + s;
end;
ShowTrackTime( )
O método ShowTrackTime( ), que aparece no código a seguir, obtém o tamanho total da faixa atual em minutos e segundos, e atualiza um controle de label. Novamente, você utiliza as rotinas de conversão de
tempo. Além disso, observe que você precisa verificar se a faixa não é a mesma de quando essa função foi
chamada pela última vez. Essa comparação garante que você não fará chamadas de função desnecessárias
ou modificará componentes desnecessariamente. Veja o código:
procedure TMainForm.ShowTrackTime;
{ Este método muda o tempo da faixa para exibir o tempo total da faixa
atualmente selecionada. }
var
Min, Sec: Byte;
Len: Longint;
begin
{ Não atualiza informações se o player ainda estiver na mesma faixa. }
if CurrentTrack < > OldTrack then
begin
Len := mpCDPlayer.TrackLength[mci_Tmsf_Track(mpCDPlayer.Position)];
Min := mci_msf_Minute(Len);
Sec := mci_msf_Second(Len);
ggTrackDone.MaxValue := (60 * Min) + Sec;
LblTrackLen.Caption := Format(MSFormatStr, [m, s]);
end;
OldTrack := CurrentTrack;
end;
250
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Código-fonte do CD Player
Você agora já viu todos os aspectos do CD Player, conforme se relacionam à multimídia. As Listagens
18.4 e 18.5 mostram o código-fonte completo para os módulos CDPlayer.dpr e CDMain.pas. A unidade CDMain também mostra algumas das técnicas que você utiliza para manipular os botões de faixas usando suas
propriedades Tag, bem como outras técnicas para atualização dos controles.
Listagem 18.4 O código-fonte para CDPlayer.dpr
program CDPlayer;
uses
Forms,
Splash in ‘Splash.pas’ { tela de abertura },
CDMain in ‘CDMain.pas’ { formulário principal };
begin
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
Listagem 18.5 O código-fonte para CDMain.pas
unit CDMain;
interface
uses
SysUtils, Windows, Classes, Graphics, Forms, Controls, MPlayer, StdCtrls,
Menus, MMSystem, Messages, Buttons, Dialogs, ExtCtrls, Splash, Gauges;
type
TMainForm = class(TForm)
tmUpdateTimer: TTimer;
MainScreenPanel: TPanel;
LblStatus: TLabel;
Label2: TLabel;
LblCurTrk: TLabel;
Label4: TLabel;
LblTrackTime: TLabel;
Label7: TLabel;
Label8: TLabel;
LblTotTrk: TLabel;
LblTotalLen: TLabel;
Label12: TLabel;
LblTrackLen: TLabel;
Label15: TLabel;
CDInfo: TPanel;
SBPanel: TPanel;
Panel1: TPanel;
mpCDPlayer: TMediaPlayer;
sbTrack1: TSpeedButton;
sbTrack2: TSpeedButton;
sbTrack3: TSpeedButton;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
251
Listagem 18.4 Continuação
sbTrack4: TSpeedButton;
sbTrack5: TSpeedButton;
sbTrack6: TSpeedButton;
sbTrack7: TSpeedButton;
sbTrack8: TSpeedButton;
sbTrack9: TSpeedButton;
sbTrack10: TSpeedButton;
sbTrack11: TSpeedButton;
sbTrack12: TSpeedButton;
sbTrack13: TSpeedButton;
sbTrack14: TSpeedButton;
sbTrack15: TSpeedButton;
sbTrack16: TSpeedButton;
sbTrack17: TSpeedButton;
sbTrack18: TSpeedButton;
sbTrack19: TSpeedButton;
sbTrack20: TSpeedButton;
ggTrackDone: TGauge;
ggDiskDone: TGauge;
Label1: TLabel;
Label3: TLabel;
procedure tmUpdateTimerTimer(Sender: TObject);
procedure mpCDPlayerPostClick(Sender: TObject; Button: TMPBtnType);
procedure FormCreate(Sender: TObject);
procedure sbTrack1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Declarações privadas }
OldTrack, CurrentTrack: Byte;
m, s: Byte;
TotalTracks: Byte;
TotalLengthM: Byte;
TotalLengthS: Byte;
procedure GetCDTotals;
procedure ShowTrackNumber;
procedure ShowTrackTime;
procedure ShowCurrentTime;
procedure ShowPlayerStatus;
procedure AdjustSpeedButtons;
procedure HighlightTrackButton;
function TrackNumToString(InNum: Byte): String;
end;
var
MainForm: TMainForm;
implementation
{$R *.DFM}
252
const
{ Array de strings representando números de um a vinte: }
NumStrings: array[1..20] of String[10] =
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 18.4 Continuação
(‘One’, ‘Two’, ‘Three’, ‘Four’, ‘Five’, ‘Six’, ‘Seven’, ‘Eight’, ‘Nine’,
‘Ten’, ‘Eleven’, ‘Twelve’, ‘Thirteen’, ‘Fourteen’, ‘Fifteen’, ‘Sixteen’,
‘Seventeen’, ‘Eighteen’, ‘Nineteen’, ‘Twenty’);
MSFormatStr = ‘%dm %ds’;
PlayButtons: TButtonSet = [btPause, btStop, btNext, btPrev];
StopButtons: TButtonSet = [btPlay, btNext, btPrev];
function TMainForm.TrackNumToString(InNum: Byte): String;
{ Esta função retorna um string correspondente a um inteiro entre 1 e 20.
Se o número for maior que 20, então o inteiro é retornado como um string. }
begin
if (InNum > High(NumStrings)) or (InNum < Low(NumStrings)) then
Result := IntToStr(InNum) { se não estiver no array, retorna só número }
else
Result := NumStrings[InNum]; { retorna string do array NumStrings }
end;
procedure TMainForm.AdjustSpeedButtons;
{ Este método ativa o número correto de botões de faixas }
var
i: integer;
begin
{ percorre o array de componentes do formulário... }
for i := 0 to SBPanel.ControlCount - 1 do
if SBPanel.Controls[i] is TSpeedButton then
// é um botão de faixa
{ desativa os botões maiores do que o número de faixas no CD }
with TSpeedButton(SBPanel.Controls[i]) do Enabled := Tag <= TotalTracks;
end;
procedure TMainForm.GetCDTotals;
{ Este método apanha o total de tempo e faixas do CD e os apresenta. }
var
TimeValue: longint;
begin
mpCDPlayer.TimeFormat := tfTMSF;
// define formato de hora
TimeValue := mpCDPlayer.Length;
// apanha tamanho do CD
TotalTracks := mci_Tmsf_Track(mpCDPlayer.Tracks); // apanha total de faixas
TotalLengthM := mci_msf_Minute(TimeValue); // apanha tamanho total em min.
TotalLengthS := mci_msf_Second(TimeValue); // apanha tamanho total em seg.
{ define texto do label Total Tracks }
LblTotTrk.Caption := TrackNumToString(TotalTracks);
{ define texto do label Total Time }
LblTotalLen.Caption := Format(MSFormatStr, [TotalLengthM, TotalLengthS]);
{ inicializa medidor }
ggDiskDone.MaxValue := (TotalLengthM * 60) + TotalLengthS;
{ ativa o número correto de botões indicativos das faixas }
AdjustSpeedButtons;
end;
procedure TMainForm.ShowPlayerStatus;
{ Este método apresenta o status do CD PLayer e do CD que está tocando
atualmente. }
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
253
Listagem 18.4 Continuação
begin
if mpCDPlayer.EnabledButtons = PlayButtons then
with LblStatus do
begin
case mpCDPlayer.Mode of
mpNotReady: Caption := ‘Not Ready’;
mpStopped: Caption := ‘Stopped’;
mpSeeking: Caption := ‘Seeking’;
mpPaused:
Caption := ‘Paused’;
mpPlaying: Caption := ‘Playing’;
end;
end
{ Se estes botões estiverem aparecendo, o CD Player deve ser parado. }
else if mpCDPlayer.EnabledButtons = StopButtons then
LblStatus.Caption := ‘Stopped’;
end;
procedure TMainForm.ShowCurrentTime;
{ Este método apresenta o tempo atual dentro da faixa atual. }
begin
{ Minutos dentro desta faixa }
m := mci_Tmsf_Minute(mpCDPlayer.Position);
{ Segundos dentro desta faixa }
s := mci_Tmsf_Second(mpCDPlayer.Position);
{ atualiza label de tempo da faixa }
LblTrackTime.Caption := Format(MSFormatStr, [m, s]);
{ atualiza medidor da faixa }
ggTrackDone.Progress := (60 * m) + s;
end;
procedure TMainForm.ShowTrackTime;
{ Este método muda o tempo da faixa para exibir o tempo total da faixa
atualmente selecionada. }
var
Min, Sec: Byte;
Len: Longint;
begin
{ Não atualiza informações se o player ainda estiver na mesma faixa. }
if CurrentTrack < > OldTrack then
begin
Len := mpCDPlayer.TrackLength[mci_Tmsf_Track(mpCDPlayer.Position)];
Min := mci_msf_Minute(Len);
Sec := mci_msf_Second(Len);
ggTrackDone.MaxValue := (60 * Min) + Sec;
LblTrackLen.Caption := Format(MSFormatStr, [m, s]);
end;
OldTrack := CurrentTrack;
end;
254
procedure TMainForm.HighlightTrackButton;
{ Este procedimento muda a cor do botão da faixa atual para vermelho,
enquanto muda a cor dos outros botões para azul-marinho. }
var
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 18.4 Continuação
i: longint;
begin
{ percorre os componentes do formulário }
for i := 0 to ComponentCount - 1 do
{ é um botão de faixa? }
if Components[i] is TSpeedButton then
if TSpeedButton(Components[i]).Tag = CurrentTrack then
{ passa para vermelho se for a faixa atual }
TSpeedButton(Components[i]).Font.Color := clRed
else
{ passa para azul se não for a faixa atual }
TSpeedButton(Components[i]).Font.Color := clNavy;
end;
procedure TMainForm.ShowTrackNumber;
{ Este método apresenta o número da faixa tocando atualmente. }
var
t: byte;
begin
t := mci_Tmsf_Track(mpCDPlayer.Position);
// apanha faixa atual
CurrentTrack := t;
// define var. de isntância
LblCurTrk.Caption := TrackNumToString(t);
// define texto do label Curr Track
HighlightTrackButton;
// Destaca botão de faixa atual
end;
procedure TMainForm.tmUpdateTimerTimer(Sender: TObject);
{ Este método é o núcleo do CD Player. Ele atualiza todas as informações
a cada intervalo do timer. }
begin
if mpCDPlayer.EnabledButtons = PlayButtons then
begin
mpCDPlayer.TimeFormat := tfMSF;
ggDiskDone.Progress := (mci_msf_minute(mpCDPlayer.Position) * 60 +
mci_msf_second(mpCDPlayer.Position));
mpCDPlayer.TimeFormat := tfTMSF;
ShowTrackNumber; // Mostra núm. faixa em que o CD player se encontra
ShowTrackTime;
// Mostra tempo total da faixa atual
ShowCurrentTime; // Mostra tempo decorrido na faixa atual
end;
end;
procedure TMainForm.mpCDPlayerPostClick(Sender: TObject;
Button: TMPBtnType);
{ Este método apresenta os botões corretos do CD Player quando um dos
botões é acionado. }
begin
Case Button of
btPlay:
begin
mpCDPlayer.EnabledButtons := PlayButtons;
LblStatus.Caption := ‘Playing’;
end;
btPause:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
255
Listagem 18.4 Continuação
begin
mpCDPlayer.EnabledButtons := StopButtons;
LblStatus.Caption := ‘Paused’;
end;
btStop:
begin
mpCDPlayer.Rewind;
mpCDPlayer.EnabledButtons := StopButtons;
LblCurTrk.Caption := ‘One’;
LblTrackTime.Caption := ‘0m 0s’;
ggTrackDone.Progress := 0;
ggDiskDone.Progress := 0;
LblStatus.Caption := ‘Stopped’;
end;
btPrev, btNext:
begin
mpCDPlayer.Play;
mpCDPlayer.EnabledButtons := PlayButtons;
LblStatus.Caption := ‘Playing’;
end;
end;
end;
256
procedure TMainForm.FormCreate(Sender: TObject);
{ Este método é chamado quando o formulário é criado. Ele abre e
inicializa o player. }
begin
try
mpCDPlayer.Open;
// Abre o dispositivo de CD Player.
{ Se um CD já estiver tocando na partida, mostra status. }
if mpCDPlayer.Mode = mpPlaying then
LblStatus.Caption := ‘Playing’;
GetCDTotals;
// Mostra total de tempo e faixas no CD atual
ShowTrackNumber; // Mostra faixa atual
ShowTrackTime;
// Mostra os minutos e segundos da faixa atual
ShowCurrentTime; // Mostra a posição atual do CD
ShowPlayerStatus; // Atualiza o status do CD Player
except
{ Se houver um erro, o sistema pode não conseguir tocar CDs. }
on EMCIDeviceError do
begin
MessageDlg(‘Error Initializing CD Player. Program will now exit.’,
mtError, [mbOk], 0);
Application.Terminate;
end;
end;
{ Verifica modo atual do CD-ROM e ativa os botões apropriados. }
case mpCDPlayer.Mode of
mpPlaying: mpCDPlayer.EnabledButtons := PlayButtons;
mpStopped, mpPaused: mpCDPlayer.EnabledButtons := StopButtons;
end;
SplashScreen.Release; // Fecha e libera a tela de abertura
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 18.4 Continuação
procedure TMainForm.sbTrack1Click(Sender: TObject);
{ Este método define a faixa atual quando o usuário pressiona um dos botões
de faixa. O método funciona com todos os 20 botões. Por isso, verificando
o ‘Sender’ ele pode saber qual botão foi pressionado, através da tag
do botão. }
begin
mpCDPlayer.Stop;
{ Define posição inicial no CD para início da faixa recém-selecionada }
mpCDPlayer.StartPos := mpCDPlayer.TrackPosition[(Sender as TSpeedButton).Tag];
{ Começa a tocar o CD na nova posição }
mpCDPlayer.Play;
mpCDPlayer.EnabledButtons := PlayButtons;
LblStatus.Caption := ‘Playing’;
end;
procedure TMainForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin
mpCDPlayer.Close;
end;
end.
Resumo
Com isso fechamos os conceitos básicos sobre o componente TMediaPlayer do Delphi. Este capítulo demonstra o poder e a simplicidade desse componente através de vários exemplos. Em particular, você
aprendeu sobre os formatos comuns de multimídia para áudio WAV, áudio/vídeo AVI e áudio de CD.
257
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Teste e
depuração
CAPÍTULO
19
NE STE CAP ÍT UL O
l
Bugs comuns do programa
l
Uso do depurador integrado
l
Resumo
258
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 8 — 2ª PROVA
Alguns programadores desta área acreditam que o conhecimento e a aplicação de boas práticas de programação tornam desnecessária a habilidade na depuração. No entanto, na realidade, os dois se complementam, e quem dominar a ambos colherá os maiores benefícios. Isso é especialmente verdadeiro quando vários programadores estão trabalhando em diferentes partes do mesmo programa. É simplesmente
impossível remover totalmente a possibilidade de erro humano.
Um número incrível de pessoas diz: “Meu código compila muito bem, e por isso não tenho bugs,
certo?” Errado! Não existe ralação alguma entre uma compilação sem erros e um programa sem bugs; há
uma grande diferença entre um código sintaticamente correto e um código logicamente correto e sem
bugs. Além do mais, não considere que, porque um trecho de código qualquer funcionou ontem ou em
outro sistema, ele está livre de bugs. Quando se trata de caçar bugs do software, tudo deverá ser considerado culpado, até que a inocência seja provada.
Durante o desenvolvimento de qualquer aplicação, você precisa permitir que o compilador o ajuda
ao máximo possível. Você pode fazer isso no Delphi ativando todas as opções de verificação de erros de
runtime em Project, Options, Compiler, como vemos na Figura 19.1, ou então ativando as diretivas necessárias no seu código. Além disso, você precisa marcar as opções Show Hints (mostrar sugestões) e
Show Warnings (mostrar advertências) na mesma caixa de diálogo, a fim de receber mais informações
sobre o seu código. É comum que um programador gaste horas desnecessárias tentando localizar “aquele
bug impossível” quando poderia ter descoberto o erro imediatamente, simplesmente empregando essas
ferramentas eficazes do compilador. (Naturalmente, os autores nunca seriam culpados por deixar de
aconselhar o uso dessas ferramentas. Concorda com isso?)
A Tabela 19.1 descreve as diferentes opções de erro de runtime disponíveis no Delphi.
FIGURA 19.1
A página Compiler da caixa de diálogo Project Options.
Tabela 19.1 Erros de runtime do Delphi
Erro de runtime
Diretiva
Função
Verificação de faixa
{$R+}
Verifica se você não indexou um array ou string além de
seus limites e se as atribuições não definem um valor a uma
variável escalar fora de seu intervalo.
Verificação de I/O
{$I+}
Verifica um erro de entrada/saída após cada chamada de
I/O (ReadLn( ) e WriteLn( ), por exemplo). Essa diretiva
quase sempre deverá ser ativada.
Verificação de estouro
{$Q+}
Verifica se resultados de cálculos não são maiores do que o
tamanho do registrador.
259
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DICA
Lembre-se de que cada um desses erros de runtime acarreta um prejuízo no desempenho da sua aplicação.
Portanto, quando você tiver saído da fase de depuração do desenvolvimento e estiver pronto para remeter
um produto final, poderá melhorar o desempenho desativando algumas das verificações de erro de runtime. É uma prática comum entre os programadores desativar todas elas, exceto a verificação de I/O, para o
produto final.
Bugs comuns do programa
Esta seção mostra alguns erros normalmente cometidos, os quais causam a interrupção ou a parada dos
programas. Se você souber o que procurar quando estiver depurando o código, poderá reduzir o tempo
necessário para encontrar erros.
Usando uma variável de classe antes que seja criada
Um dos bugs mais comuns que o atormentam quando você desenvolve em Delphi ocorre porque você usou
uma variável de classe antes que ela tenha sido criada. Por exemplo, dê uma olhada no código a seguir:
procedure TForm1.Button1Click(Sender: TObject);
var
MyStringList: TStringList;
begin
MyStringList.Assign(ListBox1.Items);
end;
A MyStringList da classe TStringList foi declarada; no entanto, ela foi usada antes de ser instanciada. Essa é combinação perfeita para causar uma violação de acesso. Você precisa certificar-se de instanciar quaisquer variáveis de classe antes de tentar usá-las. O código a seguir mostra a forma correta de
instanciar e usar uma variável de classe. No entanto, ele também introduz outro bug. Você conseguiria
encontrá-lo?
procedure TForm1.Button1Click(Sender: TObject);
var
MyStringList: TStringList;
begin
MyStringList := TStringList.Create;
MyStringList.Assign(ListBox1.Items);
end;
Se a sua resposta foi “Você não liberou sua classe TStringList”, então você está correto. Isso não causará uma interrupção ou falha no seu programa, mas consumirá memória porque, toda vez que você chamar o método, outra TStringList será criada e ficará na memória, ocupando espaço. Embora a API do
Win32 libere toda a memória alocada pelo seu processo no momento em que ele termina, o desperdício
de memória enquanto uma aplicação está sendo executada pode causar sérios problemas. Por exemplo,
uma aplicação que desperdiça memória continuará a devorar cada vez mais recursos de memória enquanto está rodando, fazendo com que o sistema operacional tenha de realizar cada vez mais paginação
em disco, o que por fim prejudicará o sistema inteiro.
A versão correta da listagem de código anterior aparece no código a seguir (menos uma melhoria
necessária, discutida no próximo tópico):
procedure TForm1.Button1Click(Sender: TObject);
260 var
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
MyStringList: TStringList;
begin
MyStringList := TStringList.Create;
// Crie!
MyStringList.Assign(ListBox1.Items);
// Use!
{ Faça o que quiser com sua instância de TStringList }
MyStringList.Free;
// Libere!
end;
Garantindo que as instâncias da classe sejam liberadas
Suponha que, no exemplo de código anterior, ocorra uma exceção logo depois que STringList for criado.
A exceção faria com que o fluxo de execução saísse imediatamente do procedimento, e nenhum código
restante no procedimento seria executado, o que causaria uma perda de memória. Cuide para que suas
instâncias de classe sejam liberadas, mesmo que haja uma exceção, usando uma construção try...finally,
da seguinte forma:
procedure TForm1.Button1Click(Sender: TObject);
var
MyStringList: TStringList;
begin
MyStringList := TStringList.Create;
// Crie!
try
MyStringList.Assign(ListBox1.Items);
// Use!
{ Faça o que quiser com sua instância de TStringList }
finally
MyStringList.Free;
// Libere!
end;
end;
Depois de ler a seção “Pontos de interrupção”, mais adiante no capítulo, faça uma experiência e coloque a linha a seguir logo após a linha na qual você atribui os itens de ListBox1 para a TStringList:
raise Exception.Create(‘Test Exception’);
Depois coloque um ponto de interrupção no início do código do método e acompanhe cada linha
do código. Você verá que TStringList ainda é liberada, mesmo depois que a exceção foi gerada.
Dominando o ponteiro maluco
O bug do ponteiro maluco é um erro comum que acaba com uma parte da memória quando você usa o
ponteiro para escrever algo na memória. O gênero do ponteiro maluco possui duas espécies comuns: o
ponteiro não inicializado e o ponteiro caduco.
Um ponteiro não inicializado é uma variável de ponteiro que foi usada antes que a memória fosse
alocada para ele. Quando tal ponteiro é usado, você acaba escrevendo em qualquer endereço que esteja
no local da variável de ponteiro. O exemplo de código a seguir ilustra um ponteiro não inicializado:
var
P: ^Integer;
begin
P^ := 1971;
// Oh louco! P não está inicializado!
Um ponteiro caduco é um ponteiro que referencia uma área da memória que já foi alocada corretamente, mas que foi liberada. O código a seguir mostra um ponteiro caduco:
var
261
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
P: ^Integer;
begin
New(P);
P^ := 1971;
Dispose(P);
P^ := 4;
// Oh louco! P está caduco!
Se você estiver com sorte, receberá uma violação de acesso quando tentar escrever em um ponteiro
maluco. Se não estiver, acabará escrevendo sobre dados usados por alguma outra parte da sua aplicação.
Esse tipo de erro não é nada divertido para se depurar. Em uma máquina, o ponteiro pode parecer rodar
muito bem até que você o transfere para outra máquina (e talvez faça algumas mudanças no código nesse
processo), onde começará a apresentar problemas. Isso poderá fazê-lo crer que as mudanças feitas recentemente causaram o problema, ou que a segunda máquina possui um problema no hardware. Quando
você tiver caído nessa armadilha, nem toda a boa prática de programação do mundo poderá salvá-lo.
Você pode começar a incluir instâncias de ShowMessage( ) em partes do código, na tentativa de descobrir o
problema, mas isso só servirá para modificar o local do código na memória, e poderá fazer com que o bug
se desloque – ou pior, que desapareça! Sua melhor defesa contra os bugs de ponteiro maluco é evitá-los
em primeiro lugar. Sempre que você tiver de trabalhar com ponteiros e alocação manual de memória,
não deixe de verificar seus algoritmos e verificar novamente, evitando algum erro tolo que possa introduzir um bug.
Usando variáveis tipo PChar não inicializadas
Você normalmente verá erros de ponteiro maluco quando usar variáveis tipo PChar. Visto que um PChar é
apenas um ponteiro para uma string, é preciso lembrar-se de alocar memória para o PChar usando a função StrAlloc( ), GetMem( ), StrNew( ), GlobalAlloc( ) ou VirtualAlloc( ), além de usar a função FreeMem( ),
StrDispose( ), GlobalFree( ) ou VirtualFree( ) para liberá-lo.
DICA
Você pode evitar bugs em potencial no seu programa usando variáveis tipo string sempre que possível, em
vez de PChars. Você pode usar typecast para passar um string para um PChar, de modo que o código envolvido seja simples, e como strings são alocados e liberados automaticamente, você não tem de se preocupar com alocação de memória.
Isso também serve especialmente para aplicações do Delphi 1.0. No Delphi 1.0, PChars são um mal necessário. No Delphi de 32 bits, eles são necessários apenas em ocasiões raras. Gaste algum tempo passando
para strings à medida que transportar suas aplicações para o Delphi de 32 bits.
Desreferenciando um ponteiro nulo
Além do ponteiro maluco, outro erro comum é desreferenciar um ponteiro nil (valor zero). Isso sempre
causará um erro de violação de acesso emitido pelo sistema operacional.. Embora não seja um erro que
você queira ter em sua aplicação, geralmente ele não é fatal. Visto que ele não modifica realmente a memória, é seguro usar o tratamento de exceção para cuidar da exceção e seguir adiante. O procedimento
de exemplo na listagem de código a seguir ilustra esse ponto:
262
procedure I_AV;
var
P: PByte;
begin
P := Nil;
try
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
P^ := 1;
except
on EAccessViolation do
MessageDlg(‘You can’t do that!!’, mtError, [mbOk], 0);
end;
end;
Se você colocar esse procedimento em um programa, verá que a caixa de diálogo de mensagem aparece para informar quanto ao problema, mas seu programa continua rodando.
Uso do depurador integrado
O Delphi oferece um depurador com todos os recursos embutidos diretamente no IDE. A maioria das facilidades do depurador integrado pode ser encontrada no menu Run. Essas facilidades incluem todos os
recursos que você esperaria encontrar em um depurador profissional, incluindo a capacidade de especificar parâmetros da linha de comandos para a sua aplicação, definir pontos de interrupção, realizar trace e
step (depuração total e parcial), incluir e exibir watches (inspeções), avaliar e modificar dados, além de
ver informações da pilha de chamada.
Usando parâmetros da linha de comandos
Se o seu programa for projetado para usar parâmetros da linha de comandos, você poderá especificá-los
na caixa de diálogo Run Parameters (parâmetros de execução). Nessa caixa de diálogo, basta digitar os
parâmetros conforme faria na linha de comandos ou na caixa de diálogo Run (ou Executar) do menu
Start (ou Iniciar) do Windows.
Pontos de interrupção
Os pontos de interrupção permitem que você suspenda a execução do seu programa sempre que uma certa condição for atendida. O tipo mais comum de ponto de interrupção é um ponto de interrupção no código, que ocorre quando uma determinada linha de código é executada. Você pode definir um ponto de
interrupção no código dando um clique no canto esquerdo de uma linha no Code Editor, usando o menu
local ou selecionando Run, Add Breakpoint (adicionar ponto de interrupção). Sempre que você quiser
ver como seu programa está se comportando dentro de um determinado procedimento ou função, basta
definir um ponto de interrupção na primeira linha de código dessa rotina. A Figura 19.2 mostra um ponto de interrupção no código, marcado em uma linha de código do programa.
Pontos de interrupção condicionais
Você pode incluir informações adicionais em um ponto de interrupção no código para suspender a execução do seu programa quando ocorrer alguma condição além de quando uma linha de código for atingida. Um exemplo típico é quando você deseja examinar o código dentro de uma construção de loop. Provavelmente você não deseja suspender e retomar a execução toda vez que seu código repetir o loop, especialmente se ele ocorre centenas de vezes, ou talvez milhares. Em vez de pressionar continuamente a tecla
F9 para executar, basta definir um ponto de interrupção que ocorrerá sempre que uma variável atingir
um certo valor. Por exemplo, em um novo projeto, coloque um TButton no formulário principal e inclua o
código a seguir no manipulador de evento do botão:
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
for I := 1 to 100 do
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
263
begin
Caption := IntToStr(I);
Button1.Caption := IntToStr(I);
Application.ProcessMessages;
end;
end;
FIGURA 19.2
// atualiza formulário
// atualiza botão
// permite que haja atualizações
Um ponto de interrupção no código, definido no Code Editor.
Agora defina um ponto de interrupção na seguinte linha:
Caption := IntToStr(I);
// atualiza formulário
Depois de ter definido um ponto de interrupção, selecione View, Debug Windows, Breakpoints,
que trará uma caixa de diálogo Breakpoint List (lista de pontos de interrupção). Seu ponto de interrupção deverá aparecer nessa lista. Dê um clique com o botão direito no seu ponto de interrupção e selecione Properties no menu local. Isso fará surgir a caixa de diálogo Edit Breakpoint (editar ponto de interrupção), como mostra a Figura 19.3. Na caixa à direita de Condition, digite I = 50 e acione OK. Isso fará
com que o ponto de interrupção que você definiu anteriormente suspenda a execução do programa apenas quando a variável I chegar ao valor 50.
DICA
A Figura 19.3 mostra o recurso de ações para o ponto de interrupção, que é um recurso novo no Delphi 5. As
ações do ponto de interrupção permitem especificar o comportamento exato do depurador quando um ponto de interrupção for encontrado. Essas ações são controladas por meio de três caixas de seleção que aparecem na figura. Break, como você poderia imaginar, instrui o depurador a interromper quando o ponto de interrupção for encontrado. Ignore Subsequent Exceptions (ignorar exceções subseqüentes) faz com que o depurador não interrompa quando forem encontradas exceções a partir do ponto de interrupção. Handle Subsequent Exceptions (tratar exceções subseqüentes) faz com que o depurador retome o comportamento default
de interromper quando as exceções forem encontradas do ponto de interrupção em diante.
As duas últimas opções foram planejadas para serem usadas uma após a outra. Se você tiver um código em
particular que esteja causando problemas com o surgimento de exceções no depurador e não quiser ser
notificado a respeito disso, poderá usar essas opções de ponto de interrupção para instruir o depurador a
ignorar exceções antes de entrar no bloco de código e começar a tratar das exceções mais uma vez depois
que sair do bloco.
264
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 19.3
A caixa de diálogo Edit Breakpoint.
Pontos de interrupção de dados
Os pontos de interrupção de dados são aqueles que você pode definir para que ocorram quando a memória em um determinado endereço for modificada. Isso é útil para depuração em baixo nível, quando você
precisa rastrear bugs que talvez ocorram quando uma variável é atribuída. Você pode definir pontos de
interrupção de dados selecionando Run, Add Breakpoint, Data Breakpoint no menu principal ou usando
um menu (ou de contexto) local na caixa de diálogo Breakpoint List. Isso faz surgir a caixa de diálogo
Add Data Breakpoint (adicionar ponto de interrupção de dados), como mostra a Figura 19.4. Nessa caixa de diálogo, você pode incluir o endereço inicial da área de memória que você pretende monitorar e a
extensão (número de bytes) para monitorar após esse endereço. Especificando o número de bytes, você
pode observar qualquer coisa desde um Char (um byte) até um Integer (quatro bytes) ou um array ou record (de qualquer quantidade de bytes). De uma forma semelhante aos pontos de interrupção no código,
a caixa de diálogo Add Data Breakpoint também permite incluir uma expressão que será avaliada quan-
FIGURA 19.4
A caixa de diálogo Add Data Breakpoint.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
265
do a região da memória for escrita, de modo que você pode localizar bugs que ocorram na enésima vez
em que uma região da memória é definida. Se você quiser que o depurador interrompa quando uma variável específica for modificada, basta informar o nome da variável no campo de endereço.
Pontos de interrupção de endereço
Um ponto de interrupção de endereço é aquele que você pode definir para que ocorra quando o código
residente em um determinado endereço for modificado. Esses tipos de pontos de interrupção normalmente são definidos a partir do menu local na visão da CPI, quando você não pode definir um ponto de
interrupção no código porque não possui o código-fonte para um determinado módulo. Assim como outros tipos de pontos de interrupção, você também pode especificar uma condição para os pontos de interrupção de endereço, a fim de ajustar melhor seus pontos de interrupção.
Pontos de interrupção de carregamento de módulo
Como você provavelmente já deve ter deduzido pelo nome, os pontos de interrupção de carregamento de
módulo permitem definir pontos de interrupção que ocorrem quando um módulo especificado é carregado no processo da aplicação sendo depurada. Com isso, você pode ser notificado imediatamente quando uma DLL ou um pacote for carregado por uma aplicação. O local mais comum para se definir os pontos de interrupção de carregamento de módulo é o menu local na janela Modules, mas eles também podem ser definidos com o uso do item Run, Add Breakpoint no menu principal.
Grupos de ponto de interrupção
Os grupos de ponto de interrupção são um dos recursos mais poderosos que o depurador integrado oferece, para que você também possa ganhar tempo. Usando grupos, qualquer ponto de interrupção pode
ser configurado para ativar ou desativar qualquer outro ponto de interrupção, de modo que pode ser criado um algoritmo muito complexo de pontos de interrupção para encontrar bugs muito específicos. Suponha que você suspeite de que um bug aparece no seu método Paint( ) apenas depois que escolhe uma
determinada opção de menu. Você poderia incluir um ponto de interrupção no método Paint( ), rodar o
programa e constantemente dizer ao depurador para continuar quando houver uma enxurrada de centenas de chamadas ao seu método Paint( ). Como alternativa, você poderia manter esse ponto de interrupção no seu método Paint( ), desativá-lo de modo que não dispare e depois incluir outro ponto de interrupção no seu manipulador de evento do item do menu para ativar o ponto de interrupção do método
Paint( ). Agora, você pode rodar em toda a velocidade no depurador e não interromper no seu manipulador de Paint( ) até que selecione a opção do menu.
Executando código linha a linha
Você pode executar código linha a linha usando a opção Step Over (depuração parcial) ou Trace Into
(depuração total) – teclas F8 e F7, respectivamente, no mapeamento de teclas Default e clássico do IDE).
Trace Into entra em cada procedimento e função à medida que são chamados; Step Over executa o procedimento ou a função imediatamente, sem parar a cada linha. Normalmente, você usa essas opções depois de parar em algum lugar do código com um ponto de interrupção. Aprenda a usar as teclas F7 e F8;
elas são suas amigas.
Você também pode dizer ao Delphi para rodar seu programa até a linha em que o cursor se encontra
atualmente, usando a opção Run To Cursor (executar até o cursor) – ou a tecla F4. Isso é útil particularmente quando você deseja evitar um loop que será repetido muitas vezes, quando o uso de F7 ou F8 for
bastante cansativo. Lembre-se de que você pode definir pontos de interrupção a qualquer momento no
Code Editor – mesmo que o seu programa esteja sendo executado; você não precisa definir todos os pontos de interrupção em primeiro lugar.
266
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DICA
Se você acidentalmente entrar em uma função que será muito difícil ou demorada de se percorrer, escolha
Run, Run Until Return (executar até retornar) no menu principal para fazer com que o depurador seja interrompido depois que o procedimento ou função atual retornar.
Você pode interromper seu código dinamicamente usando a opção Program Pause (pausa no programa). Essa opção constantemente lhe ajudará a determinar se o seu programa está em um loop infinito.
Lembre-se de que o código da VCL está sendo rodado na maior parte do tempo, e por isso provavelmente você não irá parar em uma linha de código do seu programa com essa opção.
DICA
Quando você depurar sua aplicação, provavelmente notará os pontos azuis que aparecem na margem esquerda da janela do Code Editor. Um desses pontos azuis aparece ao lado de cada linha de código para a
qual o código de máquina é gerado. Você não pode definir um ponto de interrupção ou parar em uma linha de código qualquer se ela não tiver um ponto azul ao lado, pois não existe um código de máquina associado à linha.
Usando a janela Watch
Você pode usar a janela Watch (inspeção) para observar os valores das variáveis do seu programa enquanto seu código é executado. Lembre-se de que você precisa estar em uma visão de código do seu programa (um ponto de interrupção deve ser alcançado) para que o conteúdo da janela Watch seja preciso.
Você poderá entrar com uma expressão do Object Pascal ou com o nome de um registrador na janela
Watch. Isso pode ser visto na Figura 19.5.
FIGURA 19.5
Usando a janela Watch List.
Debug Inspectors
Um inspetor de depuração (Debug Inspector) é uma espécie de inspetor de dados que talvez seja mais fácil de usar e mais poderoso de alguma maneiras do que a janela Watch. Para usar esse recurso, selecione
Run, Inspect enquanto estiver depurando uma aplicação. Isso fará surgir uma caixa de diálogo simples,
na qual você poderá incluir uma expressão. Dê um clique em OK e você verá uma janela do Debug
Inspector para a expressão incluída. Por exemplo, a Figura 19.6 mostra o Debug Inspector para o formulário principal de uma aplicação do Delphi que não faz nada.
A janela do Debug Inspector oferece um meio de exibir convenientemente dados que consistem em
muitos elementos individuais, como classes e registros. Dê um clique nas reticências à direita da coluna
de valor no Inspector para modificar o valor de um campo. Você pode ainda desmembrar os membros de
dados do registro ou da classe com um clique duplo em um campo desse tipo na lista.
267
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 19.6
Inspecionando um formulário através do Debug Inspector.
Usando as opções Evaluate e Modify
As opções Evaluate e Modify permitem inspecionar e alterar o conteúdo de variáveis, incluindo arrays e
registros, enquanto sua aplicação é executada no depurador integrado. Saiba, porém, que esse recurso
não permite acessar funções ou variáveis que esteja fora do escopo.
ATENÇÃO
A avaliação e a modificação de variáveis talvez sejam um dos recursos mais poderosos do depurador integrado, mas com esse poder vem também a responsabilidade de ter acesso direto à memória. Você precisa
ter cuidado ao mudar os valores das variáveis, pois as mudanças podem afetar o comportamento do seu
programa mais tarde.
Acessando a pilha de chamada
Você pode acessar a pilha de chamada escolhendo View, Debug Windows, Call Stack. Isso permite exibir
chamadas de função e procedimento junto com os parâmetros que são passados. A pilha de chamada é
útil para se ter um mapa indicativo das funções que foram chamadas até o ponto atual no seu código-fonte. A Figura 19.7 mostra uma visão típica da janela Call Stack.
FIGURA 19.7
A janela Call Stack.
268
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DICA
Para visualizar qualquer procedimento ou função listada na janela Call Stack, basta dar um clique com o
botão direito dentro da janela. Esse é um bom truque para voltar a ver uma função quando você tiver ido
longe demais sem perceber.
Exibindo threads
Se a sua aplicação utiliza vários threads, o depurador integrado permite obter informações sobre os diversos threads na sua aplicação, através da janela Thread Status. Selecione View, Debug Windows, Threads no menu principal para chamar essa janela. Quando sua aplicação for interrompida (tiver atingido
um ponto de interrupção), você poderá usar o menu local fornecido por essa janela para ativar outro
thread ou ver o código associado a um thread em particular. Lembre-se de que, sempre que modifica o
thread atual, o próximo comando de execução total ou parcial que você emite será relativo a esse thread.
A Figura 19.8 mostra a janela Thread Status.
FIGURA 19.8
A janela Thread Status.
Event Log
O Event Log (registro de eventos) oferece um local no qual o depurador anotará um registro para a ocorrência de diversos eventos. O Event Log, mostrado na Figura 19.9, é acessível a partir do menu View,
Debug. Você pode configurar o Event Log usando seu menu local ou a página Debugger da caixa de diálogo Tools, Environment Options.
FIGURA 19.9
O Event Log.
Os tipos de eventos que você pode registrar incluem informações como início de processo, parada
de processo e pontos de interrupção do depurador no carregamento de módulos, além de mensagens do
Windows enviadas à aplicação e a saída da aplicação por meio de OutputDebugString( ).
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
269
DICA
A função da API OutputDebugString( ) oferece um meio prático de ajudá-lo a depurar suas aplicações. O
único parâmetro de OutputDebugString( ) é um PChar. O string passado nesse parâmetro será então passado para o depurador, e no caso do Delphi, o string será incluído no Event Log. Isso permite acompanhar
os valores de variáveis ou informações de depuração semelhantes sem ter de usar watches (inspeções) ou
exibir caixas de diálogo de depuração, que aparecem enchendo a tela.
Visão de módulos
A visão de módulos permite obter informações sobre todos os módulos (EXE, DLL, BPL e assim por diante)
sendo carregados no processo da aplicação em questão. Como podemos ver na Figura 19.10, essa janela
oferece uma lista de quem é quem no processo da sua aplicação, permite que você defina pontos de interrupção de carregamento de módulo e oferece vários tipos de informações sobre cada módulo.
FIGURA 19.10
A visão dos módulos.
Depuração de DLL
O depurador integrado do Delphi oferece a capacidade de depurar seus projetos de DLL usando qualquer aplicação arbitrária como host. Na verdade, isso é muito fácil. Abra seu projeto de DLL e selecione
Run, Parameters no menu principal. Depois especifique uma aplicação host na caixa de diálogo Run Parameters, como mostra a Figura 19.11.
FIGURA 19.11
270
Especificando uma aplicação host.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
A aplicação host é um arquivo executável que carrega e utiliza a DLL que você está atualmente depurando. Depois de especificar uma aplicação host apropriada, você poderá usar o depurador integrado
da mesma forma como faria para depurar um executável normal; definindo pontos de interrupção, efetuando depuração total ou parcial, e tudo o mais.
Esse recurso é mais útil para a depuração de controles ActiveX e servidores COM em processo, que
são executados de dentro do contexto de outro processo. Por exemplo, você pode usar esse recurso para
depurar seu controle ActiveX de dentro do Visual Basic.
A visão da CPU
A visão da CPU, ativada pela seleção de View, Debug Windows, CPU no menu principal, contém uma visualização, do ponto de vista do programador, para o que está acontecendo no interior da CPU da máquina. A visão da CPU consiste em cinco painéis de informações: o painel CPU, o painel Memory Dump,
o painel Register, o painel Flags e o painel Stack (ver Figura 19.12). Cada um desses painéis permite que
o usuário veja aspectos importantes do processador como um auxílio à depuração.
FIGURA 19.12
A visão da CPU.
O painel CPU mostra os códigos de operação (opcodes) e mnemônicos do código Assembly “desassemblado” que está sendo executado. Você pode posicionar o painel CPU em qualquer endereço no seu
processo para exibir instruções, ou pode definir o ponteiro de instrução atual para qualquer novo local
de onde a execução continuará. É importante poder entender o código Assembly que o painel CPU apresenta, e os programadores experientes atestarão o fato de que muitos bugs foram encontrados e exterminados pelo exame do código Assembly gerado para uma rotina e a observação de que não estava realizando a operação desejada. Obviamente, alguém que não entenda a linguagem Assembly não conseguiria de
forma alguma encontrar tal bug por essa janela.
O menu local da visão da CPU permite mudar o modo como os itens são apresentados, ver um endereço diferente, voltar para o ponteiro de instrução atual (EIP), localizar, voltar para o código-fonte, e
assim por diante. Você também pode escolher o contexto de thread em que verá as informações da CPU.
O painel Memory Dump permite ver o conteúdo de qualquer intervalo de memória. Existem muitas maneiras como ela pode ser vista – como Byte, Word, DWORD, QWORD, Single, Double ou Extended. Você pode
procurar na memória uma seqüência de bytes, além de modificar os dados ou acompanhá-la como código ou ponteiros de dados.
Os painéis Register e Flags são muito simples. Todos os registradores e flags da CPU são apresentados aqui, e também podem ser modificados.
O painel Stack oferece uma visão da memória com base na pilha usada pelo programa. Nesse painel, você pode mudar os valores dos dados na pilha e seguir endereços.
271
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Resumo
Este capítulo ofereceu diversos detalhes relacionados ao processo de depuração. Ele mostrou os problemas comuns com que você pode se deparar enquanto desenvolve aplicações, e discute os recursos úteis
do depurador integrado e outros independentes. É importante lembrar que a depuração é tanto parte da
programação quanto a escrita do código. O depurador pode ser um dos seus aliados mais poderosos na
escrita de um código claro; portanto, gaste algum tempo para conhecê-lo bem.
Na próxima parte do livro, você entrará no âmbito do desenvolvimento baseado em componentes,
com componentes COM e VCL.
272
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Extensão da VCL de
banco de dados
CAPÍTULO
30
NE STE CAP ÍT UL O
l
Utilização do BDE
l
Tabelas do dBASE
l
Tabelas do Paradox
l
Extensão de TDataSet
l
Resumo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 9 — 2ª PROVA
Originalmente, a arquitetura de banco de dados da VCL (Visual Component Library) vem equipada para
se comunicar principalmente por meio do mecanismo de banco de dados da Borland (BDE, Borland Database Engine) – middleware de banco de dados confiável e de muitos recursos. Mais do que isso, a VCL
serve como um tipo de isolador entre você e seus bancos de dados, permitindo que você acesse diferentes
tipos de bancos de dados praticamente da mesma maneira. Embora tudo isso signifique confiabilidade,
escalabilidade e facilidade de uso, existe um lado negativo: recursos específicos do banco de dados, fornecidos dentro e fora do BDE, geralmente não estão preparados na estrutura de banco de dados da VCL.
Este capítulo oferece o conhecimento que você deverá ter para estender a VCL, comunicando-se diretamente com o BDE e outras origens de dados para obter a funcionalidade de banco de dados não disponível de outra forma no Delphi.
Utilização do BDE
Ao escrever aplicações que fazem chamadas diretas ao BDE, existem algumas regras práticas que você
precisa ter em mente. Esta seção apresenta as informações gerais de que você precisa para entrar na API
do BDE a partir de suas aplicações em Delphi.
A unidade BDE
Todas as funções, tipos e constantes do BDE são definidas na unidade BDE. Essa unidade terá que estar na
cláusula uses de qualquer unidade da qual você queira fazer chamadas ao BDE. Além disso, a parte de interface da unidade BDE está disponível no arquivo BDE.INT, que você encontrará no diretório ..\Delphi5\
Doc. Você pode usar esse arquivo como uma referência para as funções e os registros à sua disposição.
DICA
Para obter ajuda adicional sobre a programação usando a API do BDE, dê uma olhada no arquivo de ajuda BDE32.hlp, contido no seu diretório BDE (o caminho padrão para esse diretório é \Arquivos de programas\Borland\Common Files\BDE). Esse arquivo contém informações detalhadas sobre todas as funções da
API do BDE e exemplos muito bons em Object Pascal e C.
Check( )
Todas as funções do BDE retornam um valor do tipo DBIRESULT, que indica o sucesso ou a falha da chamada à função. Em vez de percorrer o processo confuso de verificar o resultado de cada chamada de função
do BDE, o Delphi define um procedimento chamado Check( ), que aceita um DBIRESULT como parâmetro.
Esse procedimento gerará uma exceção quando o DBIRESULT indicar qualquer valor exceto o sucesso. O
código a seguir mostra como fazer e como não fazer uma chamada a uma função do BDE:
// !!Não faça isto:
var
Rez: DBIRESULT;
A: array[0..dbiMaxUserNameLen] of char;
begin
Rez := dbiGetNetUserName(A);
// faz chamada ao BDE
if Rez < > DBIRR_NONE then
// trata do erro
// trata do erro aqui
else begin
// continua com a função
end;
end;
274
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
// !!Faça isto:
var
A: array[0..dbiMaxUserNameLen] of char;
begin
{ Trata do erro e faz chamada ao BDE ao mesmo tempo. }
{ Exceção será gerada em caso de erro. }
Check(dbiGetNetUserName(A));
// continua com a função
end;
Cursores e alças
Muitas funções do BDE aceita como parâmetros alças para cursores ou bancos de dados. Resumindo,
uma alça de cursor é um objeto do BDE que representa um determinado conjunto de dados posicionados
em alguma linha em particular nesses dados. O tipo de dados de uma alça de cursor é hDBICur. O Delphi
expõe esse conceito como o registro atual em uma tabela, consulta ou procedimento armazenado. As
propriedades Handle de TTable, TQuery e TStoredProc contêm essa alça de cursor. Lembre-se de passar o Handle de um desses objetos a qualquer função do BDE que exija um hDBICur.
Algumas funções do BDE também exigem uma alça para um banco de dados. Uma alça de banco de
dados do BDE possui o tipo hDBIDb, e representa um determinado banco de dados aberto – seja um diretório local ou em rede no caso do dBASE ou Paradox, ou um arquivo de banco de dados de servidor no
caso de um banco de dados de servidor SQL. Você pode obter essa alça de um TDatabase por meio de sua
propriedade Handle. Se você não estiver se conectando a um banco de dados por meio de um objeto TDatabase, as propriedades DBHandle de TTable, TQuery e TStoredProc também terão essa alça.
Sincronizando cursores
Foi estabelecido que um dataset aberto no Delphi possui o conceito de um registro atual, enquanto o BDE
básico mantém o conceito de um cursor que aponta para algum registro em particular em um dataset. Devido ao modo como o Delphi realiza o caching de registro para otimizar o desempenho, às vezes o registro
atual do Delphi não está em sincronismo com o cursor básico do BDE. Normalmente, isso não é problema
porque esse comportamento é tratado normalmente para a estrutura de banco de dados da VCL.
No entanto, se você quiser fazer uma chamada direta a uma função do BDE que espera um cursor
como parâmetro, terá de garantir que a posição atual do cursor no Delphi está em sincronismo com o
cursor básico do BDE. Pode parecer uma tarefa assustadora, mas na realidade é muito fácil de se fazer.
Basta chamar o método UpdateCursorPos( ) de um descendente de TDataSet para realizar esse sincronismo.
Em um estilo semelhante, depois de fazer uma chamada ao BDE que modifique a posição do cursor
básico, você precisa informar à VCL de que ela precisa sincronizar novamente sua própria posição de registro atual com a do BDE. Para fazer isso, é preciso chamar o método CursorPosChanged( ) dos descendentes de TDataSet imediatamente depois da chamada ao BDE. O código a seguir demonstra como usar essas
funções de sincronismo do cursor:
procedure DoSomephingWithTable(T: TTable);
begin
T.UpdatecursorPos;
// chama a função (ou funções) do BDE que modifica(m) posição do cursor
T.CursorPosChanged;
end;
Tabelas do dBASE
As tabelas do dBASE possuem diversos recursos úteis que não são aceitos diretamente pelo Delphi. Esses
recursos incluem, entre outras coisas, a manutenção de um número de registro físico exclusivo para cada 275
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
registro, a capacidade de “deletar virtualmente” os registros (deletar os registros sem removê-los da tabela), a capacidade de recuperar registros deletados virtualmente, e a capacidade de compactar uma tabela
para remover os registros deletados virtualmente. Nesta seção, você aprenderá sobre as funções do BDE
envolvidas na realização dessas ações, e criará um descendente de TTable chamado TBaseTable, que incorpora esses recursos.
Número do registro físico
As tabelas do dBASE mantêm um número de registro físico para cada registro em uma tabela. Esse número representa a posição física de um registro em relação ao início da tabela (independente de qualquer índice atualmente aplicado à tabela). Para obter um número de registro físico, você precisa chamar a função DbiGetRecord( ) do BDE, que é definida da seguinte maneira:
function DbiGetRecord(hCursor: hDBICur; eLock: DBILockType;
pRecBuff: Pointer; precProps: pRECProps): DBIResult stdcall;
tipo
hCursor é a alça do cursor. Normalmente, essa é a propriedade Handle do descendente de TDataSet.
eLock é um pedido opcional para o tipo de bloqueio a ser colocado no registro. Esse parâmetro é do
DBILockType, que é um tipo enumerado definido da seguinte maneira:
type
DBILockType = (
dbiNOLOCK;
dbiWRITELOCK;
dbiREADLOCK);
// Sem bloqueio (Default)
// Bloqueio de gravação
// Bloqueio de leitura
Nesse caso, você não deseja colocar um bloqueio no registro, pois não pretende modificar o conteúdo do registro; portanto, dbiNOLOCK é a escolha apropriada.
pRecBuff é um ponteiro para um buffer de registro. Como você deseja obter apenas as propriedades
de registro e não os dados, precisa passar Nil nesse parâmetro.
pRecProps é um ponteiro para um registro RECProps. Esse registro é definido da seguinte forma:
type
pRECProps = ^RECProps;
RECProps = packed record
iSeqNum
: Longint;
iPhyRecNum
: Longint;
iRecStatus
: Word;
bSeqNumChanged : WordBool;
bDeleteFlag
: WordBool;
end;
//
//
//
//
//
//
Propriedades do registro
Apenas quando núm. seq. é aceito
Apenas quando regs. fís. são aceitos
Status de reg. de atualizações adiadas
Não usado
Apenas quando aceita deletar virtualmente
Como você pode ver, é possível obter diversas informações por este registro. Nesse caso, você se
preocupa apenas com o campo iPhyRecNum, que é válido apenas no caso de tabelas do dBASE e do FoxPro.
Juntando tudo isso, o código a seguir mostra um método de TdBaseTable que retorna o número do registro físico referente ao registro atual:
276
function TdBaseTable.GetRecNum: Longint;
{ Retorna o número do registro físico referente ao registro atual. }
var
RP: RECProps;
begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
{ Apanha propriedades do registro atual }
Check(dbiGetRecord(Handle, dbiNOLOCK, Nil, @RP));
Result := RP.iPhyRecNum;
// retorna valor das propriedades
end;
Exibindo arquivos deletados
A exibição de registros que foram deletados virtualmente em uma tabela do dBASE é tão fácil quanto fazer uma chamada à API do BDE. A função para chamar é DbiSetProp( ), que é uma função muito poderosa
e permite modificar as diferentes propriedades de vários tipos de objetos do BDE. Para ver uma descrição completa dessa função e como ela funciona, o melhor é verificar o tópico “Properties – Getting and
Setting” (propriedades – apanhando e definindo) na ajuda do BDE. Essa função é definida da seguinte
forma:
function DbiSetProp(hObj; hDBIObj; iProp: Longint;
iPropValue: Longint): DBIResult stdcall;
O parâmetro hObj contém uma alça para algum tipo de objeto do BDE. Nesse caso, será uma alça de
cursor.
O parâmetro iProp conterá o identificador da propriedade a ser definida. Você encontrará uma lista
completa deles no tópico já mencionado da ajuda do BDE. Para fins de ativar ou desativar a visão dos registros deletados, use o identificador curSOFTDELETEON.
iPropValue é o novo valor para a propriedade indicada. Nesse caso, é um valor Booleano (0 significando desativado; 1 significando ativado).
O código a seguir mostra o método SetViewDeleted( ) de TdBaseTable:
procedure TdBaseTable.SetViewDeleted(Value: Boolean);
{ Permite que o usuário alterne entre exibir e não exibir os
registros deletados. }
begin
{ Tabela deve estar ativa }
if Active and (FViewDeleted < > Value) then begin
DisableControls;
// evita piscada
try
{ Chamada mágica ao BDE para alternar a visão de registros
deletados virtualmente. }
Check(dbiSetProp(hdbiObj(Handle), curSOFTDELETEON, Longint(Value)));
finally
Refresh;
// atualiza Delphi
EnableControls;
// processo para evitar piscada está completo
end;
FViewDeleted := Value
end;
end
Esse método primeiro realiza um teste para assegurar que a tabela está aberta e que o valor a ser definido é diferente do valor que o campo FViewDeleted do objeto já possui. Depois ele chama DisableControls( ) para evitar a piscada de quaisquer controles ligados a dados e conectados à tabela. A função DbiSetProp( ) é chamada em seguida (observe o typecast necessário do parâmetro Handle de hDBICur para um
hDBIObj). Pense em hDBIObj como uma alça não tipificada para algum tipo de objeto do BDE. Depois disso,
o dataset é renovado e quaisquer controles conectados são reativados.
277
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
DICA
Sempre que você usa DisableControls( ) para suspender a conexão de um dataset aos controles ligados a
dados, é preciso usar sempre um bloco try..finally para garantir que a chamada subseqüente a EnableControls( ) ocorrerá, haja um erro ou não.
Testando um registro deletado
Ao exibir um dataset que inclui registros excluídos, você provavelmente precisará determinar, ao navegar pelo dataset, quais registros estão deletados e quais não estão. Na verdade, você já aprendeu a realizar essa verificação. Você pode obter essas informações usando a função DbiGetRecord( ) que você usou
para obter o número do registro físico. O código a seguir mostra esse procedimento. A única diferença
material entre esse procedimento e GetRecNum( ) é a verificação do campo bDeletedFlag em vez do campo
iPhyRecNo do registro RECProps. Veja o código:
function TdBaseTable.GetIsDeleted: Boolean;
{ Retorna um Booleano indicando se o registro foi ou não
deletado virtualmente. }
var
RP: RECProps;
begin
if not FViewDeleted then
// não se importa se não estiverem vendo
Result := False
// registros deletados
else begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
{ Apanha propriedades do registro atual }
Check(dbiGetRecord(Handle, dbiNOLOCK, Nil, @RP));
Result := RP.bDeleteFlag; // retorna flag das propriedades
end;
end;
Recuperando um registro
Até aqui, você aprendeu a exibir registros deletados e também a determinar se um registro foi deletado e,
naturalmente, já sabe como deletar um registro. A única outra coisa que você precisa aprender com relação à exclusão de registros é como recuperar um registro. Felizmente, o BDE torna isso uma tarefa fácil,
graças à função DbiUndeleteRecord( ), que é definida da seguinte forma:
function DbiUndeleteRecord(hCursor: hDBICur): DBIResult stdcall;
O único parâmetro é uma alça do cursor cursor para o dataset atual. Usando essa função, você pode
criar um método UndeleteRecord( ) para TdBaseTable, como vemos aqui:
procedure TdBaseTable.UndeleteRecord;
begin
if not IsDeleted then
raise EDatabaseError.Create(‘Record is not deleted’);
Check(dbiUndeleteRecord(Handle));
Refresh;
end;
Compactando uma tabela
Para remover registros deletados virtualmente de uma tabela do dBASE, essa tabela precisa passar por
um processo chamado compactação. Para isso, o BDE oferece uma função chamada DbiPackTable( ), que
278 é definida da seguinte forma:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function DbiPackTable(hDb: hDBIDb; hCursor: hDBICur;
pszTableName: PChar; pszDriverType: PChar;
bRegenIdxs: Bool): DBIResult stdcall;
hDb é uma alça para um banco de dados. Você deverá passar a propriedade DBHandle de um descendente de TDataSet ou a propriedade Handle de um componente TDatabase nesse parâmetro.
hCursor é uma alça de cursor. Passe a propriedade Handle de um descendente de TDataSet nesse parâmetro. Você também pode passar Nil se quiser, em vez disso, usar os parâmetros pszTableName e pszDriverType para identificar a tabela.
pszTableName é um ponteiro para uma string contendo o nome da tabela.
pszDriverType é um ponteiro para uma string representando o tipo de driver da tabela. Se hCursor for
Nil, este parâmetro deverá ser definido como szDBASE. Vale a pena lembrar que é raro exigir esse parâmetro, pois a função é aceita apenas para tabelas do dBASE – não fazemos as regras, apenas as seguimos.
bRegenIdxs indica se você deseja ou não recriar todos os índices desatualizados associados à tabela.
Veja o método Pack( ) para a classe TdBaseTable:
procedure TdBaseTable.Pack(RegenIndexes: Boolean);
{ Compacta a tabela a fim de remover do arquivo os registros
excluídos virtualmente. }
const
SPackError = ‘Table must be active and opened exclusively’;
begin
{ Tablea deve estar ativa e aberta exclusivamente. }
if not (Active and Exclusive) then
raise EDatabaseError.Create(SPackError);
try
{ Compacta a tabela }
Check(dbiPackTable(DBHandle, Handle, Nil, Nil, RegenIndexes));
finally
{ atualiza Delphi pelo BDE }
CursorPosChanged;
Refresh;
end;
end;
A listagem completa do objeto TdBaseTable aparece na Listagem 30.1, mais adiante neste capítulo.
Tabelas do Paradox
As tabelas do Paradox não possuem tantos recursos interessantes, como exclusão virtual, mas elas possuem o conceito de um número de registro e pacote de tabela. Nesta seção, você aprenderá a estender
uma TTable para realizar essas tarefas específicas do Paradox e criar uma nova classe TParadoxTable.
Número de seqüência
As tabelas do Paradox não possuem o conceito de um número de registro físico no sentido do dBASE. No
entanto, elas mantêm o conceito de um número de seqüência para cada registro em uma tabela. O número de seqüência difere do número do registro físico porque o número de seqüência depende do índice
sendo aplicado atualmente à tabela. O número de seqüência de um registro é a ordem em que o registro
aparece com base no índice atual.
O BDE facilita bastante a obtenção de um número de seqüência usando a função DbiGetSeqNo( ), que
é definida desta maneira:
279
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function DbiGetSeqNo(hCursor: hDBICur; var iSeqNo: Longint( ): DBIResult;
stdcall;
hCursor é uma alça de cursor para uma tabela do Paradox, e o parâmetro iSeqNo será preenchdo com
o número de seqüência do registro atual. O código a seguir mostra a função GetRecNum( ) para a TParadoxTable:
function TParadoxTable.GetRecNum: Longint;
{ Retorna o número de seqüência do registro atual. }
begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
{ Apanha o número de seqüência do registro atual em Result. }
Check(dbiGetSeqNo(Handle, Result));
end;
Compactação de tabela
A compactação de tabela possui significados diferentes no Paradox e no dBASE, pois o Paradox não aceita a exclusão virtual de registros. Quando um registro é deletado no Paradox, ele é removido da tabela,
mas aparece um “furo” no local em que o registro se encontrava no arquivo do banco de dados. Para
compactar esses furos deixados pelos registros deletados e tornar a tabela menor e mais eficiente, você
precisa compactar a tabela.
Ao contrário das tabelas do dBASE, não existe uma função óbvia do BDE que você possa utilizar
para compactar uma tabela do Paradox. Em vez disso, você precisa usar DbiDoRestructure( ) para reestruturar a tabela e especificar que a tabela deverá ser compactada enquanto for reestruturada. DbiDoRestructure( ) é definida da seguinte forma:
function DbiDoRestructure(hDb: hDBIDb, iTblDescCount: Word;
pTbiDesc: pCRTblDesc; pszSaveAs, pszKeyviolName,
pszProblemsName: PChar; bAnalyzeOnly: Bool): DBIResult stdcall;
hDb é a alça para um banco de dados. No entanto, como essa função não funcionará quando o Delphi abrir a tabela, você não poderá usar a propriedade DBHandle de um TDataSet. Para contornar isso, o có-
digo de exemplo (mostrado mais adiante) que usa essa função demonstra como criar um banco de dados
temporário.
iTblDescCount é o número de descritores da tabela. Esse parâmetro precisa ser 1, pois a versão atual
do BDE aceita apenas um descritor de tabela por chamada.
pTblDesc é um ponteiro para um registro CRTblDesc. Esse é o registro que identifica a tabela e especifica como a tabela deve ser reestruturada. Esse registro é definido da seguinte forma:
280
type
pCRTDblDesc = ^CRTblDesc;
CRTblDesc = packed record
szTblName
: DBITBLNAME;
szTblType
: DBINAME;
szErrTblName : DBIPATH;
szUserName
: DBINAME;
szPassword
: DBINAME;
bProtected
: WordBool;
bPack
: WordBool;
iFldCount
: Word;
pecrFldOp
: pCROpType;
pfldDesc
: pFLDDesc;
//
//
//
//
//
//
//
//
//
//
//
Cria/reestrutura descr tabela
TableName inclui caminho e ext opcionais
Tipo de driver (opcional)
Nome da tabela de erro (opcional)
Nome do usuário (se for o caso)
Senha (opcional)
Senha mestra fornecida em szPassword
Compacta tabela (apenas reestrutura)
Número de defs de campo fornecidas
Array de opções de campo
Array de descritores de campo
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
iIdxCount
:
pecrIdxOp
:
pidxDesc
:
iSecRecCount :
pecrSecOp
:
psecDesc
:
iValChkCount :
pecrValChkOp :
pvchkDesc
:
iRintCount
:
pecrRintOp
:
printDesc
:
iOptParams
:
pfldOptParams:
pOptData
:
end;
Word;
pCROpType;
PIDXDesc;
Word;
pCROpType;
pSECDesc;
Word;
pCROpType;
pVCHKDesc;
Word;
pCROpType;
pRINTDesc;
Word;
pfLDDesc;
Pointer;
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
Número de defs de índice fornecidas
Array de ops de índice
Array de descritores de índice
Número de defs de segurança fornecidas
Array de ops de segurança
Array de descritores de segurança
Número de verificações de valor
Array de ops de verificação de valor
Array de descrições de verif. de valor
Número de espec. int. de ref.
Array de ops. int. de ref.
Array de espec. int. de ref.
Número de parâmetros opcionais
Array de descritores de campo
Parâmetros opcionais
Para a compactação de tabelas do Paradox, só é preciso especificar valores para os campos szTblName,
szTblType e bPack.
pszSaveAs é um ponteiro de string opcional que identifica a tabela de destino se for diferente da tabela de origem.
pszKeyviolName é um ponteiro de string opcional que identifica a tabela à qual serão enviados os registros que causam violações-chave durante a reestrutura.
pszProblemsName é um ponteiro de string opcional que identifica a tabela à qual serão enviados os registros que causam problemas durante a reestrutura.
bAnalyzeOnly não é usado.
O código a seguir mostra o método Pack( ) de TParadoxTable. Você pode notar, pelo código, como o
registro CRTblDesc é inicializado e como o banco de dados temporário é criado usando a função DbiOpenDatabase( ). Observe também o bloco finally, que garante que o banco de dados temporário será zerado
após o uso.
procedure TParadoxTable.Pack;
var
TblDesc: CRTblDesc;
TempDBHandle: HDBIDb;
WasActive: Boolean;
begin
{ Inicializa registro TblDesc }
FillChar(TblDesc, SizeOf(TblDesc), 0); // fill with 0s
with TblDesc do begin
StrPCopy(szTblName, TableName);
// define nome da tabela
szTblType := szPARADOX;
// define tipo da tabela
bPack := True;
// define flag de compactação
end;
{ Armazena estado ativo da tabela. Deve fechar tabela para compactar. }
WasActive := Active;
if WasActive then Close;
try
{ Cria um banco de dados temporário. Deve ser exclusivo e read-write. }
Check(dbiOpenDatabase(PChar(DatabaseName), Nil, dbiREADWRITE,
dbiOpenExcl, Nil, 0, Nil, Nil, TempDBHandle));
try
{ Compacta a tabela }
Check(dbiDoRestructure(TempDBHandle, 1, @TblDesc, Nil, Nil, Nil,
False));
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
281
finally
{ Fecha o banco de dados temporário }
dbiCloseDatabase(TempDBHandle);
end;
finally
{ Resseta estado ativo da tabela }
Active := WasActive;
end;
end;
A Listagem 30.1 mostra a unidade DDGTbls, onde são definidos os objetos TdBaseTable e TParadoxTable.
Listagem 30.1 A Unidade DDGtbs.pas
unit DDGTbls;
interface
uses DB, DBTables, BDE;
type
TdBaseTable = class(TTable)
private
FViewDeleted: Boolean;
function GetIsDeleted: Boolean;
function GetRecNum: Longint;
procedure SetViewDeleted(Value: Boolean);
protected
function CreateHandle: HDBICur; override;
public
procedure Pack(RegenIndexes: Boolean);
procedure UndeleteRecord;
property IsDeleted: Boolean read GetIsDeleted;
property RecNum: Longint read GetRecNum;
property ViewDeleted: Boolean read FViewDeleted write SetViewDeleted;
end;
TParadoxTable = class(TTable)
private
protected
function CreateHandle: HDBICur; override;
function GetRecNum: Longint;
public
procedure Pack;
property RecNum: Longint read GetRecNum;
end;
implementation
uses SysUtils;
{ TdBaseTable }
282
function TdBaseTable.GetIsDeleted: Boolean;
{ Retorna um Booleano indicando se o registro foi ou não
deletado virtualmente. }
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.1 Continuação
var
RP: RECProps;
begin
if not FViewDeleted then
// não se importa se não estiverem vendo
Result := False
// registros deletados
else begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
{ Apanha propriedades do registro atual }
Check(dbiGetRecord(Handle, dbiNOLOCK, Nil, @RP));
Result := RP.bDeleteFlag; // retorna flag das propriedades
end;
end;
function TdBaseTable.GetRecNum: Longint;
{ Retorna o número do registro físico referente ao registro atual. }
var
RP: RECProps;
begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
{ Apanha propriedades do registro atual }
Check(dbiGetRecord(Handle, dbiNOLOCK, Nil, @RP));
Result := RP.iPhyRecNum;
// retorna valor das propriedades
end;
function TdBaseTable.CreateHandle: HDBICur;
{ Redefinido do ancestral a fim de realizar uma verificação e garantir
que esta é uma tabela do dBASE. }
var
CP: CURProps;
begin
Result := inherited CreateHandle;
if Result < > Nil then begin
{ Apanha propriedades do cursor e gera exceção se a tabela não
estiver usando o driver do dBASE. }
Check(dbiGetCursorProps(Result, CP));
if not (CP.szTableType = szdBASE) then
raise EDatabaseError.Create(‘Not a dBASE table’);
end;
end;
procedure TdBaseTable.Pack(RegenIndexes: Boolean);
{ Compacta a tabela a fim de remover do arquivo os registros
excluídos virtualmente. }
const
SPackError = ‘Table must be active and opened exclusively’;
begin
{ Tablea deve estar ativa e aberta exclusivamente. }
if not (Active and Exclusive) then
raise EDatabaseError.Create(SPackError);
try
{ Compacta a tabela }
Check(dbiPackTable(DBHandle, Handle, Nil, Nil, RegenIndexes));
finally
{ atualiza Delphi pelo BDE }
CursorPosChanged;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
283
Listagem 30.1 Continuação
Refresh;
end;
end;
procedure TdBaseTable.SetViewDeleted(Value: Boolean);
{ Permite que o usuário alterne entre exibir e não exibir os
registros deletados. }
begin
{ Tabela deve estar ativa }
if Active and (FViewDeleted < > Value) then begin
DisableControls;
// evita piscada
try
{ Chamada mágica ao BDE para alternar a visão de registros
deletados virtualmente. }
Check(dbiSetProp(hdbiObj(Handle), curSOFTDELETEON, Longint(Value)));
finally
Refresh;
// atualiza Delphi
EnableControls;
// processo para evitar piscada está completo
end;
FViewDeleted := Value
end;
end;
procedure TdBaseTable.UndeleteRecord;
begin
if not IsDeleted then
raise EDatabaseError.Create(‘Record is not deleted’);
Check(dbiUndeleteRecord(Handle));
Refresh;
end;
function TParadoxTable.CreateHandle: HDBICur;
{ Redefinido do ancestral a fim de realizar uma verificação e garantir
que esta é uma tabela do Paradox. }
var
CP: CURProps;
begin
Result := inherited CreateHandle;
if Result < > Nil then begin
{ Apanha propriedades do cursor e gera exceção se a tabela
não estiver usando o driver do Paradox. }
Check(dbiGetCursorProps(Result, CP));
if not (CP.szTableType = szPARADOX) then
raise EDatabaseError.Create(‘Not a Paradox table’);
end;
end;
function TParadoxTable.GetRecNum: Longint;
{ Retorna o número de seqüência do registro atual. }
begin
UpdateCursorPos;
// atualiza BDE pelo Delphi
{ Apanha número de seqüência do registro atual em Result. }
Check(dbiGetSeqNo(Handle, Result));
end;
284
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.1 Continuação
procedure TParadoxTable.Pack;
var
TblDesc: CRTblDesc;
TempDBHandle: HDBIDb;
WasActive: Boolean;
begin
{ Inicializa registro TblDesc }
FillChar(TblDesc, SizeOf(TblDesc), 0); // fill with 0s
with TblDesc do begin
StrPCopy(szTblName, TableName);
// define nome da tabela
szTblType := szPARADOX;
// define tipo da tabela
bPack := True;
// define flag de compactação
end;
{ Armazena estado ativo da tabela. Deve fechar tabela para compactar. }
WasActive := Active;
if WasActive then Close;
try
{ Cria um banco de dados temporário. Deve ser exclusivo e read-write. }
Check(dbiOpenDatabase(PChar(DatabaseName), Nil, dbiREADWRITE,
dbiOpenExcl, Nil, 0, Nil, Nil, TempDBHandle));
try
{ Compacta a tabela }
Check(dbiDoRestructure(TempDBHandle, 1, @TblDesc, Nil, Nil, Nil,
False));
finally
{ Fecha o banco de dados temporário }
dbiCloseDatabase(TempDBHandle);
end;
finally
{ Resseta estado ativo da tabela }
Active := WasActive;
end;
end;
end.
Limitando os conjuntos de resultados de TQuery
Aqui está uma falha clássica na programação com SQL: sua aplicação emite uma instrução SQL para o
servidor, que retorna um conjunto de resultados contendo zilhões de linhas, fazendo com que o usuário
espere toda a vida até que a consulta retorne e consumindo a preciosa largura de banda do servidor e da
rede. A sabedoria convencional da SQL afirma que não se deve emitir consultas genéricas, que façam
com que muitos registros sejam apanhados. No entanto, isso às vezes é inevitável, e TQuery parece não ajudar muito, pois não oferece um meio de restringir o número de registros em um conjunto de resultados
apanhado do servidor. Felizmente, o BDE oferece essa capacidade, e não é muito difícil utilizá-la em um
descendente de TQuery.
A chamada à API do BDE que realiza essa mágica é a função abrangente DbiSetProp( ), que foi explicada anteriormente neste capítulo. Nesse caso, o primeiro parâmetro de DbiSetProp( ) é a alça do cursor
para a consulta, o segundo parâmetro deverá ser curMAXROWS e o parâmetro final deverá ser definido para o
número máximo de linhas ao qual você deseja restringir o conjunto de resultados.
O local ideal para fazer a chamada a essa função é no método PrepareCursor( ) de TQuery, que é chamado imediatamente após a consulta ser aberta. A Listagem 30.2 mostra a unidade ResQuery, na qual o
componente TRestrictedQuery está definido.
285
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.2 A unidade ResQuery.pas
unit ResQuery;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DB, DBTables, BDE;
type
TRestrictedQuery = class(TQuery)
private
FMaxRowCount: Longint;
protected
procedure PrepareCursor; override;
published
property MaxRowCount: Longint read FMaxRowCount write FMaxRowCount;
end;
procedure Register;
implementation
procedure TRestrictedQuery.PrepareCursor;
begin
inherited PrepareCursor;
if FMaxRowCount > 0 then
Check(DbiSetProp(hDBIObj(Handle), curMAXROWS, FMaxRowCount));
end;
procedure Register;
begin
RegisterComponents(‘DDG’, [TRestrictedQuery]);
end;
end.
Você pode limitar o conjunto de resultados de uma consulta simplesmente definindo a propriedade
MaxRowCount para um valor maior do que zero. Para ilustrar ainda mais esse ponto, a Figura 30.1 mostra o
resultado de uma consulta restrita a três linhas, como mostra o SQL Monitor.
286 F I G U R A 3 0 . 1 Uma consulta restrita vista pelo SQL Monitor.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Aspectos diversos do BDE
Através do nosso desenvolvimento de aplicações de banco de dados, descobrimos algumas tarefas comuns de desenvolvimento que poderiam ser um pouco automatizadas. Algumas dessas tarefas variadas
incluem a realização de funções de agregação SQL em uma tabela, cópia de tabelas e obtenção de uma lista de usuários do Paradox para uma determinada sessão.
Funções de agregação SQL
De um modo geral, as funções de agregação SQL são funções embutidas na linguagem SQL para realizar
alguma operação aritmética sobre uma ou mais colunas de uma ou mais linhas. Alguns exemplos comuns
diso são sum( ), que soma as colunas de várias linhas, avg( ), que calcula o valor médio das colunas de várias linhas, min( ), que encontra o valor mínimo das colunas de várias linhas, e max( ), que (como você poderia imaginar) determina o valor máximo das colunas dentro de várias linhas.
Funções de agregação como estas podem às vezes ser inconvenientes de se usar no Delphi. Por
exemplo, se você estiver trabalhando com TTables para acessar dados, o uso dessas funções envolve a criação de uma TQuery, a formulação da instrução SQL correta para a tabela e coluna em questão, a execução
da consulta e a obtenção do resultado da consulta. Certamente, esse é um processo que está pedindo para
ser automatizado, e o código na Listagem 30.3 faz exatamente isso.
Listagem 30.3 Automatizando funções de agregação SQL
type
TSQLAggFunc = (safSum, safAvg, safMin, safMax);
const
// Funções de agregação SQL
SQLAggStrs: array[TSQLAggFunc] of string = (
‘select sum(%s) from %s’,
‘select avg(%s) from %s’,
‘select min(%s) from %s’,
‘select max(%s) from %s’);
function CreateQueryFromTable(T: TTable): TQuery;
// retorna consulta ligada ao mesmo banco de dados e sessão da tabela dada
begin
Result := TQuery.Create(nil);
try
Result.DatabaseName := T.DatabaseName;
Result.SessionName := T.SessionName;
except
Result.Free;
Raise;
end;
end;
function DoSQLAggFunc(T: TTable; FieldNames: string;
Func: TSQLAggFunc): Extended;
begin
with CreateQueryFromTable(T) do
begin
try
SQL.Add(Format(SQLAggStrs[Func], [FieldNames, T.TableName]));
Open;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
287
Listagem 30.3 Continuação
Result := Fields[0].AsFloat;
finally
Free;
end;
end;
end;
function SumField(T: TTable; Field: String): Extended;
begin
Result := DoSQLAggFunc(T, Field, safSum);
end;
function AvgField(T: TTable; Field: String): Extended;
begin
Result := DoSQLAggFunc(T, Field, safAvg);
end;
function MinField(T: TTable; Field: String): Extended;
begin
Result := DoSQLAggFunc(T, Field, safMin);
end;
function MaxField(T: TTable; Field: string): Extended;
begin
Result := DoSQLAggFunc(T, Field, safMax);
end;
Como você pode ver pela listagem, cada um dos wrappers de função de agregação individuais chama a função DoSQLAggFun( ). Nessa função, a função CreateQueryFromTable( ) cria e retorna um componente
TQuery que utiliza o mesmo banco de dados e sessão da TTable passada no parâmetro T. A string SQL apropriada é então formatada a partir de um array de strings, a consulta é executada e o resultado da consulta
é retornado da função.
Cópia rápida de tabela
Se você quiser fazer uma cópia de uma tabela, a sabedoria tradicional poderia indicar alguns caminhos de
ação diferentes. O primeiro poderia ser usar a função CopyFile( ) da API do Win32 para copiar fisicamente o(s) arquivo(s) da tabela de um local para outro. Outra opção é usar o componente TBatchMove para copiar uma TTable para outra. Outra opção é usar o método BatchMove( ) de TTable para realizar a cópia.
No entanto, existem problemas em cada uma dessas alternativas tradicionais. Uma cópia de arquivo
pela força bruta, usando a função da API CopyFile( ), pode não funcionar se os arquivos da tabela estiverem abertos por outro processo ou usuário, e certamente não funcionará se a tabela existir dentro de algum tipo de arquivo de banco de dados em um servidor SQL. Uma cópia de arquivo pode se tornar uma
tarefa muito complexa se você considerar que também pode ter de copiar o índice, o BLOB ou os arquivos de valores associados. O uso de TBatchMove resolveria esses problemas, mas somente se você estiver sujeito à desvantagem da complexidade envolvida no uso desse componente. Outra desvantagem é o fato
de que o processo de mudança em batch é muito mais lento do que uma cópia direta do arquivo. O uso de
TTable.BatchMove( ) ajuda a aliviar o problema da complexidade na cópia da tabela, mas não contornaria
os problemas de desempenho inerentes ao processo de movimentação em batch.
Felizmente, os programadores do BDE também reconheceram isso e colocaram uma função de API
do BDE à sua disposição para fornecer o melhor dos dois lados: velocidade e facilidade de uso. A Função
288 em questão é DbiCopyTable( ), que é declarada como vemos a seguir:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function DbiCopyTable (
hDb
: hDBIDb;
bOverWrite : Bool;
pszSrcTableName : PChar;
pszSrcDriverType : PChar;
pszDestTableName : PChar;
): DBIResult stdcall;
{
{
{
{
{
{
Copia uma tabela para outra }
Alça do banco de dados }
True para substituir arquivo existente }
Nome da tabela de origem }
Tipo de driver de origem }
Nome da tabela de destino }
Como a função de API do BDE não pode lidar diretamente com os componentes TTable da VCL, o
procedimento a seguir envolve DbiCopyTable( ) em uma rotina para a qual você pode passar uma TTable e o
nome de uma tabela de destino:
procedure QuickCopyTable(T: TTable; DestTblName: string; Overwrite: Boolean);
// Copia TTable T para uma tabela idêntica com o nome DestTblName. Gravará
// sobre tabela existente com o nome DestTblName se Overwrite for True.
var
DBType: DBINAME;
WasOpen: Boolean;
NumCopied: Word;
begin
WasOpen := T.Active;
// salva estado ativo da tabela
if not WasOpen then T.Open; // garante que a tabela esteja aberta
// Apanha string de tipo de driver
Check(DbiGetProp(hDBIObj(T.Handle), drvDRIVERTYPE, @DBType,
SizeOf(DBINAME), NumCopied));
// Copia a tabela
Check(DbiCopyTable(T.DBHandle, Overwrite, PChar(T.TableName), DBType,
PChar(DestTblName)));
T.Active := WasOpen;
// Restaura estado ativo
end;
NOTA
Para bancos de dados locais (Paradox, dBASE, Access e FoxPro), todos os arquivos associados à tabela –
arquivos de índice e BLOB, por exemplo – são copiados na tabela de destino. Para tabelas residindo em um
banco de dados SQL, somente a tabela será copiada, e fica por sua conta garantir que os índices e outros
elementos necessários serão aplicados à tabela de destino.
Usuários da sessão do Paradox
Se a sua aplicação utiliza tabelas do Paradox, poderá surgir uma situação em que você precisa determinar
quais usuários estão atualmente usando uma determinada tabela do Paradox. Você pode fazer isso com a
função de API DbiOpenUserList( ) do BDE. Essa função oferece um cursor do BDE para uma lista de usuário da sessão atual. O procedimento a seguir demonstra como usar essa função de modo eficaz:
procedure GetPDoxUsersForSession(Sess: TSession; UserList: TStrings);
// Apagar UserList e inclui cada usuário usando o mesmo arquivo de rede da
// sessão Sess para a lista. Se Sess = nil, então o procedimento funciona
// para o arquivo de rede default.
var
WasActive: Boolean;
SessHand: hDBISes;
ListCur: hDBICur;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
289
User: UserDesc;
begin
if UserList = nil then Exit;
WasActive := False;
UserList.Clear;
if Assigned(Sess) then
begin
WasActive := Sess.Active;
if not WasActive then Sess.Open;
Check(DbiStartSession(Nil, SessHand, PChar(Sess.NetFileDir)));
end
else
Check(DbiStartSession(Nil, SessHand, Nil));
try
Check(DbiOpenUserList(ListCur));
try
while DbiGetNextRecord(ListCur, dbiNOLOCK, @User, Nil) =
DBIERR_NONE do
UserList.Add(User.szUserName);
finally
DbiCloseCursor(ListCur); // fecha cursor “user list table”
end;
finally
DbiCloseSession(SessHand);
if Assigned(Sess) then Sess.Active := WasActive;
end;
end;
O mais interessante sobre a função DbiOpenUserList( ) é que ela cria um cursor para uma tabela, o
qual é manipulado da mesma forma que qualquer outro cursor de tabela do BDE. Nesse caso, DbiGetNextRecord( ) é chamado repetidamente até que o final da tabela seja alcançado. O buffer de registro para essa
tabela segue o formato do registro UserDesc, que é definido na unidade BDE da seguinte forma:
type
pUSERDesc = ^USERDesc;
USERDesc = packed record
{ Descrição do usuário }
szUserName
: DBIUSERNAME;
{ Nome do usuário }
iNetSession : Word;
{ Número de sessão em nível de rede }
iProductClass: Word;
{ Classe de produto do usuário }
szSerialNum : packed array [0..21]; { Número de série }
end;
DICA
Observe o uso dos blocos de proteção de recurso try..finally no procedimento GetPDoxUsersForSession( ). Isso garante que os recursos do BDE associados à sessão e o cursor são liberados corretamente.
Escrita de controles da VCL ligados aos dados
290
Os Capítulos 21 e 22 deram uma visão completa das técnicas e metodologias de criação de componentes.
No entanto, um tópico importante que não foi abordado refere-se aos controles ligados aos dados. Na realidade, não há muito mais na criação de um controle ligado aos dados do que na criação de um controle
normal da VCL, mas um componente típico dessa espécie é diferente em quatro áreas básicas:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
l
l
l
Os controles ligados aos dados mantêm um objeto de link de dados interno. Descendente de TDataLink, esse objeto oferece um meio pelo qual o controle se comunica com um TDataSource. Para
os controles ligados aos dados que se conectam a um único campo de um dataset, isso normalmente é um TFieldDataLink. O controle deve lidar com o evento onDataChange do vínculo de dados a
fim de receber notificações quando os dados do campo ou do registro forem alterados.
Controle ligados aos dados precisam lidar com a mensagem CM_GETDATALINK. A resposta típica para
essa mensagem é retornar o objeto do vínculo de dados no campo Result da mensagem.
Controles ligados aos dados devem exibir uma propriedade do tipo TDataSource para que o controle possa ser conectado a uma origem de dados pela qual ele se comunicará com um dataset.
Por convenção, essa propriedade se chama DataSource. Os controles que se conectam a um único
campo também devem exibir uma propriedade de string para conter o nome do campo ao qual
está conectado. Por convenção, essa propriedade é chamada DataField.
Os controles ligados aos dados precisam redefinir o método Notification( ) de TComponent. Redefinindo esse método, o controle pode ser notificado se o componente da origem de dados conectada ao controle foi deletado do formulário.
Para demonstrar a criação de um controle simples ligado aos dados, a Listagem 30.4 mostra a unidade DBSound. Essa unidade contém o componente TDBWavPlayer, um componente que toca sons WAV a
partir de um campo BLOB de um dataset.
Listagem 30.4 A unidade DBSound.pas
unit DBSound;
interface
uses Windows, Messages, Classes, SysUtils, Controls, Buttons, DB,
DBTables, DbCtrls;
type
EDBWavError = class(Exception);
TDBWavPlayer = class(TSpeedButton)
private
FAutoPlay: Boolean;
FDataLink: TFieldDataLink;
FDataStream: TMemoryStream;
FExceptOnError: Boolean;
procedure DataChange(Sender: TObject);
function GetDataField: string;
function GetDataSource: TDataSource;
function GetField: TField;
procedure SetDataField(const Value: string);
procedure SetDataSource(Value: TDataSource);
procedure CMGetDataLink(var Message: TMessage); message
CM_GETDATALINK;
procedure CreateDataStream;
procedure PlaySound;
protected
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
291
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.4 Continuação
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Click; override;
property Field: TField read GetField;
published
property AutoPlay: Boolean read FAutoPlay write FAutoPlay
default False;
property ExceptOnError: Boolean read FExceptOnError
write FExceptOnError;
property DataField: string read GetDataField write SetDataField;
property DataSource: TDataSource read GetDataSource
write SetDataSource;
end;
implementation
uses MMSystem;
const
// Strings de erro
SNotBlobField = ‘Field “%s” is not a blob field’;
SPlaySoundErr = ‘Error attempting to play sound’;
constructor TDBWavPlayer.Create(AOwner:
begin
inherited Create(AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.OnDataChange := DataChange;
FDataStream := TMemoryStream.Create;
end;
TComponent);
//
//
//
//
chama inherited
cria vínculo de dados do campo
apanha notif. do vínculo de dados
cria stream de memória de trabalho
destructor TDBWavPlayer.Destroy;
begin
FDataStream.Free;
FDataLink.Free;
FDataLink := Nil;
inherited Destroy;
end;
procedure TDBWavPlayer.Click;
begin
inherited Click; // faz comportamento default
PlaySound;
// toca o som
end;
292
procedure TDBWavPlayer.CreateDataStream;
// cria stream da memória a partir do arquivo wave no campo blob
var
BS: TBlobStream;
begin
// verifica se é um campo blob
if not (Field is TBlobField) then
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.4 Continuação
raise EDBWavError.CreateFmt(SNotBlobField, [DataField]);
// cria um stream blob
BS := TBlobStream.Create(TBlobField(Field), bmRead);
try
// copia do stream blob para o strem da memória
FDataStream.SetSize(BS.Size);
FDataStream.CopyFrom(BS, BS.Size);
finally
BS.Free; // libera o stream blob
end;
end;
procedure TDBWavPlayer.PlaySound;
// toca o som wave carregado no stream da memória
begin
// certifica-se de que estamos ligados a um dataset e campo
if (DataSource < > nil) and (DataField < > ‘’) then
begin
// verifica se o stream de dados foi criado
if FDataStream.Size = 0 then CreateDataStream;
// Toca o som no stream da memória, gera exceção se houver erro
if (not MMSystem.PlaySound(FDataStream.Memory, 0, SND_ASYNC or
SND_MEMORY)) and FExceptOnError then
raise EDBWavError.Create(SPlaySoundErr);
end;
end;
procedure TDBWavPlayer.DataChange(Sender: TObject);
// Manipulador OnChange, FFieldDataLink.DataChange
begin
// desaloca memória ocupada pelo arquivo wave anterior
with FDataStream do if Size < > 0 then SetSize(0);
// se AutoPlay estiver ligado, então toca o som
if FAutoPlay then PlaySound;
end;
procedure TDBWavPlayer.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
// faz alguma manutenção necessária
if (Operation = opRemove) and (FDataLink < > nil) and
(AComponent = DataSource) then DataSource := nil;
end;
function TDBWavPlayer.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBWavPlayer.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
293
Listagem 30.4 Continuação
if Value < > nil then Value.FreeNotification(Self);
end;
function TDBWavPlayer.GetDataField: string;
begin
Result := FDataLink.FieldName;
end;
procedure TDBWavPlayer.SetDataField(const Value: string);
begin
FDataLink.FieldName := Value;
end;
function TDBWavPlayer.GetField: TField;
begin
Result := FDataLink.Field;
end;
procedure TDBWavPlayer.CMGetDataLink(var Message: TMessage);
begin
Message.Result := Integer(FDataLink);
end;
end.
Este componente é um descendente de TSpeedButton que, quando acionado, pode tocar um som
WAV residente em um campo BLOB de banco de dados. A propriedade AutoPlay também pode ser definida como True, o que fará com que o som toque toda vez que o usuário navegar para um novo registro da
tabela. Quando essa propriedade é definida, também pode fazer sentido definir a propriedade Visible do
componente como False para que um botão não apareça visualmente no formulário.
No manipulador de FDataLink.OnChange, DataChange( ), o componente funciona extraindo o campo
BLOB por meio de um TBlobStream e copiando o stream BLOB para um stream na memória, FDataStream.
Quando o som está no stream da memória, você pode tocá-lo usando a função PlaySound( ) da API do
Win32.
Extensão de TDataSet
Um dos recursos marcantes da VCL de banco de dados é o TDataSet abstrato, que oferece a capacidade de
manipular origens de dados não-BDE dentro da estrutura de banco de dados da VCL.
Nos velhos tempos...
Na versão anterior do Delphi, a arquitetura de banco de dados da VCL era fechada, tornando muito difícil a manipulação de origens de dados não-BDE por meio de componentes da VCL. A Figura 30.2 ilustra
a arquitetura do dataset centralizado no BDE, presente no Delphi 1 e 2.
Como vemos na Figura 30.2, TDataSet é essencialmente codificado para o BDE, e não há espaço nessa arquitetura para origens de dados não-BDE. Os programadores que desejavam usar origens de dados
não-BDE dentro da VCL tinham duas escolhas:
l
Criar uma DLL que apareça para a VCL como a BDE mas que fale com um tipo de dados diferente.
294
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
Jogar TDataSet pela janela e escrever sua própria classe de dataset e controles ligados aos dados.
Logicamente, ambas as opções envolvem um enorme trabalho, e nenhuma delas é uma solução particularmente elegante. Algo tinha de ser feito.
FIGURA 30.2
Arquitetura de dataset da VCL no Delphi 1 e 2.
Tempos modernos
Reconhecendo esses problemas e a intensa demanda do cliente por acesso mais fácil a origens de dados
não-BDE, a equipe de desenvolvimento do Delphi tornou prioritária a extensão da arquitetura do conjunto de dados da VCL no Delphi 3. A idéia por trás da nova arquitetura era tornar a classe TDataSet uma
abstração de um dataset da VCL e mover o código do conjunto de dados específico do BDE para a nova
classe TBDEDataSet. A Figura 30.3 contém uma ilustração dessa nova arquitetura.
Quando você entender como TDataSet foi desacoplado do BDE, o desafio se tornará como empregar esse conceito para criar um descendente de TDataSet que manipule algum tipo de dado não-BDE. E
não estamos usando o termo desafio de forma exagerada; a criação de um descendente funcional de
TDataSet não é uma tarefa para os medrosos. Essa é uma tarefa bastante exigente, que requer não apenas
conhecimentos sobre criação de componentes, mas também familiaridade com a arquitetura de banco
de dados da VCL.
DICA
O Delphi contém dois exemplos de criação de um descendente de TDataSet – um muito simples e outro
muito complicado. O exemplo simples é a classe TTextDataSet, encontrada na unidade TextData, no diretório \Delphi5\Demos\Db\TextData. Esse exemplo encapsula TStringList como um dataset de um campo.
O exemplo complexo é a classe TBDEDataSet, encontrada na unidade DbTables, no código-fonte da VCL.
Como já dissemos, essa classe mapeia a arquitetura de dataset da VCL para o BDE.
295
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
FIGURA 30.3
Arquitetura de dataset da VCL no Delphi 3 em diante.
Criando um descendente de TDataSet
A maioria das implementações de dataset estará entre TTextDataSet e TBDEDataSet em termos de complexidade. Para oferecer um exemplo disso, demonstraremos como criar um descendente de TDataSet que manipule um file of record do Object Pascal (para ver uma descrição de file of record, consulte o Capítulo
12). Os tipos de registro e de arquivo a seguir serão usados para este exemplo:
type
// array de tamanho arbitrário de char usado para campo de nome
TNameStr = array[0..31] of char;
// esta informação de registro representa a estrutura “table”:
PDDGData = ^TDDGData;
TDDGData = record
Name: TNameStr;
Height: Double;
ShoeSize: Integer;
end;
// file of record do Pascal, que contém dados de “table”:
TDDGDataFile = file of TDDGData;
Um file of record do Object Pascal pode oferecer um modo conveniente e eficaz de armazenar informações, mas o formato é inerentemente limitado por sua incapacidade de inserir registros ou deletar
registros do meio do arquivo. Por esse motivo, usaremos um esquema de dois arquivos para rastrear as
informações de “table”: o primeiro, o arquivo de dados, sendo o file of record; o segundo, o arquivo de
índice, mantendo uma lista de inteiros que representa os valores de busca no primeiro arquivo. Isso signi296 fica que a posição de um registro no arquivo de dados não necessariamente coincide com sua posição no
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
dataset. A posição de um registro no dataset é controlada pela ordem do arquivo de índice; o primeiro inteiro no arquivo de índice contém o valor de busca do primeiro registro no arquivo de dados, o segundo
inteiro no arquivo de índice contém o próximo valor de busca no arquivo de dados, e assim por diante.
Nesta seção, vamos discutir o que é necessário para criar um descendente de TDataSet chamado
TDDGDataSet, que se comunica com esse file of record.
Métodos abstratos de TDataSet
TDataSet, sendo uma classe abstrata, é inútil até que você redefina os métodos necessários para manipulação de algum tipo de dataset em particular. Particularmente, você precisa pelo menos redefinir cada um
dos 23 métodos abstratos de TDataSet e talvez alguns métodos opcionais. Para fins de explicação, nós os
dividimos em seis agrupamentos lógicos: métodos de buffer de registro, métodos de navegação, métodos
de bookmark, métodos de edição, métodos diversos e métodos opcionais.
O código a seguir mostra uma versão editada de TDataSet, conforme definida em Db.pas. Por questão
de clareza, somente os métodos mencionados até aqui são mostrados, e os métodos são categorizados
com base nos agrupamentos lógicos que explicamos. Veja o código:
type
TDataSet = class(TComponent)
{ ... }
protected
{ Métodos de buffer de registro }
function AllocRecordBuffer: PChar; virtual; abstract;
procedure FreeRecordBuffer(var Buffer: PChar); virtual; abstract;
procedure InternalInitRecord(Buffer: PChar); virtual; abstract;
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean):
TGetResult; virtual; abstract;
function GetRecordSize: Word; virtual; abstract;
procedure GetFieldData(Field: TField; Buffer: Pointer); Boolean;
override;
procedure SetFieldData(Field: TField; Buffer: Pointer); virtual;
abstract;
{ Métodos de bookmark }
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
procedure InternalSetToRecord(Buffer: PChar); override;
{ Métodos de navegação }
procedure InternalFirst; virtual; abstract;
procedure InternalLast; virtual; abstract;
{ Métodos de edição }
procedure InternalAddRecord(Buffer: Pointer; Append: Boolean);
virtual; abstract;
procedure InternalDelete; virtual; abstract;
procedure InternalPost; virtual; abstract;
{ Métodos diversos }
procedure InternalClose; virtual; abstract;
procedure InternalHandleException; virtual; abstract;
procedure InternalInitFieldDefs; virtual; abstract;
procedure InternalOpen; virtual; abstract;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
297
function IsCursorOpen: Boolean; virtual; abstract;
{ Métodos opcionais }
function GetRecordCount: Integer; virtual;
function GetRecNo: Integer; virtual;
procedure SetRecNo(Value: Integer); virtual;
{ ... }
end
Métodos de buffer de registro
Você precisa redefinir diversos métodos que lidam com buffers de registro. Na realidade, a VCL realiza
um ótimo trabalho ocultando os detalhes de sua implementação de buffer de registro; TDataSet criará e
gerenciará grupos de buffers, de modo que seu trabalho é principalmente decidir o que entrará nos buffers e mover os dados entre os diferentes buffers. Por ser um requisito para todos os descendentes de TDataSet implementar bookmarks, armazenaremos as informações de bookmark após os dados do registro
no buffer do registro. O registro que usaremos para descrever informações de bookmark é o seguinte:
type
// Registro de info de bookmark para dar suporte aos bookmarks de TDataset:
PDDGBookmarkInfo = ^TDDGBookmarkInfo;
TDDGBookmarkInfo = record
BookmarkData: Integer;
BookmarkFlag: TBookmarkFlag;
end;
O campo BookmarkData representará um valor de busca simples ao arquivo de dados. O campo BookmarkFlag é usado para determinar se o buffer contém um bookmark válido, e terá valores especiais quando
o dataset estiver posicionado nos pontos de início e fim de arquivo (BOF e EOF).
Lembre-se de que essa implementação de buffers de bookmarks e registros é específica desta solução. Se você estivesse criando um descendente de TDataSet para manipular algum outro tipo de dado, poderia escolher implementar seu buffer de registros ou bookmarks de modo diferente. Por exemplo, a origem de dados que você está tentando encapsular já poderá aceitar bookmarks como um suporte nativo.
Antes de examinarmos os métodos específicos do buffer de registro, primeiro dê uma olhada no
construtor para a classe TDDGDataSet:
constructor TDDGDataSet.Create(AOwner: TComponent);
begin
FIndexList := TIndexList.Create;
FRecordSize := SizeOf(TDDGData);
FBufferSize := FRecordSize + SizeOf(TDDGBookmarkInfo);
inherited Create(AOwner);
end;
Esse construtor faz três coisas importantes. Primeiro, ele cria o objeto TIndexList. Esse objeto de lista
é usado como o arquivo de índice descrito anteriormente para manter a ordem no dataset. Em seguida,
os campos FRecordSize e FBufferSize são inicializados. FRecordSize contém o tamanho do registro de dados,
e FBufferSize representa o tamanho total do buffer de registro (o registro de dados mais o tamanho do registro de informações de bookmark). Finalmente, esse método chama o construtor herdado para realizar
a configuração default de TDataSet.
A seguir vemos os métodos abstratos de TDataSet que lidam com buffers de registro, que precisam ser
redefinidos em um descendente. Exceto por GetFieldData( ), todos são declarados como abstratos na classe básica:
298
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalInitRecord(Buffer: PChar); override;
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult; override;
function GetRecordSize: Word; override;
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
AllocRecordBuffer( )
O método AllocRecordBuffer( ) é chamado para alocar memória para um único buffer de registro. Nessa
implementação do método, a função AllocMem( ) é usada para alocar memória suficiente para conter tanto os dados do registro quanto os dados do bookmark:
function TDDGDataSet.AllocRecordBuffer: PChar;
begin
Result := AllocMem(FBufferSize);
end;
FreeRecordBuffer( )
Como você poderia esperar, FreeRecordBuffer( ) precisa liberar a memória alocada pelo método AllocReEle é implementada por meio do procedimento FreeMem( ), como mostramos a seguir:
cordBuffer( ).
procedure TDDGDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer);
end;
InternalInitRecord( )
O método InternalInitRecord( ) é chamado para inicializar um buffer de registro. Nesse método, você
pode fazer coisas como definir valores de campo default e realizar algum tipo de inicialização de dados
personalizados do buffer de registro. Nesse caso, simplesmente inicializamos o buffer de registro com
zero:
procedure TDDGDataSet.InternalInitRecord(Buffer: PChar);
begin
FillChar(Buffer^, FBufferSize, 0);
end
GetRecord( )
A função principal do método GetRecord( ) é recuperar os dados do registro para o registro anterior, atual
ou seguinte no dataset. O valor de retorno dessa função é do tipo TGetResult, que é definido na unidade Db
da seguinte forma:
TGetResult = (grOK, grBOF, grEOF, grError);
O significado de cada uma das enumerações é bastante auto-explicativo: grOk significa sucesso, grBOF
significa que o dataset está no início, grEOF significa que o dataset está no final, e grError significa que houve um erro.
A implementação deste método é a seguinte:
299
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function TDDGDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
var
IndexPos: Integer;
begin
if FIndexList.Count < 1 then
Result := grEOF
else begin
Result := grOk;
case GetMode of
gmPrior:
if FRecordPos <= 0 then
begin
Result := grBOF;
FRecordPos := -1;
end
else
Dec(FRecordPos);
gmCurrent:
if (FRecordPos < 0) or (FRecordPos >= RecordCount) then
Result := grError;
gmNext:
if FRecordPos >= RecordCount-1 then
Result := grEOF
else
Inc(FRecordPos);
end;
if Result = grOk then
begin
IndexPos := Integer(FIndexList[FRecordPos]);
Seek(FDataFile, IndexPos);
BlockRead(FDataFile, PDDGData(Buffer)^, 1);
with PDDGBookmarkInfo(Buffer + FRecordSize)^ do
begin
BookmarkData := FRecordPos;
BookmarkFlag := bfCurrent;
end;
end
else if (Result = grError) and DoCheck then
DatabaseError(‘No records’);
end;
end;
O campo FRecordPos acompanha a posição atual do registro no dataset. Você notará que FRecordPos é
incrementado ou decrementado, conforme o caso, quando GetRecord( ) é chamado para obter o registro
anterior ou seguinte. Se FRecordPos tiver um número de registro válido, FRecordPos será usado como um índice para FIndexList. O número nesse índice é um valor de busca para o arquivo de dados, e os dados do
registro são lidos dessa posição no arquivo de dados para o buffer especificado pelo parâmetro Buffer.
GetRecord( ) também possui uma tarefa adicional. Quando o parâmetro DoCheck é True e grError é o
valor de retorno em potencial, uma exceção deve ser gerada.
GetRecordSize( )
300
O método GetRecordSize( ) deverá retornar o tamanho, em bytes, da parte de dados do registro contido
no buffer de registro. Cuidado para não retornar o tamanho do buffer de registro inteiro; retorne apenas
o tamanho da parte de dados. Nesta implementação, retornamos o valor do campo FRecordSize:
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function TDDGDataSet.GetRecordSize: Word;
begin
Result := FRecordSize;
end;
GetFieldData( )
O método GetFieldData( ) é responsável por copiar dados do buffer de registro ativo (conforme fornecido
pela propriedade ActiveBuffer) para um buffer de campo. Isso normalmente é feito de modo mais ágil
com o procedimento Move( ). Você pode diferenciar qual campo irá copiar usando a propriedade Index ou
Name de Field. Além disso, não se esqueça de copiar a partir do offset atual dentro de ActiveBuffer, pois
ActiveBuffer contém os dados completos de um registro, e Buffer contém apenas os dados de um campo.
Essa implementação copia os campos da estrutura de buffer interna para seu respectivo Tfield:
function TDDGDataSet.GetFieldData(Field: TField; Buffer: Pointer):
Boolean;
begin
Result := True;
case Field.Index of
0:
begin
Move(ActiveBuffer^, Buffer^, Field.Size);
Result := PChar(Buffer)^ < > #0;
end;
1: Move(PDDGData(ActiveBuffer)^.Height, Buffer^, Field.DataSize);
2: Move(PDDGData(ActiveBuffer)^.ShoeSize, Buffer^, Field.DataSize);
end;
end;
Tanto este método quanto SetFieldData( ) podem se tornar muito mais complexos se você quiser
dar suporte a recursos mais avançados, como campos calculados e filtros.
SetFieldData( )
A finalidade de SetFieldData( ) é inversa à de GetFieldData( ); SetFieldData( ) copia dados do buffer de
campo para o buffer do registro ativo. Como você pode ver pelo código a seguir, as implementações desses dois métodos são muito semelhantes:
procedure TDDGDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
case Field.Index of
0: Move(Buffer^, ActiveBuffer^, Field.Size);
1: Move(Buffer^, PDDGData(ActiveBuffer)^.Height, Field.DataSize);
2: Move(Buffer^, PDDGData(ActiveBuffer)^.ShoeSize, Field.DataSize);
end;
DataEvent(deFieldChange, Longint(Field));
end;
Depois que os dados são copiados, o método DataEvent( ) é chamado para sinalizar que o campo
mudou e disparar o evento OnChange do campo.
Métodos de bookmark
Já mencionamos que o suporte para bookmark é exigido para descendentes de TDataSet. Os métodos abstratos de TDataSet a seguir são redefinidos para fornecer esse suporte:
301
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
procedure InternalSetToRecord(Buffer: PChar); override;
Para TDDGDataSet, você verá que as implementeações desses métodos giram principalmente em torno
da manipulação das informações do bookmark anexadas ao final do buffer de registro.
GetBookmarkFlag( ) e SetBookmarkFlag( )
Flags de bookmark são usados internamente por TDataSet para determinar se um registro qualquer é o
primeiro ou o último no dataset. Para essa finalidade, você precisa redefinir os métodos GetBookmarkFlag( ) e SetBookmarkFlag( ). A implementação desses métodos por TDDGDataSet lê e grava no buffer de registro para manter essa informação, como vemos aqui:
function TDDGDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
Result := PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkFlag;
end;
procedure TDDGDataSet.SetBookmarkFlag(Buffer: PChar;
Value: TBookmarkFlag);
begin
PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkFlag := Value;
end;
GetBookmarkData( ) e SetBookmarkData( )
Os métodos GetBookmarkData( ) e SetBookmarkData( ) oferecem um meio para TDataSet manipular os dados
de bookmark de um registro sem reposicionar o registro atual. Como você pode ver, esses métodos são
implementados de uma forma semelhante aos métodos descritos no exemplo anterior:
procedure TDDGDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PInteger(Data)^ := PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkData;
end;
procedure TDDGDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkData := PInteger(Data)^;
end;
InternalGotoBookmark( )
O método InternalGotoBookmark( ) é chamado para reposicionar o registro atual para aquele representado
pelo parâmetro Bookmark. Como um valor de bookmark é igual ao número do registro para TDDGDataSet, a
implementação desse método é simples:
procedure TDDGDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
FRecordPos := Integer(Bookmark);
end;
302
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
InternalSetToRecord( )
é semelhante a InternalGotoBookmark( ), mas recebe como parâmetro um buffer de
registro em vez de um valor de bookmark. O trabalho desse método é posicionar o dataset para o registro
fornecido no parâmetro Buffer. Essa implementação de um buffer de registro contém as informações de
bookmark, pois o valor do bookmark é igual à posição do registro; portanto, a implementação desse método possui apenas uma linha:
InternalSetToRecord( )
procedure TDDGDataSet.InternalSetToRecord(Buffer: PChar);
begin
// valor do bookmark é igual ao offset dentro do arquivo
FRecordPos := PDDGBookmarkInfo(Buffer + FRecordSize)^.Bookmarkdata;
end;
Métodos de navegação
Você precisa redefinir vários métodos de navegação abstratos em TDataSet a fim de posicionar o dataset
no primeiro ou último registro:
procedure InternalFirst; override;
procedure InternalLast; override;
As implementações desses métodos são muito simples; InternalFirst( ) define o valor de FRecordPos
como -1 (a marca de BOF), e InternalLast( ) define a posição do registro como o contador de registros.
COmo o índice de registros é baseado em zero, o contador é 1 a mais do que o último índice (a marca de
EOF). Veja um exemplo:
procedure TDDGDataSet.InternalFirst;
begin
FRecordPos := -1;
end;
procedure TDDGDataSet.InternalLast;
begin
FRecordPos := FIndexList.Count;
end;
Métodos de edição
Três métodos abstratos de TDataSet precisam ser redefinidos a fim de permitir a edição, anexação, inserção e exclusão de registros:
procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
procedure InternalDelete; override;
procedure InternalPost; override;
InternalAddRecord( )
InternalAddRecord( ) é chamado quando um registro é inserido ou anexado ao dataset. O parâmetro Buffer
aponta para o buffer de registro a ser incluído no dataset, e o parâmetro Append é True quando um registro
está sendo anexado e False quando um registro está sendo inserido. A implementação de TDDGDataSet deste
método busca até o final do arquivo de dados, grava os dados do registro no arquivo e depois acrescenta
ou insere o valor de busca do arquivo de dados na posição apropriada da lista de índice:
303
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
procedure TDDGDataSet.InternalAddRecord(Buffer: Pointer;
Append: Boolean);
var
RecPos: Integer;
begin
Seek(FDataFile, FileSize(FDataFile));
BlockWrite(FDataFile, PDDGData(Buffer)^, 1);
if Append then
begin
FIndexList.Add(Pointer(FileSize(FDataFile) - 1));
InternalLast;
end
else begin
if FRecordPos = -1 then RecPos := 0
else RecPos := FRecordPos;
FIndexList.Insert(RecPos, Pointer(FileSize(FDataFile) - 1));
end;
FIndexList.SaveToFile(FIdxName);
end;
InternalDelete( )
O método InternalDelete( ) deleta o registro atual do dataset. Visto que não é prático remover um registro do meio do arquivo de dados, o registro atual é deletado da lista de índice. Isso, com efeito, deixará o
registro órfão no arquivo de dados deletado, removendo a entrada de índice para esse registro de dados.
Veja um exemplo:
procedure TDDGDataSet.InternalDelete;
begin
FIndexList.Delete(FRecordPos);
if FRecordPos >= FIndexList.Count then Dec(FRecordPos);
end;
NOTA
Este método de exclusão significa que o arquivo de dados não será encurtado em tamanho, mesmo que os
registros sejam deletados (semelhante aos arquivos do dBASE). Se você pretende usar esse tipo de dataset
em um trabalho comercial, um bom acréscimo seria um método de compactação de arquivo, que remova
os registros órfãos do arquivo de dados.
InternalPost( )
O método InternalPost( ) é chamado por TDataSet.Post( ). Nesse método, você deverá transferir os dados
do buffer de registro ativo para o arquivo de dados. Você notará que a implementação desse método é
muito semelhante à de InternalAddRecord( ), como vemos aqui:
procedure TDDGDataSet.InternalPost;
var
RecPos, InsPos: Integer;
begin
if FRecordPos = -1 then
RecPos := 0
else begin
304
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
if State = dsEdit then RecPos := Integer(FIndexList[FRecordPos])
else RecPos := FileSize(FDataFile);
end;
Seek(FDataFile, RecPos);
BlockWrite(FDataFile, PDDGData(ActiveBuffer)^, 1);
if State < > dsEdit then
begin
if FRecordPos = -1 then InsPos := 0
else InsPos := FRecordPos;
FIndexList.Insert(InsPos, Pointer(RecPos));
end;
FIndexList.SaveToFile(FIdxName);
end;
Métodos diversos
Vários outros métodos abstratos devem ser redefinidos a fim de se criar um descendente funcional para
TDataSet. Estes são métodos genéricos para manutenção, e como não podem ser encaixados em uma categoria em particular, vamos chamá-los de métodos diversos. Os métodos são os seguintes:
procedure
procedure
procedure
procedure
InternalClose; override;
InternalHandleException; override;
InternalInitFieldDefs; override;
InternalOpen; override;
InternalClose( )
InternalClose( ) é chamado por TDataSet.Close( ). Nesse método, você deverá desalocar todos os recursos
associados ao dataset que foram alocados por InternalOpen( ) ou que foram alocados através do uso do
dataset. Nessa implementação, o arquivo de dados é fechado e garantimos que a lista de índices foi persistida em disco. Além do mais, FRecordPos é definido para a marca de BOF, e o registro do arquivo de dados é zerado:
procedure TDDGDataSet.InternalClose;
begin
if TFileRec(FDataFile).Mode < > 0 then
CloseFile(FDataFile);
FIndexList.SaveToFile(FIdxName);
FIndexList.Clear;
if DefaultFields then
DestroyFields;
FRecordPos := -1;
FillChar(FDataFile, SizeOf(FDataFile), 0);
end;
InternalHandleException( )
é chamado se surgir uma exceção enquanto esse componente está sendo lido
ou gravado em um stream. A menos que você tenha uma necessidade específica de tratar dessas exceções,
implemente esse método da seguinte forma:
InternalHandleException( )
procedure TDDGDataSet.InternalHandleException;
begin
// implementação padrão para este método
Application.HandleException(Self);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
305
InternalInitFieldDefs( )
No método InternalInitFieldDefs( ), você deverá definir os campos contidos no dataset. Isso é feito através de instâncias do objeto TFieldDef, com a passagem da propriedade FieldDefs do TDataSet como Owner.
Nesse caso, três objetos de TFieldDef são criados, representando os três campos nesse dataset:
procedure TDDGDataSet.InternalInitFieldDefs;
begin
// Cria FieldDefs, que mapeia cada campo no registro de dados
FieldDefs.Clear;
TFieldDef.Create(FieldDefs, ‘Name’, ftString, SizeOf(TNameStr), False, 1);
TFieldDef.Create(FieldDefs, ‘Height’, ftFloat, 0, False, 2);
TFieldDef.Create(FieldDefs, ‘ShoeSize’, ftInteger, 0, False, 3);
end;
InternalOpen( )
O método TInternalOpen( ) é chamado por TDataSet.Open( ). Nesse método, você deverá abrir a origem de
dados básica, inicializar quaisquer campos ou propriedades internas, criar as definições de campo, se for
preciso, e vincular as definições de campo aos dados. A implementação desse método, a seguir, abre o arquivo de dados, carrega a lista de índices de um arquivo, inicializa o campo FRecordPos e a propriedade BookmarkSize, e cria e vincula as definições de campo. Você verá, no código a seguir, que o método também
dá ao usuário uma chance de criar os arquivos do banco de dados se não forem encontrados no disco:
procedure TDDGDataSet.InternalOpen;
var
HFile: THandle;
begin
// certifica-se de que os arquivos de tabela e índices existem
FIdxName := ChangeFileExt(FTableName, feDDGIndex);
if not (FileExists(FTableName) and FileExists(FIdxName)) then
begin
if MessageDlg(‘Table or index file not found. Create new table?’,
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
HFile := FileCreate(FTableName);
if HFile = INVALID_HANDLE_VALUE then
DatabaseError(‘Error creating table file’);
FileClose(HFile);
HFile := FileCreate(FIdxName);
if HFile = INVALID_HANDLE_VALUE then
DatabaseError(‘Error creating index file’);
FileClose(HFile);
end
else
DatabaseError(‘Could not open table’);
end;
// abre o arquivo de dados
FileMode := fmShareDenyNone or fmOpenReadWrite;
AssignFile(FDataFile, FTableName);
Reset(FDataFile);
try
FIndexList.LoadFromFile(FIdxName); // inicializa índice TList do arquivo
FRecordPos := -1;
// pos. de reg. inicial antes de BOF
BookmarkSize
:=
SizeOf(Integer);
// inic. tamanho do bookmark para VCL
306
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
InternalInitFieldDefs;
// inic. objetos de FieldDef
// Cria componentes TField quando nenhum campo persistente tiver
// sido criado
if DefaultFields then CreateFields;
BindFields(True);
// vincula FieldDefs aos dados reais
except
CloseFile(FDataFile);
FillChar(FDataFile, SizeOf(FDataFile), 0);
raise;
end;
end;
NOTA
Quaisquer alocações de recurso feitas em InternalOpen( ) deverão ser liberadas em InternalClose( ).
IsCursorOpen( )
O método IsCursorOpen( ) é chamado internamente em TDataSet enquanto o dataset está sendo aberto a
fim de determinar se os dados estão disponíveis, embora o dataset esteja inativo. A implementação desse
método em TDDGData retorna True somente se o arquivo de dados tiver sido aberto, como vemos aqui:
function TDDGDataSet.IsCursorOpen: Boolean;
begin
// “Cursor” está aberto se o arquivo de dados estiver aberto. Arquivo
// está aberto se o Mode de FDataFile incluir o FileMode em que o
// arquivo foi aberto.
Result := TFileRec(FDataFile).Mode < > 0;
end;
DICA
O método anterior ilustra um recurso interessante do Object Pascal: um arquivo de registro ou arquivo não
tipificado pode receber typecast para um TFileRec, a fim de obter informações de baixo nível sobre o arquivo. TFileRec está descrito no Capítulo 12.
Métodos opcionais de número de registro
Se você quiser tirar proveito da capacidade de rolagem de TDBGrid em relação à posição do cursor no dataset, então deverá redefinir três métodos:
function GetRecordCount: Integer; override;
function GetRecNo: Integer; override;
procedure SetRecNo(Value: Integer); override;
Embora esse recurso faça sentido nesta implementação, em muitos casos essa capacidade não será
prática ou sequer possível. Por exemplo, se você estiver trabalhando com uma quantidade de dados
imensa, pode não ser prático obter um contador de registros, ou se estiver se comunicando com um servidor SQL, essa informação pode nem sequer estar disponível.
Esta implementação de TDataSet é bastante simples, e esses métodos são apropriadamente simples de
se implementar:
307
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
function TDDGDataSet.GetRecordCount: Integer;
begin
Result := FIndexList.Count;
end;
function TDDGDataSet.GetRecNo: Integer;
begin
UpdateCursorPos;
if (FRecordPos = -1) and (RecordCount > 0) then
Result := 1
else
Result := FRecordPos + 1;
end;
procedure TDDGDataSet.SetRecNo(Value: Integer);
begin
if (Value >= 0) and (Value <= FIndexList.Count-1) then
begin
FRecordPos := Value - 1;
Resync([ ]);
end;
end;
TDDGDataSet
A Listagem 30.5 mostra a unidade DDG_DS, que contém a implementação completa da unidade TDDGDataSet.
Listagem 30.5 A unidade DDG_DS.pas
unit DDG_DS;
interface
uses Windows, Db, Classes, DDG_Rec;
type
// Registro de info de bookmark para dar suporte aos bookmarks de TDataset:
PDDGBookmarkInfo = ^TDDGBookmarkInfo;
TDDGBookmarkInfo = record
BookmarkData: Integer;
BookmarkFlag: TBookmarkFlag;
end;
// Lista usada para manter o acesso ao arquivo de registro:
TIndexList = class(TList)
public
procedure LoadFromFile(const FileName: string); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
procedure SaveToFile(const FileName: string); virtual;
procedure SaveToStream(Stream: TStream); virtual;
end;
// Descendente DDG especializado de TDataset para dados de nossa “tabela”:
308
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.5 Continuação
TDDGDataSet = class(TDataSet)
private
function GetDataFileSize: Integer;
public
FDataFile: TDDGDataFile;
FIdxName: string;
FIndexList: TIndexList;
FTableName: string;
FRecordPos: Integer;
FRecordSize: Integer;
FBufferSize: Integer;
procedure SetTableName(const Value: string);
protected
{ Redefinições obrigatórias }
// Métodos de buffer de registro:
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalInitRecord(Buffer: PChar); override;
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult; override;
function GetRecordSize: Word; override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
// Métodos de bookmark:
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
procedure InternalSetToRecord(Buffer: PChar); override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
// Métodos de navegação:
procedure InternalFirst; override;
procedure InternalLast; override;
// Métodos de edição:
procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
procedure InternalDelete; override;
procedure InternalPost; override;
// Métodos diversos:
procedure InternalClose; override;
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalOpen; override;
function IsCursorOpen: Boolean; override;
{ Redefinições opcionais: }
function GetRecordCount: Integer; override;
function GetRecNo: Integer; override;
procedure SetRecNo(Value: Integer); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
// Procedimentos adicionais
procedure EmptyTable;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
309
Listagem 30.5 Continuação
published
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
property
Active;
TableName: string read FTableName write SetTableName;
BeforeOpen;
AfterOpen;
BeforeClose;
AfterClose;
BeforeInsert;
AfterInsert;
BeforeEdit;
AfterEdit;
BeforePost;
AfterPost;
BeforeCancel;
AfterCancel;
BeforeDelete;
AfterDelete;
BeforeScroll;
AfterScroll;
OnDeleteError;
OnEditError;
// Propriedades adicionais:
property DataFileSize: Integer read GetDataFileSize;
end;
procedure Register;
implementation
uses BDE, DBTables, SysUtils, DBConsts, Forms, Controls, Dialogs;
const
feDDGTable = ‘.ddg’;
feDDGIndex = ‘.ddx’;
// Observe que o arquivo não está sendo bloqueado!
{ TIndexList }
procedure TIndexList.LoadFromFile(const FileName: string);
var
F: TFileStream;
begin
F := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(F);
finally
F.Free;
end;
end;
310
procedure TIndexList.LoadFromStream(Stream: TStream);
var
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.5 Continuação
Value: Integer;
begin
while Stream.Position < Stream.Size do
begin
Stream.Read(Value, SizeOf(Value));
Add(Pointer(Value));
end;
ShowMessage(IntToStr(Count));
end;
procedure TIndexList.SaveToFile(const FileName: string);
var
F: TFileStream;
begin
F := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
try
SaveToStream(F);
finally
F.Free;
end;
end;
procedure TIndexList.SaveToStream(Stream: TStream);
var
i: Integer;
Value: Integer;
begin
for i := 0 to Count - 1 do
begin
Value := Integer(Items[i]);
Stream.Write(Value, SizeOf(Value));
end;
end;
{ TDDGDataSet }
constructor TDDGDataSet.Create(AOwner: TComponent);
begin
FIndexList := TIndexList.Create;
FRecordSize := SizeOf(TDDGData);
FBufferSize := FRecordSize + SizeOf(TDDGBookmarkInfo);
inherited Create(AOwner);
end;
destructor TDDGDataSet.Destroy;
begin
inherited Destroy;
FIndexList.Free;
end;
function TDDGDataSet.AllocRecordBuffer: PChar;
begin
Result := AllocMem(FBufferSize);
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
311
Listagem 30.5 Continuação
procedure TDDGDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer);
end;
procedure TDDGDataSet.InternalInitRecord(Buffer: PChar);
begin
FillChar(Buffer^, FBufferSize, 0);
end;
function TDDGDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
var
IndexPos: Integer;
begin
if FIndexList.Count < 1 then
Result := grEOF
else begin
Result := grOk;
case GetMode of
gmPrior:
if FRecordPos <= 0 then
begin
Result := grBOF;
FRecordPos := -1;
end
else
Dec(FRecordPos);
gmCurrent:
if (FRecordPos < 0) or (FRecordPos >= RecordCount) then
Result := grError;
gmNext:
if FRecordPos >= RecordCount-1 then
Result := grEOF
else
Inc(FRecordPos);
end;
if Result = grOk then
begin
IndexPos := Integer(FIndexList[FRecordPos]);
Seek(FDataFile, IndexPos);
BlockRead(FDataFile, PDDGData(Buffer)^, 1);
with PDDGBookmarkInfo(Buffer + FRecordSize)^ do
begin
BookmarkData := FRecordPos;
BookmarkFlag := bfCurrent;
end;
end
else if (Result = grError) and DoCheck then
DatabaseError(‘No records’);
end;
end;
312
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.5 Continuação
function TDDGDataSet.GetRecordSize: Word;
begin
Result := FRecordSize;
end;
function TDDGDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
begin
Result := True;
case Field.Index of
0:
begin
Move(ActiveBuffer^, Buffer^, Field.Size);
Result := PChar(Buffer)^ < > #0;
end;
1: Move(PDDGData(ActiveBuffer)^.Height, Buffer^, Field.DataSize);
2: Move(PDDGData(ActiveBuffer)^.ShoeSize, Buffer^, Field.DataSize);
end;
end;
procedure TDDGDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
case Field.Index of
0: Move(Buffer^, ActiveBuffer^, Field.Size);
1: Move(Buffer^, PDDGData(ActiveBuffer)^.Height, Field.DataSize);
2: Move(Buffer^, PDDGData(ActiveBuffer)^.ShoeSize, Field.DataSize);
end;
DataEvent(deFieldChange, Longint(Field));
end;
procedure TDDGDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PInteger(Data)^ := PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkData;
end;
function TDDGDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
Result := PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkFlag;
end;
procedure TDDGDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
FRecordPos := Integer(Bookmark);
end;
procedure TDDGDataSet.InternalSetToRecord(Buffer: PChar);
begin
// Valor do bookmark é igual ao offset dentro do arquivo
FRecordPos := PDDGBookmarkInfo(Buffer + FRecordSize)^.Bookmarkdata;
end;
procedure TDDGDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkData := PInteger(Data)^;
end;
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
313
Listagem 30.5 Continuação
procedure TDDGDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
PDDGBookmarkInfo(Buffer + FRecordSize)^.BookmarkFlag := Value;
end;
procedure TDDGDataSet.InternalFirst;
begin
FRecordPos := -1;
end;
procedure TDDGDataSet.InternalInitFieldDefs;
begin
// Cria FieldDefs, que mapeia cada campo no registro de dados
FieldDefs.Clear;
TFieldDef.Create(FieldDefs, ‘Name’, ftString, SizeOf(TNameStr), False, 1);
TFieldDef.Create(FieldDefs, ‘Height’, ftFloat, 0, False, 2);
TFieldDef.Create(FieldDefs, ‘ShoeSize’, ftInteger, 0, False, 3);
end;
procedure TDDGDataSet.InternalLast;
begin
FRecordPos := FIndexList.Count;
end;
procedure TDDGDataSet.InternalClose;
begin
if TFileRec(FDataFile).Mode < > 0 then
CloseFile(FDataFile);
FIndexList.SaveToFile(FIdxName);
FIndexList.Clear;
if DefaultFields then
DestroyFields;
FRecordPos := -1;
FillChar(FDataFile, SizeOf(FDataFile), 0);
end;
procedure TDDGDataSet.InternalHandleException;
begin
// Implementação padrão para este método
Application.HandleException(Self);
end;
procedure TDDGDataSet.InternalDelete;
begin
FIndexList.Delete(FRecordPos);
if FRecordPos >= FIndexList.Count then Dec(FRecordPos);
end;
314
procedure TDDGDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
var
RecPos: Integer;
begin
Seek(FDataFile, FileSize(FDataFile));
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.5 Continuação
BlockWrite(FDataFile, PDDGData(Buffer)^, 1);
if Append then
begin
FIndexList.Add(Pointer(FileSize(FDataFile) - 1));
InternalLast;
end
else begin
if FRecordPos = -1 then RecPos := 0
else RecPos := FRecordPos;
FIndexList.Insert(RecPos, Pointer(FileSize(FDataFile) - 1));
end;
FIndexList.SaveToFile(FIdxName);
end;
procedure TDDGDataSet.InternalOpen;
var
HFile: THandle;
begin
// Certifica-se de que os arquivos de tabela e índices existem
FIdxName := ChangeFileExt(FTableName, feDDGIndex);
if not (FileExists(FTableName) and FileExists(FIdxName)) then
begin
if MessageDlg(‘Table or index file not found. Create new table?’,
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
begin
HFile := FileCreate(FTableName);
if HFile = INVALID_HANDLE_VALUE then
DatabaseError(‘Error creating table file’);
FileClose(HFile);
HFile := FileCreate(FIdxName);
if HFile = INVALID_HANDLE_VALUE then
DatabaseError(‘Error creating index file’);
FileClose(HFile);
end
else
DatabaseError(‘Could not open table’);
end;
// Abre o arquivo de dados
FileMode := fmShareDenyNone or fmOpenReadWrite;
AssignFile(FDataFile, FTableName);
Reset(FDataFile);
try
FIndexList.LoadFromFile(FIdxName); // inicializa índice TList do arquivo
FRecordPos := -1;
// pos. de reg. inicial antes de BOF
BookmarkSize := SizeOf(Integer);
// inic. tamanho do bookmark para VCL
InternalInitFieldDefs;
// inic. objetos de FieldDef
// Cria componentes TField quando nenhum campo persistente tiver
// sido criado
if DefaultFields then CreateFields;
BindFields(True);
// vincula FieldDefs aos dados reais
except
CloseFile(FDataFile);
FillChar(FDataFile, SizeOf(FDataFile), 0);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
315
Listagem 30.5 Continuação
raise;
end;
end;
procedure TDDGDataSet.InternalPost;
var
RecPos, InsPos: Integer;
begin
if FRecordPos = -1 then
RecPos := 0
else begin
if State = dsEdit then RecPos := Integer(FIndexList[FRecordPos])
else RecPos := FileSize(FDataFile);
end;
Seek(FDataFile, RecPos);
BlockWrite(FDataFile, PDDGData(ActiveBuffer)^, 1);
if State < > dsEdit then
begin
if FRecordPos = -1 then InsPos := 0
else InsPos := FRecordPos;
FIndexList.Insert(InsPos, Pointer(RecPos));
end;
FIndexList.SaveToFile(FIdxName);
end;
function TDDGDataSet.IsCursorOpen: Boolean;
begin
// “Cursor” está aberto se o arquivo de dados estiver aberto. Arquivo
// está aberto se o Mode de FDataFile incluir o FileMode em que o
// arquivo foi aberto.
Result := TFileRec(FDataFile).Mode < > 0;
end;
function TDDGDataSet.GetRecordCount: Integer;
begin
Result := FIndexList.Count;
end;
function TDDGDataSet.GetRecNo: Integer;
begin
UpdateCursorPos;
if (FRecordPos = -1) and (RecordCount > 0) then
Result := 1
else
Result := FRecordPos + 1;
end;
316
procedure TDDGDataSet.SetRecNo(Value: Integer);
begin
if (Value >= 0) and (Value <= FIndexList.Count-1) then
begin
FRecordPos := Value - 1;
Resync([ ]);
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Listagem 30.5 Continuação
end;
end;
procedure TDDGDataSet.SetTableName(const Value: string);
begin
CheckInactive;
FTableName := Value;
if ExtractFileExt(FTableName) = ‘’ then
FTableName := FTableName + feDDGTable;
FIdxName := ChangeFileExt(FTableName, feDDGIndex);
end;
procedure Register;
begin
RegisterComponents(‘DDG’, [TDDGDataSet]);
end;
function TDDGDataSet.GetDataFileSize: Integer;
begin
Result := FileSize(FDataFile);
end;
procedure TDDGDataSet.EmptyTable;
var
HFile: THandle;
begin
Close;
DeleteFile(FTableName);
HFile := FileCreate(FTableName);
FileClose(HFile);
DeleteFile(FIdxName);
HFile := FileCreate(FIdxName);
FileClose(HFile);
Open;
end;
end.
Resumo
Este capítulo demonstrou como estender as aplicações de banco de dados do Delphi para incorporar recursos que não são encapsulados pela VCL. Além disso, você aprendeu algumas regras e processos para
fazer chamadas diretas ao BDE a partir de aplicações em Delphi. Você também aprendeu os detalhes
para estender o comportamento de TTable com relação a tabelas do dBASE e do Paradox. Finalmente,
você analisou passo a passo o processo desafiador de criar um descendente funcional de TDataSet. No
próximo capítulo, você aprenderá a criar aplicações do lado do servidor para a Web e fornecer dados
para clientes da Web em tempo real.
317
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Mensagens de erro
e exceções
APÊNDICE
A
NE STE AP ÊN D ICE
l
l
Camadas de manipuladores, camadas
de rigor
Erros de runtime
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 10 — 2ª PROVA
Uma diferença entre software bom e software excelente é que, enquanto o software bom funciona bem, o
software excelente funciona bem e falha bem. Nos programas em Delphi, os erros detectados durante a
execução (runtime) normalmente são informados e manipulados como exceções. Isso permite que o seu
código tenha a oportunidade de responder aos problemas e recuperar-se (recuando e tentando outro método) ou pelo menos “retirar-se delicadamente” (liberando recursos alocados, fechando arquivos e exibindo uma mensagem de erro), em vez de simplesmente dar pau e deixar uma bagunça no seu sistema. A
maioria das exceções nos programas em Delphi é gerada e manipulada totalmente dentro do programa;
pouquíssimos erros de runtime realmente geram um término gritante em um programa.
Este apêndice relaciona as mensagens de erro mais comuns que uma aplicação em Delphi pode informar e observações práticas para ajudá-lo a identificar a causa da condição de erro. Visto que cada
componente que você inclui no seu ambiente Delphi normalmente possui seu próprio conjunto de mensagens de erro, essa lista nunca poderá ser completa, de modo que focalizaremos as mensagens mais comuns, ou mais insidiosas, que você provavelmente encontrará ao desenvolver e depurar suas aplicações
em Delphi.
Camadas de manipuladores, camadas de rigor
Todo programa em Delphi possui dois manipuladores de exceção default, um abaixo do outro. A VCL
oferece o manipulador de exceção padrão que você verá na maior parte do tempo. A VCL coloca um manipulador de exceção em torno dos pontos de entrada do procedimento de janela de cada objeto da VCL.
Se houver uma exceção enquanto o programa está respondendo a uma mensagem do Windows (o programa gasta 99 por cento do seu tempo fazendo isso) e a exceção não for tratada pelo seu código ou por
um componente da VCL, essa exceção vai parar no manipulador de exceção default da VCL, no procedimento de janela. Esse manipulador de exceção chama Application.HandleException, que mostrará a mensagem de texto da instância da exceção para o usuário em uma caixa de mensagem pop-up. Depois disso,
seu programa continuará funcionando e processando outras mensagens da janela.
O manipulador de exceção de mais baixo nível reside no núcleo da RTL do Delphi, vários andares
abaixo do manipulador de exceção default da VCL. Se houver uma exceção fora do contexto do processamento de mensagem – como durante a partida ou encerramento do programa ou durante a execução
do manipulador de exceção default da VCL – e a exceção não for tratada, ela no fim acabará no manipulador de exceção default da RTL. Nesse nível, não há apelo para recuperação – nenhum loop de mensagem para fazer as coisas continuarem. Quando ativado, o manipulador de exceção default da RTL apresenta uma mensagem de erro detalhada para o usuário e depois termina a aplicação.
Além do texto de mensagem da exceção, o manipulador de exceção default da RTL também informa o endereço do código que gerou a exceção, na forma de um endereço hexadecimal. Use a opção
Search, Find Error no IDE do Delphi e digite esse endereço na caixa de diálogo. O Delphi moverá o cursor para o local do seu código-fonte que corresponde a esse endereço, se puder localizar o endereço e o
código-fonte.
Se o Delphi responder com “Address Not Found” (endereço não localizado), isso pode significar
que o erro ocorreu em outro módulo (por exemplo, um ponteiro “maluco” escreveu sobre um local da
memória em uso por alguma outra aplicação). No entanto, freqüentemente a mensagem “Address Not
Found” indica que você desativou informações de número de linha na unidade a qual o endereço corresponde ({$D-}) ou que você não possui o código-fonte para essa unidade. Certifique-se de estar compilando seu projeto com as informações de depuração do compilador ativada, através de Project, caixa de diálogo Options, página Compiler, seção Debugging. Enquanto você estiver com a caixa de diálogo Project,
Options aberta, verifique se o caminho de consulta na página Directories/Conditionals contém todos os
diretórios de código-fonte que você deseja usar durante a depuração. Se o IDE do Delphi não puder localizar um arquivo-fonte, ele não poderá mostrar a linha do código que corresponde ao endereço do erro
de exceção. Depois disso, use Project, Build All para recompilar todas as suas unidades com as novas configurações do compilador.
319
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Erros de runtime
Esta seção dará algumas indicações sobre o que você deve fazer quando encontrar erros na forma de exceções ou falhas em funções da API do Win32. Esses tipos de erros raramente são fatais, mas você precisa
saber como enfrentá-los quando houver necessidade.
Exceções
Vamos descrever aqui as exceções adicionais que os componentes da VCL do Delphi podem gerar. Lembre-se de que os componentes personalizados e o seu próprio código podem definir (e normalmente definem) classes de exceção adicionais, específicas à tarefa sendo realizada.
Várias classes de exceção listadas aqui descrevem condições de erro relacionadas: famílias de erros.
O relacionamento entre as classes de exceção é capturado criando-se uma classe de exceção de uso geral
para representar toda a família e classes de exceção específicas, herdadas dessa classe de uso geral. Quando você quiser tratar de todos os erros nessa família da mesma maneira, use a classe de exceção de uso geral na cláusula on do seu bloco except. Quando você quiser apenas tratar de certos erros específicos dessa
família, use as classes de exceção específicas nas cláusulas on do seu bloco except.
Na lista a seguir, usamos recuos para agrupar classes de exceção relacionadas, abaixo de sua classe
ancestral genérica comum:
l
l
l
Exception.
Esse é o ancestral de todas as classes de exceção. Não há nada de errado em usar essa
classe para gerar exceções em um código eventual, mas para o código de produção, você deverá
distinguir entre as inúmeras famílias de erros que sua aplicação poderá encontrar. A melhor maneira de distinguir uma família de condições de erro relacionadas do restante do pacote é usar
uma classe de exceção personalizada para informar esses erros relacionados.
EAbort. Considerada a exceção “silenciosa” do Delphi, esta exceção é interceptada pelo manipulador de exceção default da VCL, mas a VCL não informa ao usuário de que a exceção ocorreu.
Use EAbort quando você quiser tirar proveito da capacidade da exceção de abortar e escapar de
um processo complicado, mas não quiser que o usuário veja uma mensagem de erro. Lembre-se
de que os termos exceção e erro não são equivalentes: as exceções são um meio de alterar o fluxo
do programa para facilitar o tratamento de erros (entre outras coisas).
EAccessViolation. Uma violação de acesso ocorreu no sistema operacional. Normalmente causado
por um ponteiro Nil ou “maluco”.
l
EAssertionFailed.
l
EBitsError.
l
A instrução passada para o procedimento Assert() foi avaliada como False.
Gerado quando a propriedade Bits ou Size de um objeto TBits está fora dos limites.
EComponentError. Esta exceção é gerada em duas situações. A primeira situação é quando você usa
RegisterClasses() para tentar registrar um componente fora do procedimento Register(). A se-
gunda é quando o nome do seu componente é inválido ou não exclusivo.
l
EControlC.
O usuário interrompeu com a combinação de teclas Ctrl+C. Esta exceção só ocorre
dentro de aplicações no modo console.
l
EDbEditError. O usuário incluiu texto em um componente TMaskEdit ou TDbEdit incompatível com a
máscara de edição atual.
l
l
EDdeError. Ocorreu um erro durante uma operação de DDE com
TDdeClientConv, TDdeClientItem, TDdeServerConv ou TDdeServerItem.
EExternalException.
tema operacional.
l
320
qualquer um dos componentes
Esta exceção ocorre quando uma exceção não reconhecida é gerada pelo sis-
EInOutError. Esta exceção é gerada quando ocorre um erro de I/O no seu programa. Ela só acontecerá quando a verificação de I/O estiver ativada por meio de {$I+} no código, ou pela ativação
de I/O Checking na página Compiler da caixa de diálogo Project Options no ambiente IDE.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
l
EIntError.
Este é o ancestral de todas as exceções matemáticas de inteiros. Aqui estão os descendentes dessa classe:
EDivByZero. Esta exceção é gerada quando você divide um número inteiro por zero. Ela surge como
resultado do erro de runtime 200. Este exemplo de código causará uma exceção EDivByZero:
var
I: integer;
begin
I := 0;
I := 10 div I;
end;
l
{ exceção gerada aqui }
EIntOverflow. Esta exceção é gerada quando você tenta realizar uma operação que estoure uma
variável integral além da capacidade do tipo da variável. A exceção é gerada como resultado
de um erro de runtime 215. Ela só surgirá se a verificação de estouro estiver ativada por meio
de {$Q+} no código ou pela ativação de Overflow Checking na página Compiler da caixa de
diálogo Project Options, no ambiente IDE. O código a seguir causará essa exceção:
var
I: longint;
begin
I := MaxLongint;
I := I * I; { exceção gerada aqui }
end;
l
ERangeError.
Esta exceção é gerada quando você tenta indexar um array além de seus limites
declarados ou quando tenta armazenar um valor muito grande em uma variável de tipo integral. Ela é gerada como resultado do erro de runtime 201. A verificação de intervalo precisa
estar ativada com {$R+} no código ou pela ativação de Range Checking da página Compiler da
caixa de diálogo Project Options no IDE para que este erro ocorra. O exemplo a seguir fará
com que o Delphi gere uma exceção:
var
a: array[1..10] of integer;
i: integer;
begin
i := 17;
a[i] := 1; { exceção gerada aqui }
end;
l
EIntfCastError.
não aceita.
l
Foi feita uma tentativa de converter um objeto ou interface para uma interface
EInvalidCast. Esta exceção é gerada quando você tenta usar o operador as para efetuar o typecast
de uma classe para uma classe incompatível. Esta exceção é gerada como resultado do erro de
runtime 219. O código a seguir fará com que a exceção seja gerada:
var
b: TObject;
begin
B := TButton.Create(nil);
{ exceção gerada aqui – TMemo não é ancestral de TButton }
with B as TMemo do
...
end;
l
EInvalidGraphic. Esta exceção é gerada quando você tenta usar LoadFromFile() em um arquivo que
não tenha um formato gráfico compatível em uma classe que espera um arquivo gráfico.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
321
l
EInvalidGraphicOperation.
Esta exceção é gerada quando você tenta realizar uma operação ilegal
sobre um objeto gráfico. Por exemplo, redimensionar um TIcon é ilegal.
l
EInvalidOperation.
Esta exceção ocorre quando você tenta exibir ou realizar qualquer outra operação que exija uma alça de janela em um controle sem um pai. Veja um exemplo:
var
b: TBitBtn;
begin
b := TBitBtn.Create(Self);
b.SetFocus; { exceção gerada aqui }
end;
l
EInvalidPointer. Esta exceção é gerada normalmente quando você tenta liberar uma parte inválida ou já liberada da memória em uma chamada a Dispose(), FreeMem() ou ao destruidor de uma
classe. Este exemplo gera uma exceção EInvalidPointer:
var
p: pointer;
begin
GetMem(p, 8);
FreeMem(p, 8);
FreeMem(p, 8);
end;
l
{ exceção gerada aqui }
EListError. Esta exceção será
TList. Veja um exemplo:
gerada se você tentar indexar além do final de um descendente de
var
S: TStringList;
Strng: String;
begin
S := TStringList.Create;
S.Add(‘Uma String’);
Strng := S.String[2]; { exceção gerada aqui }
end;
l
EMathError.
das:
l
Este é o objeto ancestral do qual as exceções de ponto flutuante a seguir são deriva-
EInvalidOp. Esta exceção é gerada quando uma instrução inválida é enviada ao co-processador
numérico. A exceção é rara, a menos que você controle o co-processador diretamente com código BASM.
l
l
322
l
EOverflow.
Esta exceção é gerada como resultado de um overflow de ponto flutuante (ou seja,
quando um valor se torna muito grande para estar contido em uma variável de ponto flutuante). Esta exceção corresponde ao erro de runtime 205.
l
EUnderflow.
l
EZeroDivide.
Esta exceção é gerada como resultado de um underflow de ponto flutuante (ou
seja, quando um valor se torna muito pequeno para estar contido em uma variável de ponto
flutuante). Esta exceção corresponde ao erro de runtime 206.
Gerada quando um número de ponto flutuante é dividido por zero.
EMCIDeviceError.
Esta exceção indica que ocorreu um erro no componente TMediaPlayer. Normalmente, a exceção é gerada quando o usuário tenta tocar alguma mídia cujo tipo não é aceito pelo
hardware.
EMenuError. Esta é uma exceção genérica que ocorre em quase toda condição de erro que envolve
um componente TMenu, TMenuItem ou TPopupMenu.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
EOleCtrlError. Esta exceção é reservada para os erros de wrapper do controle ActiveX, mas atual-
mente não está sendo usada na VCL.
l
EOleError.
l
Esta exceção é gerada quando ocorre um erro de OLE Automation.
EOleSysError. Esta exceção é gerada pelas rotinas OleCheck() e OleError() quando ocorre um erro
na chamada a uma função da API OLE.
l
l
l
EOleException.
call.
Gerada quando ocorre um erro dentro de uma função ou procedimento safe-
EOutlineError. Esta é uma exceção genérica que é gerada quando ocorre um erro enquanto se trabalha com um componente TOutline.
EOutOfMemory. Esta exceção é gerada quando você chama New(), GetMem() ou um construtor de classe
e não há memória suficiente disponível no heap para a alocação. Esta exceção corresponde ao
erro de runtime 203.
EOutOfResources. Esta exceção ocorre quando o Windows não pode preencher um pedido de
alocação para um recurso do Windows, como uma alça de janela. Esta exceção normalmente reflete bugs no seu driver de vídeo, especialmente se estiver rodando em um modo high-color
(32KB ou 64KB cores). Se esse erro desaparecer quando você passar a usar o driver VGA padrão
do Windows ou um modo inferior do seu driver de vídeo normal, é provável que você tenha encontrado um bug no seu driver de vídeo. Entre em contato com o fabricante da placa de vídeo e
peça uma atualização do driver.
l
EPackageError. Gerada quando ocorre um erro carregando, inicializando ou finalizando um pacote.
l
EParserError. Gerada quando o Delphi não consegue passar o seu arquivo de formulário em texto
de volta para o formato DFM binário. Em geral, isso é resultado de um erro de sintaxe na edição
do formulário no IDE.
l
EPrinter. Esta é uma exceção genérica que será gerada quando ocorre um erro durante a tentativa
de usar o objeto TPrinter.
l
EPrivilege. Esta exceção indica que foi feita uma tentativa de executar uma instrução privilegiada.
l
EPropertyError.
l
ERegistryException.
l
EStackOverflow. Esta exceção representa um erro sério no gerenciamento da pilha em nível de sis-
Esta exceção é gerada quando ocorre um erro dentro do editor de propriedades
de um componente.
Os objetos TRegistry e TRegIniFile geram esta exceção quando ocorre um erro
na leitura ou gravação do Registro do sistema.
tema operacional. O erro deverá ser raro, pois a pilha de uma aplicação é expandida dinamicamente conforme a necessidade pelo sistema operacional, mas pode ocorrer em situações de
pouca memória.
l
EReportError. Esta é uma exceção genérica para um erro que ocorre enquanto se trabalha com um
componente do relatório.
l
EResNotFound. Esta exceção é gerada quando existem problemas carregando um formulário de um
arquivo DFM. Esta exceção normalmente indica que você editou o arquivo DFM e o tornou inválido, o arquivo DFM ou EXE foi modificado ou o arquivo DFM não foi vinculado ao EXE.
Certifique-se de que não deletou ou alterou a diretiva {R *.DFM na unidade do seu formulário.
l
EStreamError. Esta exceção é a classe básica de todas as exceções de stream. Ela normalmente indica um problema de carregamento de uma TStrings de um stream ou a definição da capacidade de
um stream da memória. As classes de exceção descendentes, a seguir, sinalizam outras condições
de erro mais específicas.
323
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
l
EFCreateError. Gerada quando ocorre um erro na criação de um arquivo de stream. Esta exce-
ção normalmente indica que um arquivo não pode ser criado porque o nome de arquivo não é
válido ou está sendo usado por outro processo.
l
EFilerError. Esta exceção é gerada quando você tenta registrar a mesma classe duas vezes usando o procedimento RegisterClasses(). Esta classe também serve como base para outras exce-
ções relacionadas ao arquivador:
l
EClassNotFound.
Esta exceção é gerada quando o Delphi lê o nome de uma classe de componente
de um stream mas não consegue encontrar uma declaração para o componente na unidade correspondente. Lembre-se de que o código e as declarações que não são usadas por um programa
não serão copiadas para o arquivo EXE pelo linkeditor inteligente do Delphi.
l
EInvalidImage. Esta exceção é gerada quando você tenta ler componentes de um arquivo de re-
cursos inválido.
l
EMethodNotFound.
Esta exceção é gerada quando um método especificado no arquivo DFM ou
recurso não existe na unidade correspondente. Isso pode acontecer se você tiver excluído código da unidade, recompilado o EXE, ignorado os muitos avisos sobre o arquivo DFM contendo referências a código deletado e apesar disso, por fim, ainda execute o código EXE.
l
EReadError.
l
EFOpenError. Esta exceção é gerada quando o arquivo de stream especificado não pode ser aber-
Esta exceção ocorre quando sua aplicação não lê o número de bytes de um stream
que deveria (por exemplo, um fim de arquivo inesperado) ou quando o Delphi não consegue
ler uma propriedade.
to; ele normalmente ocorre quando o arquivo não existe.
l
EStringListError.
l
EThreadError.
l
l
Esta é uma exceção genérica que surge quando uma condição de erro aparece
enquanto se trabalha com um objeto TStringList.
Esta é uma exceção relacionada a TThread. Atualmente, esta exceção só é gerada
quando um usuário tenta chamar Synchronize() em um thread à espera.
ETreeViewError. Esta exceção é gerada quando você passa um índice de item inválido para um método ou propriedade de TTreeView.
EWin32Error.
Esta exceção é gerada quando ocorre um erro ao chamar uma função da API do
Win32. A mensagem associada a esta exceção possui informações de código e string de erro.
Erros do sistema do Win32
Quando você encontrar um erro na chamada a uma função ou procedimento da API do Win32, o código
do erro será normalmente obtido pela chamada à função GetLastError(). Como o valor retornado de GetLastError() é um número DWORD, às vezes é difícil combinar esse valor com uma explicação real do que poderia ser o problema. Para ajudá-lo a decifrar melhor os códigos de erro, a Tabela A.1 contém uma lista
dos identificadores de constante e valores para os erros e uma pequena descrição de cada um.
324
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Códigos de erro do Win32
Constante
Valor
Descrição
ERROR_SUCCESS
0
A operação foi completada com sucesso.
ERROR_INVALID_FUNCTION
1
A função está incorreta.
ERROR_FILE_NOT-FOUND
2
O sistema não consegue localizar o arquivo especificado.
ERROR_PATH_NOT_FOUND
3
O sistema não consegue encontrar o caminho especificado.
ERROR_TOO_MANY_OPEN_FILES
4
O sistema não consegue abrir o arquivo.
ERROR_ACCESS_DENIED
5
O acesso foi negado.
ERROR_INVALID_HANDLE
6
A alça é inválida.
ERROR_ARENA_TRASHED
7
Os blocos de controle de armazenamento foram destruídos.
ERROR_NOT_ENOUGH_MEMORY
8
Não há armazenamento suficiente disponível para processar
este comando.
ERROR_INVALID_BLOCK
9
O endereço do bloco de controle de armazenamento é
inválido.
ERROR_BAD_ENVIRONMENT
10
O ambiente está incorreto.
ERROR_BAD_FORMAT
11
Foi feita uma tentativa de carregar um programa com um
formato incorreto.
ERROR_INVALID_ACCESS
12
O código de acesso é inválido.
ERROR_INVALID_DATA
13
Os dados são inválidos.
ERROR_OUTOFMEMORY
14
Não há armazenamento suficiente disponível para
completar esta operação.
ERROR_INVALID_DRIVE
15
O sistema não consegue encontrar a unidade especificada.
ERROR_CURRENT_DIRECTORY
$10
O diretório não pode ser removido.
ERROR_NOT_SAME_DEVICE
17
O sistema não consegue mover o arquivo para uma unidade
de disco diferente.
ERROR_NO_MORE_FILES
18
Não existem mais arquivos.
ERROR_WRITE_PROTECT
19
A mídia está protegida contra gravação.
ERROR_BAD_UNIT
20
O sistema não consegue encontrar o dispositivo
especificado.
ERROR_NOT_READY
21
O dispositivo não está pronto.
ERROR_BAD_COMMAND
22
O dispositivo não reconhece o comando.
ERROR_CRC
23
Ocorreu um erro de dados (CRC).
ERROR_BAD_LENGTH
24
O programa emitiu um comando, mas o tamanho do
comando está incorreto.
ERROR_SEEK
25
A unidade não pode localizar uma área ou faixa específica
no disco.
ERROR_NOT_DOS_DISK
26
O disco ou disquete especificado não pode ser acessado.
ERROR_SECTOR_NOT_FOUND
27
A unidade não consegue localizar o setor solicitado.
ERROR_OUT_OF_PAPER
28
A impressora está sem papel.
ERROR_WRITE_FAULT
29
O sistema não pode escrever no dispositivo especificado.
ERROR_READ_FAULT
30
O sistema não pode ler do dispositivo especificado.
ERROR_GEN_FAILURE
31
Um dispositivo ligado ao sistema não está funcionando.
ERROR_SHARING_VIOLATION
$20
O processo não pode acessar o arquivo porque outro
processo o está utilizando.
325
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
326
Continuação
Constante
Valor
Descrição
ERROR_LOCK_VILATION
33
O processo não pode acessar o arquivo porque outro
processo bloqueou uma parte do arquivo.
ERROR_WRONG_DISK
34
Há um disquete errado na unidade. Insira %2 (Volume
Serial Number: %3) na unidade %1.
ERROR_SHARING_BUFFER_EXCEEDED
36
Muitos arquivos foram abertos para compartilhamento.
ERROR_HANDLE_EOF
39
O final do arquivo foi alcançado.
ERROR_HANDLE_DISK_FULL
39
O disco está cheio.
ERROR_NOT_SUPPORTED
50
O pedido de rede não é aceito.
ERROR_REM_NOT_LIST
51
O computador remoto não está disponível.
ERROR_DUP_NAME
52
Existe um nome duplicado na rede.
ERROR_BAD_NETPATH
53
O caminho da rede não foi encontrado.
ERROR_NETWORK_BUSY
54
A rede está ocupada.
ERROR_DEV_NOT_EXIST
55
O recurso ou dispositivo de rede especificado não está mais
disponível.
ERROR_TOO_MANY_CMDS
56
O limite de comando do BIOS de rede foi atingido.
ERROR_ADAP_HDW_ERR
57
Houve um erro no hardware adaptador de rede.
ERROR_BAD_NET_RESP
58
O servidor especificado não pode realizar a operação
solicitada.
ERROR_UNEXP_NET_ERR
59
Houve um erro inesperado na rede.
ERROR_BAD_REM_ADAP
60
O adaptador remoto não é compatível.
ERROR_PRINTQ_FULL
61
A fila da impressora está cheia.
ERROR_NO_SPOOL_SAPCE
62
Não há espaço disponível no servidor para armazenar o
arquivo aguardando para ser impresso.
ERROR_PRINT_CANCELLED
63
Seu arquivo aguardando para ser impresso foi deletado.
ERROR_NETNAME_DELETED
$40
O nome de rede especificado não está mais disponível.
ERROR__NETWORK_ACCESS_DENIED
65
Acesso negado à rede.
ERROR_BAD_DEV_TYPE
66
O tipo de recurso de rede não está correto.
ERROR_BAD_NET_NAME
67
O nome da rede não foi localizado.
ERROR_TOO_MANY_NAMES
68
O limite de nome para a placa adaptadora de rede do
computador local foi excedido.
ERROR_TOO_MANY_SESS
69
O limite de sessão do BIOS de rede foi excedido.
ERROR_SHARING_PAUSED
70
O servidor remoto foi interrompido ou está em processo de
ser iniciado.
ERROR_REQ_NOT_ACCEP
71
Não podem ser feitas mais conexões a este computador
remoto no momento, pois o computador já trabalha com o
máximo de conexões aceitas.
ERROR_REDIR_PAUSED
72
A impressora ou dispositivo de disco especificado foi
interrompido.
ERROR_FILE_EXISTS
80
O arquivo existe.
ERROR_CANNOT_MAKE
82
O diretório ou arquivo não pode ser criado.
ERROR_FAIL_I24
83
Ocorreu uma falha na INT 24.
ERROR_OUT_OF_STRUCTURES
84
O armazenamento para processar esse pedido não está
disponível.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_OUT_OF_STRUCTURES
85
O nome do dispositivo local já está em uso.
ERROR_INVALID_PASSWORD
86
A senha de rede especificada não está correta.
ERROR_INVALID_PARAMETER
87
O parâmetro está incorreto.
ERROR_NET_WRITE_FAULT
88
Houve um erro de falha de gravação na rede.
ERROR_NO_PROC_SLOTS
89
O sistema não pode iniciar outro processo no momento.
ERROR_TOO_MANY_SEMAPHORES
100
Outro semáforo do sistema não pode ser criado.
ERROR_EXCL_SEM_ALREADY_OWNED
101
O semáforo exclusivo pertence a outro processo.
ERROR_SEM_IS_NET
102
O semáforo está definido e não pode ser fechado.
ERROR_TOO_MANY_SEM_REQUESTS
103
O semáforo não pode ser definido novamente.
ERROR_INVALID_AT_INTERRUPT_TIME
104
Semáforos exclusivos não podem ser solicitados no
momento da interrupção.
ERROR_SEM_OWNER_DIED
105
A propriedade anterior deste semáforo foi encerrada.
ERROR_SEM_USER_LIMIT
106
Insira o disquete na unidade %1.
ERROR_DISK_CHANGE
107
O programa parou porque um disquete alternativo não foi
inserido.
ERROR_DRIVE_LOCKED
108
O disco está em uso ou bloqueado por outro processo.
ERROR_BROKEN_PIPE
109
O pipe foi encerrado.
ERROR_OPEN_FAILED
110
O sistema não pode abrir o dispositivo ou arquivo
especificado.
ERROR_BUFFER_OVERFLOW
111
O nome do arquivo é muito extenso.
ERROR_DISK_FULL
112
O disco não contém espaço suficiente.
ERROR_NO_MORE_SEARCH_HANDLES
113
Não há mais identificadores de arquivo internos à
disposição.
ERROR_INVALID_TARGET_HANDLE
114
O identificador de arquivo interno de destino está
incorreto.
ERROR_INVALID_CATEGORY
117
A chamada IOCTL feita pelo programa aplicativo não está
correta.
ERROR_INVALID_VERIFY_SWITCH
118
O valor do parâmetro-chave de verificação da gravação não
está correto.
ERROR_BAD_DRIVER_LEVEL
119
O sistema não aceita o comando solicitado.
ERROR_CALL_NOT_IMPLEMENTED
120
Esta função só é válida no modo Windows NT.
ERROR_SEM_TIMEOUT
121
O período de tempo decorrido do semáforo foi expirado.
ERROR_INSUFFICIENT_BUFFER
122
A área de dados passada a uma chamada do sistema é muito
pequena.
ERROR_INVALID_NAME
123
A sintaxe do nome de arquivo, nome de diretório ou label
de volume está incorreta.
ERROR_INVALID_LEVEL
124
O nível de chamada do sistema não está correto.
ERROR_NO_VOLUME_LABEL
125
O disco não possui label de volume.
ERROR_MOD_NOT_FOUND
126
O módulo especificado não pôde ser localizado.
ERROR_PROC_NOT_FOUND
127
O procedimento especificado não pôde ser localizado.
ERROR_WAIT_NO_CHILDREN
$80
Não há processos filhos para esperar.
ERROR_CHILD_NOT_COMPLETE
129
A aplicação %1 não pode ser executada no modo Windows
NT.
327
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_DIRECT_ACCESS_HANDLE
130
Foi feita uma tentativa de usar uma alça de arquivo para
uma partição de disco aberta, a fim de realizar uma
operação que não seja o I/O direto no disco.
ERROR_NEGATIVE_SEEK
131
Foi feita uma tentativa para mover o ponteiro do arquivo
para antes do início do arquivo.
ERROR_SEEK_ON_DEVICE
132
O ponteiro do arquivo não pode ser definido para o
dispositivo ou arquivo especificado.
ERROR_IS_JOIN_TARGET
133
Um comando JOIN ou SUBST não pode ser usado para uma
unidade de disco que contenha unidades previamente
associadas.
ERROR_IS_JOINED
134
Tentou-se usar um comando JOIN ou SUBST em uma unidade
que já foi associada.
ERROR_IS_SUBSTED
135
Tentou-se usar um comando JOIN ou SUBST em uma unidade
que já foi substituída.
ERROR_NOT_JOINED
136
O sistema tentou desfazer a associação de uma unidade que
não foi associada.
ERROR_NOT_SUBSTED
137
O sistema tentou desfazer a substituição de uma unidade
que não foi substituída.
ERROR_JOIN_TO_JOIN
138
O sistema tentou associar uma unidade a um diretório de
uma unidade associada.
ERROR_SUBST_TO_SUBST
139
O sistema tentou substituir uma unidade por um diretório
de uma unidade substituída.
ERROR_JOIN_TO_SUBST
140
O sistema tentou associar uma unidade a um diretório em
uma unidade substituída.
ERROR_SUBST_TO_JOIN
141
O sistema tentou realizar um comando SUBST em uma
unidade para um diretório em uma unidade associada.
ERROR_BUSY_DRIVE
142
O sistema não pode realizar uma operação JOIN ou SUBST no
momento.
ERROR_SAME_DRIVE
143
O sistema não pode associar ou substituir uma unidade por
um diretório na mesma unidade.
ERROR_DIR_NOT_ROOT
144
O diretório não é um subdiretório do diretório-raiz.
ERROR_DIR_NOT_EMPTY
145
O diretório não está vazio.
ERROR_IS_SUBST_PATH
146
O caminho especificado está sendo usado em uma
substituição.
ERROR_IS_JOIN_PATH
147
Não há recursos suficientes à disposição para processar este
comando.
ERROR_PATH_BUST
148
O caminho especificado não pode ser usado no momento.
ERROR_IS_SUBST_TARGET
149
Tentou-se associar ou substituir uma unidade para a qual
um diretório na unidade é o destino de uma substituição
anterior.
ERROR_SYSTEM_TRAGE
150
Informações de ‘trace’ do sistema não foram especificadas
no seu arquivo CONFIG.SYS ou então o tracing está
desativado.
ERROR_INVALID_EVENT_COUNT
151
O número de eventos de semáforo especificados para
DosMuxSemWait não está correto.
328
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_TOO_MANY_MAYXWAITERS
152
ERROR_INVALID_LIST_FORMAT
153
A lista de DosMuxSemWait não está correta.
ERROR_LABEL_TOO_LONG
154
O label de volume que você incluiu excede o limite de 11
caracteres. Os 11 primeiros caracteres foram gravados no
disco. Quaisquer caracteres que excederem o limite de 11
caracteres foram automaticamente deletados.
ERROR_TOO_MANY_TCBS
155
Não é possível criar outro thread.
ERROR_SIGNAL_REFUSED
156
O processo destinatário foi recusou o sinal.
ERROR_DISCARDED
157
O segmento já foi descartado e não pode ser bloqueado.
ERROR_NOT_LOCKED
158
O segmento já foi desbloqueado.
ERROR_BAD_THEREADID_ADDR
159
O endereço para o ID do thread não está correto.
ERROR_BAD_ARGUMMENTS
160
A string de argumento passada para DosExecPgm não está
correta.
ERROR_BAD_PATHNAME
161
O caminho especificado não é válido.
ERROR_SIGNAL_PENDING
162
Um sinal já está pendente.
ERROR_MAX_THDS_REACHED
164
Não há mais threads para serem criados no sistema.
ERROR_LOCK_FAILED
167
Uma região de um arquivo não pode ser bloqueada.
ERROR_BUSY
170
O recurso solicitado está em uso.
ERROR_CANCEL_VIOLATION
173
Um pedido de bloqueio não estava pendente para a região
de cancelamento fornecida.
ERROR_ATOMIC_LOCKS_NOT_SUPPORTED
174
O sistema de arquivo não aceita alterações atômicas no tipo
de bloqueio.
ERROR_INVALID_SEGMENT_NUMBER
180
O sistema detectou um número de segmento que não fosse
correto.
ERROR_INVALID_ORDINAL
182
O sistema operacional não pode executar %1.
ERROR_ALREADY_EXISTS
183
Impossível criar um arquivo quando esse arquivo já existe.
ERROR_INVALID_FLAG_NUMBER
186
O flag passado não está correto.
ERROR_SEM_NOT_FOUND
187
O nome do semáforo do sistema especificado não foi
localizado.
ERROR_INVALID_STARTING_CODESEG
188
O sistema operacional não pode executar %1.
ERROR_INVALID_STACKSEG
189
O sistema operacional não pode executar %1.
ERROR_INVALID_MODULETYPE
190
O sistema operacional não pode executar %1.
ERROR_INVALID_EXE_SIGNATURE
191
O Modo Windows NT não pode executar %1.
ERROR_EXE_MARKED_INVALID
192
O sistema operacional não pode executar %1.
ERROR_BAD_EXE_FORMAT
193
%1 não é uma aplicação válida para o Windows NT.
ERROR_ITERATED_DATA_EXCEEDS_64K
194
O sistema operacional não pode executar %1.
ERROR_INVALID_MINALLOCSIZE
195
O sistema operacional não pode executar %1.
ERROR_DYNLINK_FROM_INVALID_RING
196
O sistema operacional não pode executar esta aplicação.
ERROR_IOPL_NOT_ENABLED
197
O sistema operacional não está configurado atualmente
para executar esta aplicação.
ERROR_INVALID_SEGDPL
198
O sistema operacional não pode executar %1.
ERROR_AUTODATASEG_EXCEEDS_64K
199
O sistema operacional não pode executar esta aplicação.
DosMuxSemWait não foi executado; muitos semáforos já
estão definidos.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
329
Tabela A.1
330
Continuação
Constante
Valor
Descrição
ERROR_RING2SEG_MUST_BE_MOVABLE
200
O segmento de código não pode ser maior ou igual a 64KB.
ERROR_RELOC_CHAIN_XEEDS_SEGLIM
201
O sistema operacional não pode executar %1.
ERROR_INFLOOP_IN_RELOC_CHAIN
202
O sistema operacional não pode executar %1.
ERROR_ENVVAR_NOT_FOUND
203
O sistema não pôde localizar a opção de ambiente que foi
indicada.
ERROR_NO_SIGNAL_SENT
205
Nenhum processo na sub-árvore de comandos possui um
manipulador de sinais.
ERROR_FILENAME_EXCED_RANGE
206
O nome de arquivo ou a extensão são muito longos.
ERROR_RING2_STACK_IN_USE
207
A pilha do anel 2 está em uso.
ERROR_META_EXPANSION_TOO_LONG
208
Os caracteres de nome de arquivo globais (como * e ?)
foram incluídos incorretamente ou muitos caracteres
globais foram especificados.
ERROR_INVALID_SIGNAL_NUMBER
209
O sinal sendo postado não está correto.
ERROR_THREAD_1_INATIVE
210
O manipulador de sinais não pode ser definido.
ERROR_LOCKED
212
O segmento está bloqueado e não pode ser realocado.
ERROR_TOO_MANY-MODULES
214
Muitos módulos de vínculo dinâmico estão vinculados.
ERROR_NESTING_NOT_ALLOWED
215
As chamadas não podem ser aninhadas em LoadModule.
ERROR_BAD_PIPE
230
O estado do pipe é inválido.
ERROR_PIPE_BUSY
231
Todas as instâncias de pipe estão ocupadas.
ERROR_NO_DATA
232
O pipe está sendo fechado.
ERROR_PIPE_NOT_CONNECTED
233
Não existe outro processo no outro lado do pipe.
ERROR_MORE_DATA
234
Mais dados estão disponíveis.
ERROR_VC_DISCONNECTED
240
A sessão foi cancelada.
ERROR_INVALID_EA_NAME
254
O nome do atributo estendido especificado foi inválido.
ERROR_EA_LIST_INCONSISTENT
255
Os atributos estendidos são incoerentes.
ERROR_NO_MORE_ITEMS
259
Não há mais dados à disposição.
ERROR_CANNOT_COPY
266
A API Copy não pode ser usada.
ERROR_DIRECTORY
267
O nome do diretório é inválido.
ERROR_EAS_DIDNT_FIT
275
Os atributos estendidos não couberam no buffer.
ERROR_EA_FILE_CORRUPT
276
O arquivo de atributo estendido no sistema de arquivos
montado está danificado.
ERROR_EA_TABLE_FULL
277
O arquivo de tabela de atributos estendidos está cheio.
ERROR_INVALID_EA_HANDLE
278
A alça de atributo estendido especificada é inválida.
ERROR_EAS_NOT_SUPPORTED
282
O sistema de arquivos montado não aceita atributos
estendidos.
ERROR_NOT_OWNER
288
Tentou-se liberar um mutex não possuído por quem
chamou.
ERROR_TOO_MANY_POSTS
298
Muitas postagens foram feitas a um semáforo.
ERROR_PARTIAL_COPY
299
Somente parte do pedido Read/Write ProcessMemory foi
completada.
ERROR_MR_MID_NOT_FOUND
317
O sistema não pode localizar uma mensagem para o
número de mensagem $%1 no arquivo de mensagem
para %2.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_INVALID_ADDRESS
487
Tentativa inválida de acessar endereço.
ERROR_ARITHMETIC_OVERFLOW
534
A operação foi completada com sucesso.
ERROR_PIPE_CONNECTED
535
Um processo está no outro extremo do pipe.
ERROR_PIPE_LISTENING
536
Um pipe está esperando por um processo para abrir o outro
extremo do pipe.
ERROR_EA_ACCESS_DENIED
994
O acesso ao atributo estendido foi negado.
ERROR_OPERATION_ABORTED
995
A operação de I/O foi abortada por causa de uma saída de
thread ou um pedido da aplicação.
ERROR_IO_INCOMPLETE
996
O evento de I/O não está em um estado sinalizado.
ERROR_IO_PENDING
997
O evento de I/O superposto está em andamento.
ERROR_NOACCESS
998
O acesso ao local da memória é inválido.
ERROR_SWAPERROR
999
Ocorreu um erro na operação de página.
ERROR_STACK_OVERFLOW
1001
A recursão está muito profunda e a pilha estourou.
ERROR_INVALID_MESSAGE
1002
A janela não pode atuar em uma mensagem enviada.
ERROR_CAN_NOT_COMPLETE
1003
Esta função não pode ser completada.
ERROR_INVALID_FLAGS
1004
Flags são inválidos.
ERROR_UNRECOGNIZED_VOLUME
1005
O volume não contém um sistema de arquivos reconhecido.
Certifique-se de que todos os drivers do sistema de arquivo
exibidos estão carregados e que o volume não está
danificado.
ERROR_FILE_INVALID
1006
O volume para um arquivo foi alterado externamente, de
modo que o arquivo aberto não é mais válido.
ERROR_FULLSCREEN_MODE
1007
A operação solicitada não pode ser realizada em modo de
tela cheia.
ERROR_NO_TOKEN
1008
Foi feita uma tentativa de referenciar um token que não
existe.
ERROR_BADDB
1009
O banco de dados do Registro de configuração foi
modificado.
ERROR_BADKEY
1010
A chave do Registro de configuração é inválida.
ERROR_CANTOPEN
1011
A chave do Registro de configuração não pôde ser aberta.
ERROR_CANTREAD
1012
A chave do Registro de configuração não pôde ser lida.
ERROR_CANTWRITE
1013
A chave do Registro de configuração não pôde ser gravada.
ERROR_REGISTRY_RECOVERED
1014
Um dos arquivos do banco de dados do Registro teve de ser
recuperado pelo uso de uma cópia alternativa ou log. A
recuperação teve sucesso.
ERROR_REGISTRY_CORRUPT
1015
O Registro está danificado. A estrutura de um dos arquivos
que contém dados do Registro está danificada, a imagem do
arquivo no sistema está danificada na memória ou o
arquivo não pôde ser recuperado porque uma cópia
alternativa ou log estava ausente ou danificada.
ERROR_REGISTRY_IO_FAILED
1016
Uma operação de I/O iniciada pelo Registro falhou e não
pôde ser recuperada. O Registro não pôde ler, gravar ou
dar um flush em um dos arquivos que contêm a imagem do
Registro no sistema.
331
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
332
Continuação
Constante
Valor
Descrição
ERROR_NOT_REGISTRY_FILE
1017
O sistema tentou carregar ou restaurar um arquivo para o
Registro, mas o arquivo especificado não está em um
formato de arquivo de Registro.
ERROR_KEY_DELETED
1018
Tentou-se realizar uma operação ilegal em uma chave do
Registro marcada para exclusão.
ERROR_NO_LOG_SPACE
1019
O sistema não conseguiu alocar o espaço necessário em um
log do Registro.
ERROR_KEY_HAS_CHILDREN
1020
Um vínculo simbólico não pôde ser criado em uma chave
do Registro que já possui subchaves ou valores.
ERROR_CHILD_MUST_BE_VOLATILE
1021
Uma subchave estável sob uma chave pai volátil não pôde
ser criada.
ERROR_NOTIFY_ENUM_DIR
1022
Um pedido de “notificação de mudança” está sendo
completado, e as informações não estão sendo retornadas
no buffer de quem chamou. Quem chamou agora precisa
enumerar os arquivos para localizar as mudanças.
ERROR_DEPENDENT_SERVICES_RUNNING
1051
Um comando de parada foi enviado a um serviço do qual
outros serviços depende.
ERROR_INVALID_SERVICE_CONTROL
1052
O controle solicitado não é válido para este serviço.
ERROR_SERVICE_REQUEST_TIMEOUT
1053
O serviço não respondeu ao pedido de iniciar ou controle
em tempo.
ERROR_SERVICE_NO_THREAD
1054
Um thread não pôde ser criado para o dispositivo.
ERROR_SERVICE_DATABASE_LOCKED
1055
O banco de dados de serviço está bloqueado.
ERROR_SERVICE_ALREADY_RUNNING
1056
Uma instância do serviço já está rodando.
ERROR_INVALID_SERVICE_ACCOUNT
1057
O nome de conta é inválido ou não existe.
ERROR_SERVICE_DISABLED
1058
O serviço especificado está desativado e não pode ser
iniciado.
ERROR_CIRCULAR_DEPENDENCY
1059
Uma dependência de serviço circular foi especificada.
ERROR_SERVICE_DOES_NOT_EXIST
1060
O serviço especificado não existe como um serviço
instalado.
ERROR_SERVICE_CANNOT_ACCEPT_CTRL
1061
O serviço não pode aceitar mensagens de controle no
momento.
ERROR_SERVICE_NOT_ACTIVE
1062
O serviço não foi iniciado.
ERROR_FAILED_SERVICE_CONTROLLER
1063
O processo do serviço não pôde se conectar ao controlador
do serviço.
ERROR_EXCEPTION_IN_SERVICE
1064
Houve uma exceção no serviço quando tratava do pedido
de controle.
ERROR_DATABASE_DOES_NOT_EXIST
1065
O banco de dados especificado não existe.
ERROR_SERVICE_SPECIFIC_ERROR
1066
O serviço retornou um código de erro específico do
serviço.
ERROR_PROCESS_ABORTED
1067
O processo terminou inesperadamente.
ERROR_SERVICE_DEPENDENCY_FAIL
1068
O serviço ou grupo de dependência falhou ao tentar
iniciar.
ERROR_SERVICE_LOGON_FAILED
1069
O serviço não iniciou devido a uma falha de login.
ERROR_SERVICE_START_HANG
1070
Depois de iniciar, o serviço travou em um estado de
pendência de partida.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_INVALID_SERVICE_LOCK
1071
O bloqueio de banco de dados de serviço especificado é
inválido.
ERROR_SERVICE_MARKED_FOR_DELETE
1072
O serviço especificado foi marcado para exclusão.
ERROR_SERVICE_EXISTS
1073
O serviço especificado já existe.
ERROR_ALREADY_RUNNING_LKG
1074
O sistema está atualmente rodando com a última
configuração conhecida.
ERROR_SERVICE_DEPENDENCY_DELETED
1075
O serviço de dependência não existe ou foi marcado para
exclusão.
ERROR_BOOT_ALREADY_ACCEPTED
1076
O boot atual já foi aceito para uso como último conjunto
de controle sabidamente bom.
ERROR_SERVICE_NEVER_STARTED
1077
Nenhuma tentativa de iniciar o serviço foi feita desde o
último boot.
ERROR_DUPLICATE_SERVICE_NAME
1078
O nome já está em uso como um nome de serviço ou como
um nome de exibição do serviço.
ERROR_END_OF_MEDIA
1100
O final físico da fita foi atingido.
ERROR_FILEMARK_DETECTED
1101
Um acesso por fita atingiu uma marca de arquivo.
ERROR_BEGINNING_OF_MEDIA
1102
Foi localizado o início da fita ou partição.
ERROR_SETMARK_DETECTED
1103
Um acesso por fita atingiu o final do conjunto de arquivos.
ERROR_NO_DATA_DETECTED
1104
Não há mais dados na fita.
ERROR_PARTITION_FAILURE
1105
A fita não pôde ser particionada.
ERROR_INVALID_BLOCK_LENGTH
1106
Durante o acesso de uma nova fita em uma partição
multivolumes, o tamanho de bloco atual está incorreto.
ERROR_DEVICE_NOT_PARTITIONED
1107
A informação de partição de fita não pôde ser encontrada
quando a fita foi carregada.
ERROR_UNABLE_TO_LOCK_MEDIA
1108
O mecanismo de ejeção de mídia não pôde ser bloqueado.
ERROR_UNABLE_TO_UNLOAD_MEDIA
1109
A mídia não pôde ser carregada.
ERROR_MEDIA_CHANGED
1110
A mídia na unidade pode ter alterado.
ERROR_BUS_RESET
1111
O bus de I/O foi reinicializado.
ERROR_NO_MEDIA_IN_DRIVE
1112
Nenhuma mídia estava na unidade.
ERROR_NO_UNICODE_TRANSLATION
1113
Não existe mapeamento para o caractere Unicode na
página de código multibyte de destino.
ERROR_DLL_INIT_FAILED
1114
Uma rotina de inicialização da DLL falhou.
ERROR_SHUTDOWN_IN_PROGRESS
1115
Um encerramento do sistema está em andamento.
ERROR_NO_SHUTDOWN_IN_PROGRESS
1116
O encerramento do sistema não pôde ser abortado porque
nenhum encerramento estava sendo realizado.
ERROR_IO_DEVICE
1117
O pedido não pôde ser atendido devido a um erro de
dispositivo de I/O.
ERROR_SERIAL_NO_DEVICE
1118
Nenhum dispositivo serial foi inicializado com sucesso.
O driver serial será descarregado.
ERROR_IRQ_BUSY
1119
Um dispositivo não pôde ser aberto, pois estava
compartilhando um pedido de interrupção (IRQ) com
outros dispositivos. Pelo menos um outro dispositivo que
usa essa IRQ foi aberto com sucesso.
333
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
334
Continuação
Constante
Valor
Descrição
ERROR_MORE_WRITES
1120
Uma operação de I/O serial foi completada por outra
gravação na porta serial. (IOCTL_SERIAL_XOFF_COUNT atingiu
zero.)
ERROR_COUNTER_TIMEOUT
1121
Uma operação de I/O serial foi completada porque o
período limite de tempo foi expirado.
(IOCTL_SERIAL_XOFF_COUNT não atingiu zero.)
ERROR_FLOPPY_ID_MARK_NOT_FOUND
1122
Nenhuma marca de endereço de ID foi encontrada no
disquete.
ERROR_FLOPPY_WRONG_CYLINDER
1123
Houve uma divergência entre o campo de ID de setor do
disquete e o endereço de trilha do controlador de disquete.
ERROR_FLOPPY_UNKNOWN_ERROR
1124
O controlador de disquete informou um erro de que o
driver de disquete não reconhece.
ERROR_FLOPPY_BAD_REGISTERS
1125
O controlador de disquete retornou resultados incoerentes
em seus registradores.
ERROR_DISK_RECALIBRATE_FAILED
1126
Durante um acesso ao disco rígido, uma operação
recalibrável falhou, mesmo depois de novas tentativas.
ERROR_DISK_OPERATION_FAILED
1127
Durante um acesso ao disco rígido, uma operação de disco
falhou, mesmo depois de novas tentativas.
ERROR_DISK_RESET_FAILED
1128
Durante um acesso ao disco rígido, foi preciso ressetar o
controlador de disco, mas até mesmo isso falhou.
ERROR_EOM_OVERFLOW
1129
Foi alcançado o final físico da fita.
ERROR_NOT_ENOUGH_SERVER_MEMORY
1130
Não há armazenamento disponível no servidor para
processar este comando.
ERROR_POSSIBLE_DEADLOCK
1131
Uma condição de impasse (deadlock) em potencial foi
detectada.
ERROR_MAPPED_ALIGNMENT
1132
O endereço de base ou o offset do arquivo especificado não
possui o alinhamento apropriado.
ERROR_SET_POWER_STATE_VETOED
1140
Uma tentativa de mudar o estado de energia do sistema foi
vetada por outra aplicação ou driver.
ERROR_SET_POWER_STATE_FAILED
1141
O BIOS do sistema falhou na tentativa de mudar o estado
de energia do sistema.
ERROR_OLD_WIN_VERSION
1150
O programa especificado requer uma versão mais nova do
Windows.
ERROR_APP_WRONG_OS
1151
O programa especificado não é um programa do Windows
ou do MS-DOS.
ERROR_SINGLE_INSTANCE_APP
1152
Você não pode iniciar mais de uma instância do programa
especificado.
ERROR_RMODE_APP
1153
Você não pode iniciar mais de uma instância do programa
especificado.
ERROR_INVALID_DLL
1154
Um dos arquivos de biblioteca necessários para rodar esta
aplicação está danificado.
ERROR_NO_ASSOCIATION
1155
Nenhuma aplicação está associada ao arquivo especificado
para esta operação.
ERROR_DDE_FAIL
1156
Houve um erro no envio do comando para a aplicação.
ERROR_DLL_NOT_FOUND
1157
Um dos arquivos de biblioteca necessários para rodar esta
aplicação não pôde ser localizado.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_BAD_USERNAME
2202
O nome de usuário especificado é inválido.
ERROR_NOT_CONNECTED
2250
Esta conexão da rede não existe.
ERROR_OPEN_FILES
2401
Esta conexão da rede possui arquivos abertos ou pedidos
pendentes.
ERROR_ACTIVE_CONNECTIONS
2402
Ainda existem conexões ativas.
ERROR_DEVICE_IN_USE
2404
O dispositivo está em uso por um processo ativo e não
pode ser desconectado.
ERROR_BAD_DEVICE
1200
O nome de dispositivo específico é inválido.
ERROR_CONNECTION_UNAVAIL
1201
O dispositivo não está atualmente conectado, mas é uma
conexão relembrada.
ERROR_DEVICE_ALREADY_REMEMBERED
1202
Tentou-se relembrar de um dispositivo que tinha sido
relembrado anteriormente.
ERROR_NO_NET_OR_BAD_PATH
1203
Nenhum provedor de rede aceitou o caminho de rede
indicado.
ERROR_BAD_PROVIDER
1204
O nome do provedor de rede especificado é inválido.
ERROR_CANNOT_OPEN_PROFILE
1205
O perfil da conexão de rede não pôde ser aberto.
ERROR_BAD_PROFILE
1206
O perfil da conexão de rede está danificado.
ERROR_NOT_CONTAINER
1207
Um não-container não pode ser enumerado.
ERROR_EXTENDED_ERROR
1208
Houve um erro estendido.
ERROR_INVALID_GROUPNAME
1209
O formato do nome de grupo especificado é inválido.
ERROR_INVALID_COMPUTERNAME
1210
O formato do nome de computador especificado é inválido.
ERROR_INVALID_EVENTNAME
1211
O formato do nome de evento especificado é inválido.
ERROR_INVALID_DOMAINNAME
1212
O formato do nome de domínio especificado é inválido.
ERROR_INVALID_SERVICENAME
1213
O formato do nome de serviço especificado é inválido.
ERROR_INVALID_NETNAME
1214
O formato do nome de rede especificado é inválido.
ERROR_INVALID_SHARENAME
1215
O formato do nome de share especificado é inválido.
ERROR_INVALID_PASSWORDNAME
1216
O formato do nome de senha especificado é inválido.
ERROR_INVALID_MESSAGENAME
1217
O formato do nome de mensagem especificado é inválido.
ERROR_INVALID_MESSAGEDEST
1218
O formato do nome de destino de mensagem especificado é
inválido.
ERROR_SESSION_CREDENTIAL_CONFLIC
1219
As credenciais fornecidas estão em conflito com um
conjunto de credenciais existente.
ERROR_REMOTE_SESSION_LIMIT_EXCEE
1220
Tentou-se estabelecer uma sessão com um servidor da rede,
mas muitas sessões já estão estabelecidas nesse servidor.
ERROR_DUP_DOMAINNAME
1221
Outro computador da rede já está usando o nome do grupo
de trabalho ou domínio.
ERROR_NO_NETWORK
1222
A rede não está presente ou não foi iniciada.
ERROR_CANCELLED
1223
O usuário cancelou a operação.
ERROR_USER_MAPPED_FILE
1224
A operação solicitada não pode ser realizada em um
arquivo com uma seção mapeada pelo usuário aberta.
ERROR_CONNECTION_REFUSED
1225
O sistema remoto recusou a conexão da rede.
ERROR_GRACEFUL_DISCONNECT
1226
A conexão da rede foi fechada corretamente.
335
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
336
Continuação
Constante
Valor
Descrição
ERROR_ADDRESS_ALREADY_ASSOCIATED
1227
O ponto final de transporte da rede já possui um endereço
associado a ele.
ERROR_ADDRESS_NOT_ASSOCIATED
1228
Um endereço ainda não foi associado ao ponto final da
rede.
ERROR_CONNECTION_INVALID
1229
Tentou-se realizar uma operação sobre uma conexão de
rede não existente.
ERROR_CONNECTION_ACTIVE
1230
Tentou-se realizar uma operação sobre uma conexão de
rede ativa.
ERROR_NETWORK_UNREACHABLE
1231
O transporte não pode atingir a rede remota.
ERROR_HOST_UNREACHABLE
1232
O transporte não pode atingir o sistema remoto.
ERROR_PROTOCOL_UNREACHABLE
1233
O protocolo de transporte não pode atingir o sistema
remoto.
ERROR_PORT_UNREACHABLE
1234
Nenhum serviço está operando no ponto final da rede de
destino no sistema remoto.
ERROR_REQUEST_ABORTED
1235
O pedido foi abortado.
ERROR_CONNECTION_ABORTED
1236
O sistema local abortou a conexão da rede.
ERROR_RETRY
1237
A operação não pôde ser completada. Tentou-se realizar
nova tentativa.
ERROR_CONNECTION_COUNT_LIMIT
1238
Uma conexão com o servidor não pôde ser feita porque o
limite no número de conexões concorrentes para esta conta
foi atingido.
ERROR_LOGIN_TIME_RESTRICTION
1239
Tentou-se conectar durante um horário não autorizado do
dia para esta conta.
ERROR_LOGIN_WKSTA_RESTRICTION
1240
A conta não está autorizada a se conectar a partir desta
estação.
ERROR_INCORRECT_ADDRESS
1241
O endereço da rede não pôde ser usado para a operação
solicitada.
ERROR_ALREADY_REGISTERED
1242
O serviço já está registrado.
ERROR_SERVICE_NOT_FOUND
1243
O serviço especificado não existe.
ERROR_NOT_AUTHENTICATED
1244
A operação sendo solicitada não foi realizada porque o
usuário não havia sido autenticado.
ERROR_NOT_LOGGED_ON
1245
A operação sendo solicitada não foi realizada porque o
usuário não foi conectado à rede. O serviço especificado
não existe.
ERROR_CONTINUE
1246
Este é um retorno que deseja que quem chamou continue
com o trabalho em progresso.
ERROR_ALREADY_INITIALIZED
1247
Tentou-se realizar uma operação de inicialização quando a
inicialização já foi completada.
ERROR_NO_MORE_DEVICES
1248
Não existem mais dispositivos locais.
ERROR_NOT_ALL_ASSIGNED
1300
Nem todos os privilégios referenciados são atribuídos a
quem chamou.
ERROR_SOME_NOT_MAPPED
1301
Algum mapeamento entre nomes de conta e IDs de
segurança não foi realizado.
ERROR_NO_QUOTAS_FOR_ACCOUNT
1302
Nenhum limite de cota do sistema foi definido
especificamente para esta conta.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_LOCAL_USER_SESSION_KEY
1303
Nenhuma chave de criptografia está disponível. Uma chave
de criptografia bem conhecida foi retornada.
ERROR_NULL_LM_PASSWORD
1304
A senha do NT é muito complexa para ser convertida para
uma senha do LAN Manager. A senha do LAN Manager
retornada é uma string nula.
ERROR_UNKNOWN_REVISION
1305
O nível de revisão é desconhecido.
ERROR_REVISION_MISMATCH
1306
Os dois níveis de revisão são incompatíveis.
ERROR_INVALID_OWNER
1307
O ID de segurança pode não estar atribuído como
proprietário deste objeto.
ERROR_INVALID_PRIMARY_GROUP
1308
Este ID de segurança pode não estar atribuído como grupo
primário de um objeto.
ERROR_NO_IMPERSONATION_TOKEN
1309
Um thread que não está atualmente personificando um
ciente tentou operar sobre um token de personificação.
ERROR_CANT_DISABLE_MANDATORY
1310
O grupo não pode ser desativado.
ERROR_NO_LOGON_SERVERS
1311
Nenhum servidor de logon está disponível atualmente para
atender ao pedido de logon.
ERROR_NO_SUCH_LOGON_SESSION
1312
Uma sessão de logon especificada não existe. Ela já pode ter
sido terminada.
ERROR_NO_SUCH_PRIVILEGE
1313
Um privilégio especificado não existe.
ERROR_PRIVILEGE_NOT_HELD
1314
Um privilégio solicitado não é mantido pelo cliente.
ERROR_INVALID_ACCOUNT_NAME
1315
O nome fornecido não é um nome de conta formado
corretamente.
ERROR_USER_EXISTS
1316
O usuário especificado já existe.
ERROR_NO_SUCH_USER
1317
O usuário especificado não existe.
ERROR_GROUP_EXISTS
1318
O grupo especificado já existe.
ERROR_NO_SUCH_GROUP
1319
O grupo especificado não existe.
ERROR_MEMBER_IN_GROUP
1320
A conta de usuário especificada já é membro do grupo
especificado ou o grupo especificado não pode ser deletado
porque contém um membro.
ERROR_MEMBER_NOT_IN_GROUP
1321
A conta de usuário especificada não é membro da conta de
grupo especificada.
ERROR_LAST_ADMIN
1322
A última conta de administração restante não pode ser
desativada ou deletada.
ERROR_WRONG_PASSWORD
1323
A senha não pode ser atualizada. O valor fornecido como
senha atual está incorreto.
ERROR_ILL_FORMED_PASSWORD
1324
A senha não pode ser atualizada. O valor fornecido para a
nova senha contém valores que não são permitidos em
senhas.
ERROR_PASSWORD_RESTRICTION
1325
A senha não pode ser atualizada porque uma regra de
atualização de senha foi violada.
ERROR_LOGON_FAILURE
1326
Ocorreu uma falha de logon: nome de usuário
desconhecido ou senha incorreta.
ERROR_ACCOUNT_RESTRICTION
1327
Ocorreu uma falha de logon: restrição de conta do usuário.
ERROR_INVALID_LOGON_HOURS
1328
Ocorreu uma falha de logon: violação de restrição de
tempo de logon da conta.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
337
Tabela A.1
338
Continuação
Constante
Valor
Descrição
ERROR_INVALID_WORKSTATION
1329
Ocorreu uma falha de logon: o usuário não tem permissão
para efetuar logon neste computador.
ERROR_PASSWORD_EXPIRED
1330
Ocorreu uma falha de logon: a senha da conta especificada
foi expirada.
ERROR_ACCOUNT_DISABLED
1331
Ocorreu uma falha de logon: a conta está desativada
atualmente.
ERROR_NONE_MAPPED
1332
Não houve mapeamento entre nomes de conta e IDs de
segurança.
ERROR_TOO_MANY_LUIDS_REQUESTED
1333
Muitos identificadores de usuário local (LUIDs) foram
solicitados ao mesmo tempo.
ERROR_LUIDS_EXHAUSTED
1334
Não há mais identificadores de usuário local (LUIDs)
disponíveis.
ERROR_INVALID_SUB_AUTHORITY
1335
A parte de subautoridade de um ID de segurança é inválida
para este uso em particular.
ERROR_INVALID_ACL
1336
A estrutura Access Control List (ACL) é inválida.
ERROR_INVALID_SID
1337
A estrutura do ID de segurança é inválida.
ERROR_INVALID_SECURITY_DESCR
1338
A estrutura do descritor de segurança é inválida.
ERROR_BAD_INHERITANCE_ACL
1340
A Access Control List (ACL) ou a Acces Control Entry
(ACE) herdada não pôde ser montada.
ERROR_SERVER_DISABLED
1341
O servidor está desativado atualmente.
ERROR_SERVER_NOT_DISABLED
1342
O servidor está ativado atualmente.
ERROR_INVALID_ID_AUTHORITY
1343
O valor fornecido foi inválido para uma autoridade de
identificação.
ERROR_ALLOTTED_SPACE_EXCEEDED
1344
Não há mais memória disponível para atualizações de
informações de segurança.
ERROR_INVALID_GROUP_ATTRIBUTES
1345
Os atributos especificados são inválidos ou incompatíveis
com os atributos para o grupo como um todo.
ERROR_BAD_IMPERSONATION_LEVEL
1346
Um nível de personificação exigido não foi fornecido ou o
nível fornecido é inválido.
ERROR_CANT_OPEN_ANONYMOUS
1347
Um token de segurança de nível anônimo não pode ser
aberto.
ERROR_BAD_VALIDATION_CLASS
1348
A classe de informações de validação solicitada foi inválida.
ERROR_BAD_TOKEN_TYPE
1349
O tipo de token é impróprio para esse uso intencionado.
ERROR_NO_SECURITY_ON_OBJECT
1350
Uma operação de segurança não pôde ser realizada sobre
um objeto que não possui segurança associada.
ERROR_CANT_ACCESS_DOMAIN_INFO
1351
Um servidor do Windows NT não pôde ser contatado ou
objetos dentro do domínio são protegidos, de modo que as
informações necessárias não puderam ser apanhadas.
ERROR_INVALID_SERVER_STATE
1352
O servidor Security Account Manager (SAM) ou Local
Security Authority (LSA) estava no estado errado para
realizar a operação de segurança.
ERROR_INVALID_DOMAIN_STATE
1353
O domínio estava no estado errado para realizar a operação
de segurança.
ERROR_INVALID_DOMAIN_ROLE
1354
Essa operação só é permitida para o Primary Domain
Controller do domínio.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_NO_SUCH_DOMAIN
1355
O domínio especificado não existia.
ERROR_DOMAIN_EXISTS
1356
O domínio especificado já existe.
ERROR_DOMAIN_LIMIT_EXCEEDED
1357
Tentou-se exceder o limite do número de domínios por
servidor.
ERROR_INTERNAL_DB_CORRUPTION
1358
A operação solicitada não pôde ser completada por causa
de uma falha catastrófica na mídia ou por danos na
estrutura dos dados no disco.
ERROR_INTERNAL_ERROR
1359
O banco de dados de conta de segurança contém uma
incoerência interna.
ERROR_GENERIC_NOT_MAPPED
1360
Tipos de acesso genéricos estavam contidos em uma
máscara de acesso que já deveria estar mapeada para tipos
não genéricos.
ERROR_BAD_DESCRIPTOR_FORMAT
1361
Um descritor de segurança não está no formato correto
(absoluto ou relativo).
ERROR_NOT_LOGON_PROCESS
1362
O uso da ação solicitada é restrito apenas aos processos de
logon. O processo de chamada não foi registrado como um
processo de logon.
ERROR_LOGON_SESSION_EXISTS
1363
Você não pode iniciar uma nova sessão de logon com um
ID que á esteja em uso.
ERROR_NO_SUCH_PACKAGE
1364
Um pacote de autenticação especificado é desconhecido.
ERROR_BAD_LOGON_SESSION_STATE
1365
A sessão de logon não está em um estado não coerente com
a operação solicitada.
ERROR_LOGON_SESSION_COLLISION
1366
O ID da sessão de logon já está em uso.
ERROR_INVALID_LOGON_TYPE
1367
Um pedido de logon continha um valor inválido de tipo de
logon.
ERROR_CANNOT_IMPERSONATE
1368
Você não pode personificar por meio de um named pipe
até que os dados tenham sido lidos desse pipe.
ERROR_RXACT_INVALID_STATE
1369
O estado da transação de uma subárvore do Registro é
incompatível com a operação solicitada.
ERROR_RXACT_COMMIT_FAILURE
1370
Foi encontrado um dano no banco de dados de segurança
interna.
ERROR_SPECIAL_ACCOUNT
1371
Você não pode realizar essa operação em contas internas.
ERROR_SPECIAL_GROUP
1372
Você não pode realizar essa operação neste grupo especial
interno.
ERROR_SPECIAL_USER
1373
Você não pode realizar essa operação neste usuário especial
interno.
ERROR_MEMBERS_PRIMARY_GROUP
1374
O usuário não pode ser removido de um grupo porque o
grupo é atualmente o grupo principal do usuário.
ERROR_TOKEN_ALREADY_IN_USE
1375
O token já está em uso como token primário.
ERROR_NO_SUCH_ALIAS
1376
O grupo local especificado não existe.
ERROR_MEMBER_NOT_IN_ALIAS
1377
O nome de conta especificado não é um membro do grupo
local.
ERROR_MEMBER_IN_ALIAS
1378
O nome de conta especificado já é um membro do grupo
local.
ERROR_ALIAS_EXISTS
1379
O grupo local especificado já existe.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
339
Tabela A.1
340
Continuação
Constante
Valor
Descrição
ERROR_LOGON_NOT_GRANTED
1380
Houve uma falha de logon: o usuário não recebeu o tipo de
logon solicitado neste computador.
ERROR_TOO_MANY_SECRETS
1381
O número máximo de segredos que podem ser
armazenados em um único sistema foi excedido.
ERROR_SECRET_TOO_LONG
1382
O tamanho de um segredo excede o tamanho máximo
permitido.
ERROR_INTERNAL_DB_ERROR
1383
O banco de dados do servidor Local Security Authority
contém uma incoerência interna.
ERROR_TOO_MANY_CONTEXT_IDS
1384
Durante uma tentativa de logon, o contexto de segurança
do usuário acumulou muitos IDs de segurança.
ERROR_LOGON_TYPE_NOT_GRANTED
1385
Houve uma falha no logon: o usuário não recebeu o tipo de
logon solicitado neste computador.
ERROR_NT_CROSS_ENCRYPTION_REQUIR
1386
Uma senha criptografata é necessária para mudar uma
senha do usuário.
ERROR_NO_SUCH_MEMBER
1387
Um novo membro não pôde ser incluído no grupo local
porque o membro não existe.
ERROR_INVALID_MEMBER
1388
O novo membro não pôde ser incluído no grupo local
porque o membro possui o tipo de conta errado.
ERROR_TOO_MANY_SIDS
1389
Muitos IDs de segurança foram especificados.
ERROR_LM_CROSS_ENCRYPTION_REQUIR
1390
Uma senha criptografada é necessária para mudar esta
senha do usuário.
ERROR_NO_INHERITANCE
1391
Indica que um TACL não contém componentes herdáveis.
ERROR_FILE_CORRUPT
1392
O arquivo ou diretório foi danificado e não pode ser lido.
ERROR_DISK_CORRUPT
1393
A estrutura de disco está danificada e é incapaz de ser lida.
ERROR_NO_USER_SESSION_KEY
1394
Não existe uma chave de sessão do usuário para a sessão de
logon especificada.
ERROR_LICENSE_QUOTA_EXCEEDED
1395
O serviço sendo acessado está licenciado para um
determinado número de conexões. Nenhuma outra
conexão pode ser feita ao serviço no momento, pois já
existe o máximo de conexões aceitas.
ERROR_INVALID_WINDOW_HANDLE
1400
A alça da janela é inválida.
ERROR_INVALID_MENU_HANDLE
1401
A alça do menu é inválida.
ERROR_INVALID_CURSOR_HANDLE
1402
A alça do cursor é inválida.
ERROR_INVALID_ACCEL_HANDLE
1403
A alça da tabela aceleradora é inválida.
ERROR_INVALID_HOOK_HANDLE
1404
A alça do hook é inválida.
ERROR_INVALID_DWP_HANDLE
1405
A alça para uma estrutura de posição de várias janelas é
inválida.
ERROR_TLW_WITH_WSCHILD
1406
Uma janela filha de alto nível não pode ser criada.
ERROR_CANNOT_FIND_WND_CLASS
1407
Uma classe de janela não pode ser localizada.
ERROR_WINDOW_OF_OTHER_THREAD
1408
A janela é inválida; ela pertence ao outro thread.
ERROR_HOTKEY_ALREADY_REGISTERED
1409
A tecla de ativação já está registrada.
ERROR_CLASS_ALREADY_EXISTS
1410
A classe já existe.
ERROR_CLASS_DOES_NOT_EXIST
1411
A classe não existe.
ERROR_CLASS_HAS_WINDOWS
1412
A classe ainda possui janelas abertas.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_INVALID_INDEX
1413
O índice é inválido.
ERROR_INVALID_ICON_HANDLE
1414
A alça do ícone é inválida.
ERROR_PRIVATE_DIALOG_INDEX
1415
Você está usando palavras da janela de diálogo privada.
ERROR_LISTBOX_ID_NOT_FOUND
1416
O identificador da caixa de listagem não foi localizado.
ERROR_NO_WILDCARD_CHARACTERS
1417
Nenhum curinga foi encontrado.
ERROR_CLIPBOARD_NOT_OPEN
1418
O thread não possui um Clipboard aberto.
ERROR_HOTKEY_NOT_REGISTERED
1419
A tecla de ativação não está registrada.
ERROR_WINDOW_NOT_DIALOG
1420
A janela não é uma janela de diálogo válida.
ERROR_CONTROL_ID_NOT_FOUND
1421
O ID de controle não foi localizado.
ERROR_INVALID_COMBOBOX_MESSAGE
1422
A mensagem para a caixa de combinação foi inválida
porque não tem um controle de edição.
ERROR_WINDOW_NOT_COMBOBOX
1423
A janela não é uma caixa de combinação.
ERROR_INVALID_EDIT_HEIGHT
1424
A altura deve ser inferior a 256 pixels.
ERROR_DC_NOT_FOUND
1425
A alça de contexto de dispositivo (DC) é inválida.
ERROR_INVALID_HOOK_FILTER
1426
O tipo de procedimento de hook é inválido.
ERROR_INVALID_FILTER_PROC
1427
O procedimento de hook é inválido.
ERROR_HOOK_NEEDS_HMOD
1428
Você não pode definir um hook não local sem uma alça de
módulo.
ERROR_GLOBAL_ONLY_HOOK
1429
Esse procedimento de hook só pode ser definido
globalmente.
ERROR_JOURNAL_HOOK_SET
1430
O procedimento de hook diário já foi instalado.
ERROR_HOOK_NOT_INSTALLED
1431
O procedimento de hook não está instalado.
ERROR_INVALID_LB_MESSAGE
1432
A mensagem para uma caixa de listagem de única seleção é
inválida.
ERROR_SETCOUNT_ON_BAD_LB
1433
LB_SETCOUNT foi definido para uma caixa de listagem não
desocupada.
ERROR_LB_WITHOUT_TABSTOPS
1434
Essa caixa de listagem não aceita paradas de tabulação.
ERROR_DESTROY_OBJECT_OF_OTHER_TH
1435
Você não pode destruir um objeto criado por outro thread.
ERROR_CHILD_WINDOW_MENU
1436
Janelas filhas não podem ter menus.
ERROR_NO_SYSTEM_MENU
1437
A janela não tem um menu do sistema.
ERROR_INVALID_MSGBOX_STYLE
1438
O estilo da caixa de mensagem é inválido.
ERROR_INVALID_SPI_VALUE
1439
O parâmetro do sistema (SPI_*) é inválido.
ERROR_SCREEN_ALREADY_LOCKED
1440
A tela já foi bloqueada.
ERROR_HWNDS_HAVE_DIFF_PARENT
1441
Todas as alças de janelas em uma estrutura de múltiplas
posições de janela devem ter o mesmo pai.
ERROR_NOT_CHILD_WINDOW
1442
A janela não é uma janela filha.
ERROR_INVALID_GW_COMMAND
1443
O comando GW_* é inválido.
ERROR_INVALID_THREAD_ID
1444
O identificador de thread é inválido.
ERROR_NON_MDICHILD_WINDOW
1445
Uma mensagem não pode ser processada a partir da janela
que não seja uma janela de interface de múltiplo
documento (MDI).
ERROR_POPUP_ALREADY_ACTIVE
1446
O menu pop-up já está ativo.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
341
Tabela A.1
342
Continuação
Constante
Valor
Descrição
ERROR_NO_SCROLLBARS
1447
A janela não possui barras de rolagem.
ERROR_INVALID_SCROLLBAR_RANGE
1448
O intervalo da barra de rolagem não pode ser maior do que
$7FFF.
ERROR_INVALID_SHOWWIN_COMMAND
1449
Você não pode exibir ou remover a janela no modo
especificado.
ERROR_EVENTLOG_FILE_CORRUPT
1500
O arquivo de log de evento está danificado.
ERROR_EVENTLOG_CANT_START
1501
Nenhum arquivo de log de evento pôde ser aberto, de
modo que o serviço de log de evento não foi iniciado.
ERROR_LOG_FILE_FULL
1502
O arquivo de log de evento está cheio.
ERROR_EVENTLOG_FILE_CHANGED
1503
O arquivo de log de evento foi alterado entre as leituras.
ERROR_INVALID_USER_BUFFER
1784
O buffer de usuário fornecido não é válido para a operação
solicitada.
ERROR_UNRECOGNIZED_MEDIA
1785
A mídia do disco não foi reconhecida. Ele pode não estar
formatado.
ERROR_NO_TRUST_LSA_SECRET
1786
A estação de trabalho não possui um segredo de
confiança.
ERROR_NO_TRUST_SAM_ACCOUNT
1787
O banco de dados SAM no servidor Windows NT não
possui uma conta de computador para este relacionamento
de confiança da estação de trabalho.
ERROR_TRUSTED_DOMAIN_FAILURE
1788
O relacionamento de confiança entre o domínio primário e
o domínio confiado falhou.
ERROR_TRUSTED_RELATIONSHIP_FAILU
1789
O relacionamento de confiança entre esta estação de
trabalho e o domínio primário falhou.
ERROR_TRUST_FAILURE
1790
O logon da rede falhou.
ERROR_NETLOGON_NOT_STARTED
1792
Uma chamada de procedimento remoto já está em
progresso para este thread. Tentou-se conectar, mas o
serviço de logon da rede não foi iniciado.
ERROR_ACCOUNT_EXPIRED
1793
A conta do usuário expirou.
ERROR_REDIRECTOR_HAS_OPEN_HANDLE
1794
O redirecionador está em uso e não pode ser
descarregado.
ERROR_PRINTER_DRIVER_ALREADY_INS
1795
O driver de impressora especificado já está instalado.
ERROR_UNKNOWN_PORT
1796
A porta especificada é desconhecida.
ERROR_UNKNOWN_PRINTER_DRIVER
1797
O driver de impressora é desconhecido.
ERROR_UNKNOWN_PRINTPROCESSOR
1798
O processador de impressão é desconhecido.
ERROR_INVALID_SEPARATOR_FILE
1799
O arquivo separador especificado é inválido.
ERROR_INVALID_PRIORITY
1800
A prioridade especificada é inválida.
ERROR_INVALID_PRINTER_NAME
1801
O nome da impressora é inválido.
ERROR_PRINTER_ALREADY_EXISTS
1802
A impressora já existe.
ERROR_INVALID_PRINTER_COMMAND
1803
O comando da impressora é inválido.
ERROR_INVALID_DATATYPE
1804
O tipo de dado especificado é inválido.
ERROR_INVALID_ENVIRONMENT
1805
O ambiente especificado é inválido.
ERROR_NOLOGON_INTERDOMAIN_TRUST
1807
Não há mais ligações. A conta usada é uma conta de trust
de interdomínio. Use sua conta de usuário global ou sua
conta de usuário local para acessar este servidor.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_NOLOGON_WORKSTATION_TRUST
1808
A conta usada é uma conta de computador. Use sua conta
de usuário global ou sua conta de usuário local para acessar
este servidor.
ERROR_NOLOGON_SERVER_TRUST_ACCOU
1809
A conta usada é uma conta de trust do servidor. Use sua
conta de usuário global ou sua conta de usuário local para
acessar este servidor.
ERROR_DOMAIN_TRUST_INCONSISTENT
1810
O nome ou código de segurança (SID) do domínio
especificado é incoerente com a informação de trust para
esse domínio.
ERROR_SERVER_HAS_OPEN_HANDLES
1811
O servidor está em uso e não pode ser descarregado.
ERROR_RESOURCE_DATA_NOT_FOUND
1812
O arquivo de imagem especificado não continha uma seção
de recurso.
ERROR_RESOURCE_TYPE_NOT_FOUND
1813
O tipo de recurso especificado não pode ser localizado no
arquivo de imagem.
ERROR_RESOURCE_NAME_NOT_FOUND
1814
O nome do recurso especificado não pode ser localizado no
arquivo de imagem.
ERROR_RESOURCE_LANG_NOT_FOUND
1815
O código de linguagem do recurso especificado não pode
ser localizado no arquivo de imagem.
ERROR_NOT_ENOUGH_QUOTA
1816
Não há cota suficiente disponível para processar este
comando.
ERROR_INVALID_TIME
1901
O horário especificado é inválido.
ERROR_INVALID_FORM_NAME
1902
O nome do formulário especificado é inválido.
ERROR_INVALID_FORM_SIZE
1903
O tamanho de formulário especificado é inválido.
ERROR_ALREADY_WAITING
1904
A alça de impressora especificada já está sendo aguardada.
ERROR_PRINTER_DELETED
1905
A impressora especificada foi deletada.
ERROR_INVALID_PRINTER_STATE
1906
O estado da impressora é inválido.
ERROR_PASSWORD_MUST_CHANGE
1907
O usuário precisa mudar sua senha antes de se conectar
pela primeira vez.
ERROR_DOMAIN_CONTROLLER_NOT_FOUN
1908
O controlador de domínio para este domínio não pôde ser
localizado.
ERROR_ACCOUNT_LOCKED_OUT
1909
A conta referenciada atualmente está bloqueada e pode não
ser registrada.
ERROR_NO_BROWSER_SERVERS_FOUND
6118
A lista de servidores para este grupo de trabalho não está
disponível atualmente.
ERROR_INVALID_PIXEL_FORMAT
2000
O formato de pixel é inválido.
ERROR_BAD_DRIVER
2001
O driver especificado é inválido.
ERROR_INVALID_WINDOW_STYLE
2002
O estilo de janela ou atributo de classe é inválido para esta
operação.
ERROR_METAFILE_NOT_SUPPORTED
2003
A operação de metafile solicitada não é aceita.
ERROR_TRANSFORM_NOT_SUPPORTED
2004
A operação de transformação solicitada não é aceita.
ERROR_CLIPPING_NOT_SUPPORTED
2005
A operação de corte solicitada não é aceita.
ERROR_UNKNOWN_PRINT_MONITOR
3000
O monitor de impressão especificado é desconhecido.
ERROR_PRINTER_DRIVER_IN_USE
3001
O driver de impressora especificado está em uso
atualmente.
343
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela A.1
Continuação
Constante
Valor
Descrição
ERROR_SPOOL_FILE_NOT_FOUND
3002
O arquivo de spool não foi localizado.
ERROR_SPL_NO_STARTDOC
3003
Uma chamada a StartDocPrinter não foi emitida.
ERROR_SPL_NO_ADDJOB
3004
Uma chamada a AddJob não foi emitida.
ERROR_PRINT_PROCESSOR_ALREADY_IN
3005
O processador de impressão especificado já foi instalado.
ERROR_PRINT_MONITOR_ALREADY_INST
3006
O monitor de impressão especificado já foi instalado.
ERROR_WINS_INTERNAL
4000
O WINS encontrou um erro enquanto processava o
comando.
ERROR_CAN_NOT_DEL_LOCAL_WINS
4001
O WINS local não pode ser excluído.
ERROR_STATIC_INIT
4002
A importação do arquivo falhou.
ERROR_INC_BACKUP
4003
O backup falhou. Um backup completo já foi feito?
ERROR_FULL_BACKUP
4004
O backup falhou. Verifique o diretório para o qual está
copiando o banco de dados.
ERROR_REC_NON_EXISTENT
4005
O nome não existe no banco de dados WINS.
ERROR_RPL_NOT_ALLOWED
4006
A replicação com um parceiro não configurado não é
permitida.
344
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Códigos de erro
do BDE
APÊNDICE
B
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO 11 — 2ª PROVA
Ao trabalhar com o Borland Database Engine, ocasionalmente você receberá uma caixa de diálogo de
erro indicando que ocorreu algum erro no mecanismo. Normalmente, isso acontece quando os clientes
instalam o seu software em suas máquinas, mas a máquina possui alguns problemas de configuração e
você está tentando resolver para eles. Normalmente, essa caixa de diálogo de erro oferece um código de
erro hexadecimal como descrição do erro. A questão é como transformar esse número em uma mensagem de erro significativa. Para ajudá-lo nessa tarefa, oferecemos a tabela a seguir. A Tabela B.1 relaciona
todos os códigos de erro possíveis do BDE, além das strings de erro do BDE associadas a esses códigos de
erro.
Tabela B.1
346
Códigos de erro do BDE
Código de Erro
String de erro
Decimal
Hexa
0
0000
Término bem-sucedido.
33
0021
Erro do sistema.
34
0022
Objeto de interesse não-localizado.
35
0023
Danos físicos aos dados.
36
0024
Erro relacionado a I/O.
37
0025
Erro de recurso ou limite.
38
0026
Violação de integridade de dados.
39
0027
Pedido inválido.
40
0028
Violação de bloqueio.
41
0029
Violação de acesso/segurança.
42
002A
Contexto inválido.
43
002B
Erro do sistema operacional.
44
002C
Erro de rede.
45
002D
Parâmetro opcional.
46
002E
Processador de consulta.
47
002F
Divergência de versão.
48
0030
Capacidade não aceita.
49
0031
Erro de configuração do sistema.
50
0032
Advertência.
51
0033
Diversos.
52
0034
Erro de compatibilidade.
62
003E
Erro específico do driver.
63
003F
Símbolo interno.
256
0100
KEYVIOL.
257
0101
PROBLEMS.
258
0102
CHANGED.
512
0200
Arquivo de índice de produção inexistente, danificado ou
não pode interpretar chave de índice.
513
0201
Abrir apenas para leitura.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
514
0202
Abrir a tabela no modo apenas de leitura.
515
0203
Abrir e destacar.
516
0204
Abrir a tabela e destacar o arquivo de índice de produção.
517
0205
Falha na abertura.
518
0206
Não abrir a tabela.
519
0207
Converter índice não-dBASE.
520
0208
Converter índice de produção para formato do dBASE.
521
0209
Arquivo BLOB não localizado.
522
020
A
523
020B
Abrir a tabela sem o arquivo BLOB.
524
020C
Esvaziar todos os campos BLOB.
525
020D
Reinicializar arquivo BLOB e perder todos os BLOBs.
526
020E
Falha na abertura.
527
020F
Não abra a tabela.
528
0210
Importar arquivo BLOB não-dBASE.
529
0211
Importar arquivo BLOB para o formato do dBASE.
530
0212
Abrir como tabela não-dBASE.
531
0213
Abrir tabela e arquivo BLOB no seu formato nativo.
532
0214
Divergência de driver de linguagem do índice de
produção.
533
0215
Índice de produção danificado.
534
0216
Recriar índice de produção.
535
0217
Recriar todos os índices de produção.
1024
0400
Tabela de pesquisa não localizada ou danificada.
1025
0401
Arquivo BLOB não localizado ou danificado.
1026
0402
Abrir somente para leitura.
1027
0403
Abrir a tabela no modo somente de leitura.
1028
0404
Falha na abertura.
1029
0405
Não abrir a tabela.
1030
0406
Remover pesquisa.
1031
0407
Remover vínculo com tabela de pesquisa.
1280
0500
Existe objeto de dicionário.
1281
0501
Pular este objeto.
1282
0502
Pular a importação deste objeto e seus relacionamentos
associados.
1283
0503
Usar objeto existente.
Abrir sem arquivo BLOB.
347
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
348
Continuação
Código de Erro
String de erro
Decimal
Hexa
1284
0504
Usar objeto do dicionário existente para os relacionamentos.
1285
0505
Abortar
1286
0506
Abortar a operação.
1287
0507
Importação do objeto de dicionário falhou.
4608
1200
SQL Unknown.
4609
1201
SQL Prepare.
4610
1202
SQL Execute.
4611
1203
SQL Error.
4612
1204
SQL STMT.
4613
1205
SQL Connect.
4614
1206
SQL Transact.
4615
1207
SQL BLOB I/O.
4616
1208
SQL Misc.
4617
1209
SQL Vendor.
4618
120A
ORACLE – orlon.
4619
120B
ORACLE – olon.
4620
120C
ORACLE – ologof.
4621
120D
ORACLE – ocon.
4622
120E
ORACLE – ocof.
4623
120F
ORACLE – oopen.
4624
1210
ORACLE – osql3.
4625
1211
ORACLE – odsc.
4626
1212
ORACLE – odefin.
4627
1213
ORACLE – obndrv.
4628
1214
ORACLE – obndrvn.
4629
1215
ORACLE – oexec.
4630
1216
ORACLE – ofetch.
4631
1217
ORACLE – ofen.
4632
1218
ORACLE – ocan.
4633
1219
ORACLE – oclose.
4634
121A
ORACLE – oerhms.
4635
121B
ORACLE – oparse.
4636
121C
ORACLE – oflng.
4637
121D
ORACLE – odessp.
4638
121E
ORACLE – odescr.
4639
121F
ORACLE – oexn.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
4648
1228
INTRBASE – isc_attach_database.
4649
1229
INTRBASE – isc_blob_default_desc.
4650
122A
INTRBASE – isc_blob_gen_bpb.
4651
122B
INTRBASE – isc_blob_info.
4652
122C
INTRBASE – isc_blob_lookup_desc.
4653
122D
INTRBASE – isc_close_blob.
4654
122E
INTRBASE – isc_commit_retaining.
4655
122F
INTRBASE – isc_commit_transaction.
4656
1230
INTRBASE – isc_create_blob.
4657
1231
INTRBASE – isc_create_blob2.
4658
1232
INTRBASE – isc_decode_date.
4659
1233
INTRBASE – isc_detach_database.
4660
1234
INTRBASE – isc_dsql_allocate_statement.
4661
1235
INTRBASE – isc_dsql_execute.
4662
1236
INTRBASE – isc_dsql_execute2.
4663
1237
INTRBASE – isc_dsql_fetch.
4664
1238
INTRBASE – isc_dsql_free_statement.
4665
1239
INTRBASE – isc_dsql_prepare.
4666
123A
INTRBASE – isc_dsql_set_cursor_name.
4667
123B
INTRBASE – isc_dsql_sql_info.
4668
123C
INTRBASE – isc_encode_date.
4669
123D
INTRBASE – isc_get_segment.
4670
123E
INTRBASE – isc_interprete.
4671
123F
INTRBASE – isc_open_blob.
4672
1240
INTRBASE – isc_open_blob2.
4673
1241
INTRBASE – isc_put_segment.
4674
1242
INTRBASE – isc_rollback_transaction.
4675
1243
INTRBASE – isc_sqlcode.
4676
1244
INTRBASE – isc_start_transaction.
4677
1245
INTRBASE – isc_vax_integer.
4688
1250
MSSQL – dbbind.
4689
1251
MSSQL – dbcmd.
4690
1252
MSSQL – dbcancel.
4691
1253
MSSQL – dbclose.
4692
1254
MSSQL – dbcollen.
4693
1255
MSSQL – dbcolname.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
349
Tabela B.1
350
Continuação
Código de Erro
String de erro
Decimal
Hexa
4694
1256
MSSQL – dbcoltype.
4695
1257
MSSQL – dbconvert.
4696
1258
MSSQL – dbdataready.
4697
1259
MSSQL – dbdatlen.
4698
125A
MSSQL – dberrhandle.
4699
125B
MSSQL – dbfreebuf.
4700
125C
MSSQL – dbfreelogin.
4701
125D
MSSQL – dbhasretstat.
4702
125E
MSSQL – dbinit.
4703
125F
MSSQL – dblogin.
4704
1260
MSSQL – dbmoretext.
4705
1261
MSSQL – dbmsghandle.
4706
1262
MSSQL – dbnextrow.
4707
1263
MSSQL – dbnumcols.
4708
1264
MSSQL – dbnumrets.
4709
1265
MSSQL – dbopen.
4710
1266
MSSQL – dbresults.
4711
1267
MSSQL – dbretdata.
4712
1268
MSSQL – dbretlen.
4713
1269
MSSQL – dbretstatus.
4714
126A
MSSQL – dbrpcinit.
4715
126B
MSSQL – dbrpcparam.
4716
126C
MSSQL – dbrpcsend.
4717
126D
MSSQL – dbsetlogintime.
4718
126E
MSSQL – dbsetmaxprocs.
4719
126F
MSSQL – dbsetopt.
4720
1270
MSSQL – dbsettime.
4721
1271
MSSQL – dbsqlexec.
4722
1272
MSSQL – dbsqlok.
4723
1273
MSSQL – dbsqlsend.
4724
1274
MSSQL – dbtxptr.
4725
1275
MSSQL – dbtxtimestamp.
4726
1276
MSSQL – dbtxtsnewval.
4727
1277
MSSQL – dbuse.
4728
1278
MSSQL – dbwinexit.
4729
1279
MSSQL – dbwritetext.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
4738
1282
ODBC – SQLAllocConnect.
4739
1283
ODBC – SQLAllocEnv.
4740
1284
ODBC – SQLAllocStmt.
4741
1285
ODBC – SQLBindCol.
4742
1286
ODBC – SQLBindParameter.
4743
1287
ODBC – SQLCancel.
4744
1288
ODBC – SQLColumns.
4745
1289
ODBC – SQLConnect.
4746
128A
ODBC – SQLDataSources.
4747
128B
ODBC – SQLDescribeCol.
4748
128C
ODBC – SQLDisconnect.
4750
128E
ODBC – SQLError.
4751
128F
ODBC – SQLExecDirect.
4752
1290
ODBC – SQLExtendedFetch.
4753
1291
ODBC – SQLFetch.
4754
1292
ODBC – SQLFreeConnect.
4755
1293
ODBC – SQLFreeEnv.
4756
1294
ODBC – SQLFreeStmt.
4757
1295
ODBC – SQLGetConnectOption.
4758
1296
ODBC – SQLGetCursorName.
4760
1298
ODBC – SQLGetFunctions.
4761
1299
ODBC – SQLGetInfo.
4762
129A
ODBC – SQLGetTypeInfo.
4763
129B
ODBC – SQLNumResultCols.
4764
129C
ODBC – SQLProcedures.
4765
129D
ODBC – SQLProcedureColumns.
4766
129E
ODBC – SQLRowCount.
4767
129F
ODBC – SQLSetConnectOption.
4768
12A0
ODBC – SQLSetCursorName.
4769
12A1
ODBC – SQLSetParam.
4770
12A2
ODBC – SQLSetStmtOption.
4771
12A3
ODBC – SQLStatistics.
4772
12A4
ODBC – SQLTables.
4773
12A5
ODBC – SQLTransact.
4788
12B4
SYBASE – dbbind.
4789
12B5
SYBASE – dbcmd.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
351
Tabela B.1
352
Continuação
Código de Erro
String de erro
Decimal
Hexa
4790
12B6
SYBASE – dbcancel.
4791
12B7
SYBASE – dbclose.
4792
12B8
SYBASE – dbcollen.
4793
12B9
SYBASE – dbcolname.
4794
12BA
SYBASE – dbcoltype.
4795
12BB
SYBASE – dbconvert.
4796
12BC
SYBASE – dbpoll.
4797
12BD
SYBASE – dbdatlen.
4798
12BE
SYBASE – dberrhandle.
4799
12BF
SYBASE – dbfreebuf.
4800
12C0
SYBASE – dbloginfree.
4801
12C1
SYBASE – dbhasretstat.
4802
12C2
SYBASE – dbinit.
4803
12C3
SYBASE – dblogin.
4804
12C4
SYBASE – dbmoretext.
4805
12C5
SYBASE – dbmsghandle.
4806
12C6
SYBASE – dbnextrow.
4807
12C7
SYBASE – dbnumcols.
4808
12C8
SYBASE – dbnumrets.
4809
12C9
SYBASE – dbopen.
4810
12CA
SYBASE – dbresults.
4811
12CB
SYBASE – dbretdata.
4812
12CC
SYBASE – dbretlen.
4813
12CD
SYBASE – dbretstatus.
4814
12CE
SYBASE – dbrpcinit.
4815
12CF
SYBASE – dbrpcparam.
4816
12D0
SYBASE – dbrpcsend.
4817
12D1
SYBASE – dbsetlogintime.
4818
12D2
SYBASE – dbsetmaxprocs.
4819
12D3
SYBASE – dbsetopt.
4820
12D4
SYBASE – dbsettime.
4821
12D5
SYBASE – dbsqlexec.
4822
12D6
SYBASE – dbsqlok.
4823
12D7
SYBASE – dbsqlsend.
4824
12D8
SYBASE – dbtxptr.
4825
12D9
SYBASE – dbtxtimestamp.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
4826
12DA
SYBASE – dbtxtsnewval.
4827
12DB
SYBASE – dbuse.
4828
12DC
SYBASE – dbwinexit.
4829
12DD
SYBASE – dbwritetext.
4830
12DE
SYBASE – dbcount.
4831
12DF
SYBASE – dbdead.
4942
134E
Código de erro SQL não mapeado.
8449
2101
Impossível abrir arquivo do sistema.
8450
2102
Erro de I/O em um arquivo do sistema.
8451
2103
Danos na estrutura de dados.
8452
2104
Impossível localizar arquivo de configuração do mecanismo.
8453
2105
Impossível gravar no arquivo de configuração do mecanismo.
8454
2106
Impossível inicializar com arquivo de configuração diferente.
8455
2107
Sistema foi reentrado ilegalmente.
8456
2108
Impossível localizar IDAPI32.DLL.
8457
2109
Impossível carregar IDAPI32.DLL.
8458
210A
Impossível carregar uma biblioteca de serviço IDAPI.
8459
210B
Impossível criar ou abrir arquivo temporário.
8705
2201
No início da tabela.
8706
2202
No final da tabela.
8707
2203
Registro movido porque o valor-chave foi alterado.
8708
2204
Registro/chave alterados.
8709
2205
Nenhum registro atual.
8710
2206
Impossível localizar registro.
8711
2207
Fim do BLOB.
8712
2208
Impossível localizar objeto.
8713
2209
Impossível localizar membro da família.
8714
220A
Arquivo BLOB inexistente.
8715
220B
Impossível localizar driver da linguagem.
8961
2301
Cabeçalho de tabela/índice danificado.
8962
2302
Arquivo danificado – não o cabeçalho.
8963
2303
Arquivo memo/BLOB danificado.
8965
2305
Índice danificado.
8966
2306
Arquivo de bloqueio danificado.
8967
2307
Arquivo de família danificado.
8968
2308
Arquivo VAL danificado ou inexistente.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
353
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
8969
2309
Formato de arquivo de índice externo.
9217
2401
Falha na leitura.
9218
2402
Falha na gravação.
9219
2403
Impossível acessar diretório.
9220
2404
Falha na operação de exclusão de arquivo.
9221
2405
Impossível acessar arquivo.
9222
2406
Acesso à tabela desativado por causa de erro anterior.
9473
2501
Memória insuficiente para esta operação.
9474
2502
Alças de arquivo insuficientes.
9475
2503
Espaço em disco insuficiente.
9476
2504
Limite de recurso de tabela temporária.
9477
2505
Tamanho do registro muito grande para a tabela.
9478
2506
Muitos cursores abertos.
9479
2507
Tabela cheia.
9480
2508
Muitas sessões a partir desta estação de trabalho.
9481
2509
Limite de número de série (Paradox).
9482
250A
Algum limite interno (ver contexto).
9483
250B
Muitas tabelas abertas.
9484
250C
Muitos cursores por tabela.
9485
250D
Muitos bloqueios de registro na tabela.
9486
250E
Muitos clientes.
9487
250F
Muitos índices na tabela.
9488
2510
Muitas sessões.
9489
2511
Muitos bancos de dados abertos.
9490
2512
Muitas senhas.
9491
2513
Muitos drivers ativos.
9492
2514
Muitos campos na criação de tabela.
9493
2515
Muitos bloqueios de tabela.
9494
2516
Muitos BLOBs abertos.
9495
2517
Arquivo de bloqueio cresceu muito.
9496
2518
Muitas consultas abertas.
9498
251A
Muitos BLOBs.
9499
251B
Nome de arquivo muito longo para uma tabela do
Paradox 5.0.
9500
251C
Limite de busca de linha excedido.
9501
251D
Nome longo não permitido para este nível de tabela.
354
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
9729
2601
Violação de chave.
9730
2602
Falha na verificação de validade mínima.
9731
2603
Falha na verificação de validade máxima.
9732
2604
Valor de campo requerido.
9733
2605
Registro mestre inexistente.
9734
2606
Mestre possui registros de detalhe. Impossível excluir ou
modificar.
9735
2607
Nível da tabela mestra incorreto.
9736
2608
Valor de campo fora do intervalo da tabela de pesquisa.
9737
2609
Falha na operação de abertura de tabela de pesquisa.
9738
260A
Falha na operação de abertura de tabela de detalhe.
9739
260B
Falha na operação de abertura de tabela mestre.
9740
260C
Campo em branco.
9741
260D
Vínculo com tabela mestre já definido.
9742
260E
Tabela mestre aberta.
9743
260F
Tabelas de detalhe existem.
9744
2610
Mestre possui registros de detalhe. Impossível esvaziá-la.
9745
2611
Integridade referencial de auto-referência precisa ser
incluída uma de cada vez, sem outras mudanças na tabela.
9746
2612
Tabela de detalhe aberta.
9747
2613
Impossível tornar esta tabela mestra em detalhe de outra
tabela se seus detalhes não estiverem vazios.
9748
2614
Campos de integridade referencial precisam estar
indexados.
9749
2615
Uma tabela vinculada pela integridade referencial exige
senha para ser aberta.
9750
2616
Campo(s) vinculado(s) a mais de um mestre.
9985
2701
Número fora do intervalo.
9986
2702
Parâmetro inválido.
9987
2703
Nome de arquivo inválido.
9988
2704
Arquivo não existe.
9989
2705
Opção inválida.
9990
2706
Alça inválida para a função.
9991
2707
Tipo de tabela desconhecido.
9992
2708
Impossível abrir arquivo.
9993
2709
Impossível redefinir chave primária.
9994
270A
Impossível alterar este RINTDesc.
355
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
9995
270B
Chave externa e primária divergentes.
9996
270C
Pedido de modificação inválido.
9997
270D
Índice não existe.
9998
270E
Offset inválido no BLOB.
9999
270F
Número de descritor inválido.
10000
2710
Tipo de arquivo inválido.
10001
2711
Descritor de campo inválido.
10002
2712
Transformação de campo inválida.
10003
2713
Estrutura de registro inválida.
10004
2714
Descritor inválido.
10005
2715
Array inválido de descritores de índice.
10006
2716
Array inválido de descritores de verificação de validade.
10007
2717
Array inválido de descritores de integridade referencial.
10008
2718
Ordenação inválida de tabelas durante a reestrutura.
10009
2719
Nome não exclusivo neste contexto.
10010
271A
Nome de índice exigido.
10011
271B
Alça de sessão inválida.
10012
271C
Operação de reestrutura inválida.
10013
271D
Driver não conhecido no sistema.
10014
271E
Banco de dados desconhecido.
10015
271F
Senha inválida fornecida.
10016
2720
Nenhuma função de callback.
10017
2721
Tamanho inválido do buffer de callback.
10018
2722
Diretório inválido
10019
2723
Erro de tradução. Valor fora dos limites.
10020
2724
Impossível definir cursor de uma tabela para outra.
10021
2725
Bookmarks não combinam com a tabela.
10022
2726
Nome de índice/tag inválido.
10023
2727
Descritor de índice inválido.
10024
2728
Tabela não existe.
10025
2729
Tabela possui muitos usuários.
10026
272A
Impossível avaliar chave ou chave não passa condição de
filtro.
356
10027
272B
Índice já existe.
10028
272C
Índice aberto.
10029
272D
Tamanho de BLOB inválido.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
10030
272E
Alça de BLOB inválida no buffer de registro.
10031
272F
Tabela está aberta.
10032
2730
Precisa reestruturar (hard).
10033
2731
Modo inválido.
10034
2732
Impossível fechar índice.
10035
2733
Índice está sendo usado para classificar tabela.
10036
2734
Nome de usuário e senha desconhecidos.
10037
2735
Cascada multinível não é aceita.
10038
2736
Nome de arquivo inválido.
10039
2737
Nome de tabela inválido.
10040
2738
Expressão de cursor vinculado inválida.
10041
2739
Nome reservado.
10042
273A
Extensão de arquivo inválida.
10043
273B
Driver de linguagem inválido.
10044
273C
Alias não aberto atualmente.
10045
273D
Estruturas de registro incompatíveis.
10046
273E
Nome reservado pelo DOS.
10047
273F
Destino precisa ser indexado.
10048
2740
Tipo de índice inválido.
10049
2741
Drivers de linguagem da tabela e índice não combinam.
10050
2742
Alça do filtro inválida.
10051
2743
Filtro inválido.
10052
2744
Pedido Table Create inválido.
10053
2745
Pedido Table Delete inválido.
10054
2746
Pedido Index Create inválido.
10055
2747
Pedido Index Delete inválido.
10056
2748
Tabela inválida especificada.
10058
274A
Hora inválida.
10059
274B
Data inválida.
10060
274C
Data/hora inválida.
10061
274D
Tabelas em diretórios diferentes.
10062
274E
Divergência no número de argumentos.
10063
274F
Função não localizada na biblioteca de serviço.
10064
2750
Precisa usar baseorder para esta operação.
10065
2751
Nome de procedimento inválido.
10066
2752
O mapa de campo é inválido.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
357
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
10241
2801
Registro bloqueado por outro usuário.
10242
2802
Falha ao desbloquear.
10243
2803
Tabela está ocupada.
10244
2804
Diretório está ocupado.
10245
2805
Arquivo está bloqueado.
10246
2806
Diretório está bloqueado.
10247
2807
Registro já bloqueado por esta sessão.
10248
2808
Objeto não bloqueado.
10249
2809
Tempo limite de bloqueio esgotado.
10250
280A
Grupo de chave está bloqueado.
10251
280B
Bloqueio de tabela foi perdido.
10252
280C
Acesso exclusivo foi perdido.
10253
280D
Tabela não pode ser aberta para uso exclusivo.
10254
280E
Bloqueio de registro em conflito nesta sessão.
10255
280F
Um impasse (deadlock) foi detectado.
10256
2810
Uma transação do usuário já está em andamento.
10257
2811
Nenhuma transação do usuário em andamento no
momento.
10258
2812
Falha no bloqueio de registro.
10259
2813
Impossível realizar a edição porque outro usuário alterou
o registro.
10260
2814
Impossível realizar a edição porque outro usuário excluiu
ou moveu o registro.
358
10497
2901
Direitos de campo insuficientes para a operação.
10498
2902
Direitos de tabela insuficientes para a operação. Senha
exigida.
10499
2903
Direitos de família insuficientes para a operação.
10500
2904
Esse diretório é somente de leitura.
10501
2905
Banco de dados é somente de leitura.
10502
2906
Tentando modificar campo somente de leitura.
10503
2907
Tabelas criptografadas do dBASE não aceitas.
10504
2908
Direitos SQL insuficientes para a operação.
10753
2A01
Campo não é um BLOB.
10754
2A02
BLOB já aberto.
10755
2A03
BLOB não aberto.
10756
2A04
Operação não se aplica.
10757
2A05
Tabela não é indexada.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
10758
2A06
Mecanismo não inicializado.
10759
2A07
Tentativa de reinicializar mecanismo.
10760
2A08
Tentativa de misturar objetos de diferentes sessões.
10761
2A09
Driver do Paradox não ativo.
10762
2A0A
Driver não carregado.
10763
2A0B
Tabela apenas de leitura.
10764
2A0C
Nenhum índice associado.
10765
2A0D
Tabela(s) aberta(s). Impossível realizar esta operação.
10766
2A0E
Tabela não aceita esta operação.
10767
2A0F
Índice somente de leitura.
10768
2A10
Tabela não aceita esta operação porque não está indexada
exclusivamente.
10769
2A11
Operação precisa ser realizada na sessão atual.
10770
2A12
Uso inválido da palavra-chave
10771
2A13
Conexão está em uso por outra instrução.
10772
2A14
Conexão SQL de passagem precisa ser compartilhada.
11009
2B01
Número de função inválido.
11010
2B02
Arquivo ou diretório não existe.
11011
2B03
Caminho não localizado.
11012
2B04
Muitos arquivos abertos. Você pode ter de aumentar o
limite de MAXFILEHANDLE na configuração IDAPI.
11013
2B05
Permissão negada.
11014
2B06
Número de arquivo incorreto.
11015
2B07
Blocos de memória destruídos.
11016
2B08
Memória insuficiente.
11017
2B09
Endereço inválido de bloco de memória.
11018
2B0A
Ambiente inválido.
11019
2B0B
Formato inválido.
11020
2B0C
Código de acesso inválido.
11021
2B0D
Dados inválidos.
11023
2B0F
Dispositivo não existe.
11024
2B10
Tentativa de remover diretório atual.
11025
2B11
Não é o mesmo dispositivo.
11026
2B12
Não há mais arquivos.
11027
2B13
Argumento inválido.
11028
2B14
Lista de arquivos muito longa.
359
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
11029
2B15
Erro no formato de execução.
11030
2B16
Vínculo entre dispositivo.
11041
2B21
Argumento matemático.
11042
2B22
Resultado muito grande
11043
2B23
Arquivo já existe.
11047
2B27
Erro interno desconhecido do sistema operacional.
11058
2B32
Violação de compartilhamento.
11059
2B33
Violação de bloqueio.
11060
2B34
Erro crítico do DOS.
11061
2B35
Unidade não está pronta.
11108
2B64
Leitura/gravação inexata.
11109
2B65
Erro de rede do sistema operacional.
11110
2B66
Erro do servidor de arquivos Novell.
11111
2B67
Servidor Novell sem memória.
11112
2B68
Registro já bloqueado por esta estação de trabalho.
11113
2B69
Registro não bloqueado.
11265
2C01
Falha na inicialização da rede.
11266
2C02
Excedido o limite de usuários da rede.
11267
2C03
Versão de arquivo NET incorreta.
11268
2C04
Impossível bloquear arquivo da rede.
11269
2C05
Diretório não é privado.
11270
2C06
Diretório é controlado por outro arquivo NET.
11271
2C07
Erro de rede desconhecido.
11272
2C08
Não inicializado para acessar arquivos da rede.
11273
2C09
Compartilhamento não carregado. Exibido para
compartilhar arquivos locais.
11274
2C0A
Não está em uma rede. Não conectado ou driver de rede
errado.
11275
2C0B
Comunicação perdida com o servidor SQL.
11277
2C0D
Impossível localizar ou conectar-se ao servidor SQL.
11278
2C0E
Impossível localizar ou conectar-se ao servidor da rede.
11521
2D01
Parâmetro opcional exigido.
11522
2D02
Parâmetro opcional inválido.
11777
2E01
Obsoleto.
11778
2E02
Obsoleto.
11779
2E03
Uso ambíguo de ! (operador de inclusão).
360
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
11780
2E04
Obsoleto.
11781
2E05
Obsoleto.
11782
2E06
Uma operação SET não pode ser incluída em seu próprio
agrupamento.
11783
2E07
Somente campos numéricos e data/hora podem ter a
média calculada.
11784
2E08
Expressão inválida.
11785
2E09
Expressão OR inválida.
11786
2E0A
Obsoleto.
11787
2E0B
Bitmap.
11788
2E0C
Expressão CALC não pode ser usada em linhas INSERT,
DELETE, CHANGETO e SET.
11789
2E0D
Erro de tipo na expressão CALC.
11790
2E0E
CHANGETO só pode ser usado em um formulário de consulta
11791
2E0F
Impossível modificar tabela CHANGED.
11792
2E10
Um campo pode conter apenas uma expressão CHANGETO.
11793
2E11
Um campo não pode conter mais de uma expressão a ser
de cada vez.
inserida.
11794
2E12
Obsoleto.
11795
2E13
CHANGETO precisa ser seguido pelo novo valor para o campo.
11796
2E14
Marca de seleção ou expressões CALC não podem ser usadas
em consultas FIND.
11797
2E15
Impossível realizar operação em tabela CHANGED junto com
uma consulta CHANGETO.
11798
2E16
Chunk.
11799
2E17
Mais de 255 campos na tabela ANSWER.
11800
2E18
AS precisa ser seguido pelo nome para o campo na tabela
ANSWER.
11801
2E19
DELETE pode ser usado apenas em um formulário de
11802
2E1A
Impossível realizar operação sobre tabela DELETED junto
com uma consulta DELETE.
11803
2E1B
Impossível deletar da tabela DELETED.
11804
2E1C
Elemento de exemplo é usado em dois campos com tipos
incompatíveis ou com um BLOB.
11805
2E1D
Impossível usar elementos de exemplo em uma expressão OR.
11806
2E1E
Expressão neste campo possui o tipo errado.
consulta de cada vez.
361
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
11807
2E1F
Encontrada vírgula extra.
11808
2E20
Encontrado OR extra.
11809
2E21
Uma ou mais linhas de consulta não contribuem para
a ANSWER.
362
11810
2E22
FIND pode ser usado apenas em um formulário de consulta
11811
2E23
FIND não pode ser usado com a tabela ANSWER.
11812
2E24
Uma linha com GROUPBY precisa conter operações SET.
11813
2E25
GROUPBY só pode ser usado em linhas SET.
11814
2E26
Use apenas INSERT, DELETE, SET ou FIND na coluna mais à
esquerda.
11815
2E27
Use apenas um INSERT, DELETE, SET ou FIND por linha.
11816
2E28
Erro de sintaxe na expressão.
11817
2E29
INSERT s’pode ser usado em uma consulta de cada vez.
11818
2E2A
Impossível realizar operação sobre tabela INSERTED junto
com uma consultar INSERT.
11819
2E2B
Linhas INSERT, DELETE, CHANGETO e SET não podem ser
marcadas.
11820
2E2C
Campo precisa conter uma expressão para inserir (ou estar
em branco).
11821
2E2D
Impossível inserir na tabela INSERTED.
11822
2E2E
Variável é um array e não pode ser acessado.
11823
2E2F
Label.
11824
2E30
Linhas de elementos de exemplo na expressão CALC
precisam ser vinculadas.
11825
2E31
Nome da variável é muito longo.
11826
2E32
Consulta pode levar um longo tempo para ser processada.
11827
2E33
Palavra reservada ou que não pode ser usada como nome
de variável.
11828
2E34
Falta vírgula.
11829
2E35
Falta parêntese da direita.
11830
2E36
Falta aspa da direita.
11831
2E37
Impossível especificar nomes de coluna duplicados.
11832
2E38
Consulta não possui campos marcados.
11833
2E39
Elemento de exemplo não possui ocorrência de definição.
11834
2E3A
Nenhum agrupamento definido para operação SET.
11835
2E3B
Consulta não faz sentido.
11836
2E3C
Impossível usar padrões neste contexto.
de cada vez.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
11837
2E3D
Data não existe.
11838
2E3E
Variável não recebeu um valor.
11839
2E3F
Uso inválido do elemento de exemplo na expressão de
resumo.
11840
2E40
Instrução de consulta incompleta. Consulta não possui uma
definição SET.
11841
2E41
Elemento de exemplo com ! não faz sentido na expressão.
11842
2E42
Elemento de exemplo não pode ser usado mais de duas
vezes com uma consulta !.
11843
2E43
Linha não pode conter expressão.
11844
2E44
Obsoleto.
11845
2E45
Obsoleto.
11846
2E46
Não há permissão para inserir ou excluir registros.
11847
2E47
Não há permissão para modificar campo.
11848
2E48
Campo não localizado na tabela.
11849
2E49
Esperando um operador de coluna no cabeçalho de tabela.
11850
2E4A
Esperando um separador de coluna na tabela.
11851
2E4B
Esperando um nome de coluna na tabela.
11852
2E4C
Esperando nome da tabela.
11853
2E4D
Esperando um número coerente de colunas em todas as
linhas da tabela.
11854
2E4E
Impossível abrir tabela.
11855
2E4F
Campo aparece mais de uma vez na tabela.
11856
2E50
Essa consulta DELETE, CHANGE ou INSERT não possui ANSWER.
11857
2E51
Consulta não está preparada. Propriedades desconhecidas.
11858
2E52
Linhas DELETE não podem conter expressão quantificadora.
11859
2E53
Expressão inválida na linha INSERT.
11860
2E54
Expressão inválida na linha INSERT.
11861
2E55
Expressão inválida na definição SET.
11862
2E56
Uso de linha.
11863
2E57
Palavra-chave SET esperada.
11864
2E58
Uso ambíguo do elemento de exemplo.
11865
2E59
Obsoleto.
11866
2E5A
Obsoleto.
11867
2E5B
Somente campos numéricos podem ser somados.
11868
2E5C
Tabela é protegida contra gravação.
11869
2E5D
Token não localizado.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
363
Tabela B.1
364
Continuação
Código de Erro
String de erro
Decimal
Hexa
11870
2E5E
Impossível usar elemento de exemplo com ! mais de uma
vez em uma única linha.
11871
2E5F
Divergência de tipo na expressão.
11872
2E60
Consulta parece fazer duas perguntas não relacionadas.
11873
2E61
Linha SET não utilizada.
11874
2E62
INSERT, DELETE, FIND e SET só podem ser usados na colunas
mais à esquerda.
11875
SET.
2E63
CHANGETO não pode ser usado com INSERT, DELETE, FIND ou
11876
2E64
Expressão precisa ser seguida por um elemento de exemplo
definido em um SET.
11877
2E65
Falha de bloqueio.
11878
2E66
Expressão muito longa.
11879
2E67
Exceção de atualização durante consulta.
11880
2E68
Consulta cancelada.
11881
2E69
Erro inesperado no mecanismo de banco de dados.
11882
2E6A
Não há memória suficiente para concluir a operação.
11883
2E6B
Exceção inesperada.
11884
2E6C
Recurso ainda não implementado na consulta.
11885
2E6D
Formato de consulta não aceito.
11886
2E6E
String de consulta está vazia.
11887
2E6F
Tentou preparar uma consulta vazia.
11888
2E70
Buffer muito pequeno para conter string de consulta.
11889
2E71
Consulta não foi desmembrada ou preparada anteriormente.
11890
2E72
Função chamou com alça de consulta errada.
11891
2E73
Erro de sintaxe do QBE.
11892
2E74
Erro de contagem no campo de sintaxe estendida da
consulta.
11893
2E75
Nome de campo na cláusula de classificação ou campo não
localizado.
11894
2E76
Nome de tabela na cláusula de classificação ou campo não
localizado.
11895
2E77
Operação não aceita nos campos BLOB.
11896
2E78
Erro geral do BLOB.
11897
2E79
Consulta precisa ser reiniciada.
11898
2E7A
Tipo de tabela de resposta desconhecida.
11926
2E96
BLOB não pode ser usado como campo de agrupamento.
11927
2E97
Propriedades de consulta não foram apanhadas.
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
11928
2E98
Tabela de resposta é de tipo inadequado.
11929
2E99
Tabela de resposta ainda não é aceita sob o alias do servidor.
11930
2E9A
Campo BLOB não nulo exigido. Impossível inserir registros.
11931
2E9B
Índice exclusivo obrigatório para realizar CHANGETO.
11932
2E9C
Índice exclusivo obrigatório para deletar registros.
11933
2E9D
Falha na atualização da tabela no servidor.
11934
2E9E
Impossível processar esta consulta remotamente.
11935
2E9F
Fim de comando inesperado.
11936
2EA0
Parâmetro não definido na string de consulta.
11937
2EA1
String de consulta é muito longa.
11946
2EAA
Não existe tal nome de tabela ou correlação.
11947
2EAB
Expressão possui tipo de dado ambíguo.
11948
2EAC
Campo em ORDER BY precisa estar no resultset.
11949
2EAD
Erro de desmembramento geral.
11950
2EAE
Falha na restrição de registro ou campo.
11951
2EAF
Campo de GROUP BY precisa estar no resultset.
11952
2EB0
Função definida pelo usuário não está definida.
11953
2EB1
Erro desconhecido da função definida pelo usuário.
11954
2EB2
Subconsulta de única linha produziu mais de uma linha.
11955
2EB3
Expressões em GROUP BY não são aceitas.
11956
2EB4
Consultas sobre texto ou tabelas ASCII não são aceitas.
11957
2EB5
Palavras-chave USING e NATURAL da associação ANSI não são
aceitas nesta versão.
11958
2EB6
SELECT DISTINCT não pode ser usado com UNION a menos
que seja usado UNION ALL.
11959
2EB7
GROUP BY é obrigatório quando campos de agregação e não
11960
2EB8
Operações INSERT e UPDATE não são aceitas em tipo de
campo de autoincrement.
11961
2EB9
UPDATE em chave primária de uma tabela mestre pode
12033
2F01
Divergência de interface. Versão diferente do mecanismo.
12034
2F02
Índice desatualizado.
12035
2F03
Versão mais antiga (ver contexto).
12036
2F04
Arquivo VAL desatualizado.
12037
2F05
Versão do arquivo BLOB muito antiga.
12038
2F06
DLLs de consulta e mecanismo são divergentes.
de agregação são usados no resultset.
modificar mais de um registro.
365
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
12039
2F07
Servidor tem versão incompatível.
12040
2F08
Exigido nível de tabela superior.
12289
3001
Capacidade não aceita.
12290
3002
Ainda não implementado.
12291
3003
Réplicas do SQL não aceitas.
12292
3004
Coluna não BLOB na tabela exigida para realizar operação.
12293
3005
Conexões múltiplas não aceitas.
12294
3006
Expressões completas do dBASE não aceitas.
12545
3101
Especificação de alias de banco de dados inválida.
12546
3102
Tipo de banco de dados desconhecido.
12547
3103
Arquivo de configuração do sistema danificado.
12548
3104
Tipo de rede desconhecido.
12549
3105
Fora da rede.
12550
3106
Parâmetro de configuração inválido.
12801
3201
Objeto removido implicitamente.
12802
3202
Objeto pode estar truncado.
12803
3203
Objeto modificado implicitamente.
12804
3204
As restrições de campo devem ser verificadas?
12805
3205
Campo de verificação de validade modificado.
12806
3206
Nível de tabela alterado.
12807
3207
Copiar tabelas vinculadas?
12809
3209
Objeto truncado implicitamente.
12810
320A
Verificação de validade não será imposta.
12811
320B
Encontrados múltiplos registros, mas somente um era
esperado.
12812
320C
Campo será aparado. Impossível colocar registros
mestre na tabela com problema.
13057
3301
Arquivo já existe.
13058
3302
BLOB foi modificado.
13059
3303
Erro genérico da SQL.
13060
3304
Tabela já existe.
13061
3305
Tabelas do Paradox 1.0 não são aceitas.
13062
3306
Atualização abortada.
13313
3401
Ordem de classificação diferente.
13314
3402
Diretório em uso por versão anterior do Paradox.
13315
3403
Precisa de driver de linguagem compatível com Paradox 3.5.
366
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA
Tabela B.1
Continuação
Código de Erro
String de erro
Decimal
Hexa
13569
3501
Dicionário de dados está danificado.
13570
3502
BLOB de informações do dicionário de dados danificado.
13571
3503
Esquema do dicionário de dados danificado.
13572
3504
Tipo de atributo já existe.
13573
3505
Tipo de objeto inválido.
13574
3506
Tipo de relação inválida.
13575
3507
Visão já existe.
13576
3508
Não existe tal visão.
13577
3509
Restrição de registro inválida.
13578
350A
Objeto está em um DB lógico.
13579
350B
Dicionário já existe.
13580
350C
Dicionário não existe.
13581
350D
Banco de dados de dicionário não existe.
13582
350E
Informações de dicionário desatualizadas. Precisa atualizar.
13584
3510
Nome de dicionário inválido.
13585
3511
Existem objetos dependentes.
13586
3512
Muitos relacionamentos para este tipo de objeto.
13587
3513
Existem relacionamentos com o objeto.
13588
3514
Arquivo de troca de dicionário danificado.
13589
3515
Divergência na versão do arquivo de troca de dicionário.
13590
3516
Divergência no tipo de objeto do dicionário.
13591
3517
Objeto já existe no diretório de destino.
13592
3518
Impossível acessar dicionário de dados.
13593
3519
Impossível criar dicionário de dados.
13594
351A
Impossível abrir banco de dados.
15873
3E01
Nome de driver errado.
15874
3E02
Versão errada do sistema.
15875
3E03
Versão errada do driver.
15876
3E04
Tipo de driver errado.
15877
3E05
Impossível carregar driver.
15878
3E06
Impossível carregar driver de linguagem.
15879
3E07
Falha na inicialização do fornecedor.
15880
3E08
Sua aplicação não está ativada para usar este driver.
367
EDITORA CAMPUS — DELPHI 5 — 0424 — CAPÍTULO NO CD — 2ª PROVA

Documentos relacionados

Elementos Graficos

Elementos Graficos Além de pintar as figuras em um canvas de formulário, podemos pintá-las em um canvas de impressora, efetivamente imprimindo-as! Pelo fato de ser possível executar os mesmos métodos em um canvas de ...

Leia mais

desenvolvendo aplicações para bancos de dados desktop

desenvolvendo aplicações para bancos de dados desktop Windows vários programas são executados de maneira simultânea e não há como evitar isso. Controle da Tela: No DOS geralmente um programa ocupa todo o espaço da tela, e o usuário via e interagia ape...

Leia mais

Artigos Clube Delphi

Artigos Clube Delphi Essa função reproduz um som padrão do sistema operacional, o qual varia de acordo com a versão do Windows. Com sua chamada sempre é executado o som do sistema SystemDefault, independentemente dos v...

Leia mais