Class Inseptors no Delphi: como adicionar propriedades a classes existentes

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (15)  (0)

Neste artigo veremos como adicionar variáveis de escopo de classe (Fields) dinamicamente a classes e componentes do Delphi utilizando Class Inseptors.

Motivação

Quando há a necessidade de estender ou modificar o comportamento de classes e componentes no Delphi, existem algumas soluções possíveis, desde as mais invasivas, como alterar o código fonte original, até as mais orientadas a objetos, que fazem uso de herança e padrões de projeto. Uma solução, propriamente desenvolvida para isso, é o Class Helper. Ele permite obter esse resultado através de um código simples e organizado. Porém, por meio desse recurso só é possível trabalhar com métodos e propriedades ligadas a esses métodos, inviabilizando a adição de atributos que estejam relacionados a variáveis de escopo, conhecidas também por Fields (variáveis que normalmente são precedidas pelo sufixo F e armazenam dados para as propriedades).

Antes mesmo de surgir o recurso de Class Helper, no entanto, o compilador do Delphi já contava com outra solução, conhecida como Class Inseptor. Essa, de forma semelhante, permite inserir e modificar métodos, bem como inserir novas variáveis em uma classe para atender necessidades específicas inerentes ao design do software.

Problema: Adicionar variáveis de escopo de classe com Class Helper

Embora essa limitação possa ser corrigida em versões futuras do Delphi, atualmente, quando se trabalha com Class Helper, o compilador verifica para quais classes ele foi escrito e adiciona as propriedades e métodos ao final dos já existentes na classe, ajustando os possíveis “overrides”. Como o compilador do Delphi exige que as variáveis de escopo de classe (Fields) sejam declaradas obrigatoriamente antes das properties, functions e procedures, caso elas sejam encontradas em um Class Helper, será disparado um erro de compilação.

Na Listagem 1 é apresentado um exemplo de Class Helper para um componente do tipo TButton, no qual é necessário realizar a contagem dos cliques que foram efetuados sobre ele. Nesse código, então, realizamos uma tentativa de criar novas propriedades e sobrescrever o método Click.

        01 unit UnitButtonCountHelper;
        02 
        03 interface
        04 
        05 uses
        06   Vcl.StdCtrls;
        07 type
        08   TButtonCount = class helper for TButton
        09   private
        10     FLabelCount: TLabel;
        11     FClickCount: integer;
        12   public
        13     property LabelCount: TLabel read FLabelCount write FLabelCount;
        14     property ClickCount: integer read FClickCount;
        15     procedure Click;override;
        16   end;
        17 implementation
        18 
        19 { TButtonCount }
        20 
        21 procedure TButtonCount.Click;
        22 begin
        23    Inc(FClickCount);
        24 
        25   if assigned(FLabelCount) then
        26     FLabelCount.Caption:=FClickCount.ToString;
        27   inherited;
        28 
        29 end;
        30 
        31 end.  
        
Listagem 1. Class Helper para um TButton que armazena a quantidade de cliques

Linhas 10 e 11: declaramos dois fields para armazenarem, respectivamente, um TLabel, que receberá o valor do contador, e um integer, para armazenar a quantidade de cliques;

Linhas 13 e 14: declaramos as propriedades que apontam para as variáveis FLabelCount e FClickCount, respectivamente;

Linha 15: sobrescrevemos o método Click, já existente na classe TButon;

Linha 23: incrementamos o valor armazenado em FClickCount;

Linhas 25 e 26: verificamos se FLabelCount aponta para algum TLabel. Se verdadeiro, o Caption do TLabel recebe o valor contido em FClickCount.

Observação: O método FClickCount.ToString, utilizado na linha 26, só é possível a partir do Delphi XE3. Em versões anteriores, pode-se utilizar o IntToStr(FClickCount).

Ao executarmos o código dessa listagem o compilador dispara um erro apontando para linha 10 com a seguinte mensagem:

[dcc32 Error] UnitButtonCountHelper.pas(10): E2599 Field definition not allowed in helper type

Isso indica que a definição Fields não é permitida para um helper, invalidando totalmente seu uso para necessidades como a que foi apresentada acima.

Saiba mais sobre Class Helpers no Delphi.

Solução: Utilizando Class Inseptor

Embora não haja uma documentação oficial, Class Inspetor é um recurso de compilador que injeta métodos, propriedades e variáveis em uma superclasse através de uma classe especializada que contenha o mesmo nome.

O código da Listagem 2 visa resolver o mesmo problema da Listagem 1, mas desta vez utilizando o recurso de Class Inspetor, com o qual será possível armazenar a quantidade de cliques em uma variável (field) dentro da instância da classe, e ainda exibir esse valor em um TLabel encapsulado por outra variável.

        01 unit UnitButtonCount;
        02 
        03 interface
        04 
        05 uses
        06   Vcl.StdCtrls, SysUtils;
        07 
        08 type
        09   TButton = class(Vcl.StdCtrls.TButton)
        10   private
        11     FLabelCount: TLabel;
        12     FClickCount: integer;
        13   public
        14     property LabelCount: TLabel read FLabelCount write FLabelCount;
        15     property ClickCount: integer read FClickCount;
        16     procedure Click; override;
        17   end;
        18 
        19 implementation
        20 
        21 { TButton }
        22 
        23 procedure TButton.Click;
        24 begin
        25 
        26   Inc(FClickCount);
        27 
        28   if assigned(FLabelCount) then
        29     FLabelCount.Caption:=FClickCount.ToString;
        30   inherited;
        31 end;
        32 
        33 end. 
        
Listagem 2. Exemplo de Class Inseptor para um TButton

Linha 9: criamos um tipo que possui o mesmo nome daquele já existente no Delphi, TButton, da unit Vcl.StdCtrls. Para o compilador não disparar um erro, é necessário, na declaração da classe, colocar o caminho inteiro do tipo: Vcl.StdCtrls.TButton;

Linhas 11 e 12: declaramos as variáveis que serão referenciadas pelas respectivas propriedades, nas linhas 14 e 15.

Após a implementação desse código, para cada unit do sistema em que for acrescentada a unit UnitButtonCount na cláusula uses, todas as instâncias de TButton passarão a contar com as novas propriedades.

Assim, o compilador não irá disparar nenhum erro, pois a declaração dos fields encontra-se na posição adequada dentro da classe filha, diferente do que acontece quando isso é feito via Class Helper, em que eles são adicionados após os métodos já existentes na classe.

Para que isso funcione, no entanto, há uma única exigência: sempre manter a declaração da unit onde foi criado o Class Inseptor (UnitButtonCount, neste caso) após a declaração da unit do componente original (Vcl.StdCtrls, neste exemplo), da seguinte forma:

        uses
        Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, UnitButtonCount;
        

Feito isso, para utilizar esse novo botão, basta atribuir um label à sua propriedade LabelCount dentro do evento OnCreate do form em que o label será atualizado com um novo valor a cada click do mouse, conforme o código a seguir:

        procedure TForm3.FormCreate(Sender: TObject);
        begin
        Button1.LabelCount:=Label1;
        end; 
        

Do mesmo modo que acessamos a propriedade anterior, também será possível acessar a propriedade ClickCount desse botão, ou qualquer outra que tenhamos adicionado por meio de Class Inseptors. No caso do nosso exemplo, basta declarar: Button1.ClickCount.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?