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
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 maisdesenvolvendo 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 maisArtigos 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