Componente escrita, parte 3

Este artigo é a parte final de um artigo de três partes sobre componentes. Esta última parte irá cobrir editores de propriedade / componente, como escrever editores dedicados para o seu componente / propriedade, e como escrever componentes "escondidos".


Este artigo apareceu originalmente em Delphi Developer 

Copyright Pinnacle Publishing, Inc. Todos os direitos reservados. 


Editores de componente personalizado

Assim que você começar a escrever avançados tipos de propriedade para os nossos componentes, a vida vai se tornar um pouco mais complicada. Embora o inspetor objeto embutido no Delphi é capaz de reconhecer mais tipos de propriedade, é impossível que ele seja capaz de lidar com todos os tipos possíveis personalizados que nós podemos escrever em nossos componentes. Às vezes, o inspetor objeto é capaz de lidar com os nossos tipos personalizados, mas uma editar um esquema de propriedades no objeto inspetor tão complexo simplesmente não é intuitivo o suficiente. É neste ponto que nós podemos ser obrigados a escrever editores propriedade / componente. Delphi tem muitos editores já previamente definidos, estes editores estão no arquivo no DsgnIntf.pas na pasta $(Delphi)\Source\ToolsAPI. Você precisará listar essa unidade na cláusula de uso de qualquer editor componente / editor propriedade você talvez escreva, também é uma boa idéia manter este arquivo aberto para referência ao escrever seus próprios editores.

Normas de codificação

Para começar, eu vou cobrir alguns padrões de codificação que são usados quando se escreve editores de componente ou propriedade. Existem apenas alguns, mas seria uma boa idéia se manter a estas normas quando se escreve seus próprios editores porque torna mais fácil para as outras pessoas compreenderem o seu trabalho.

    Ao criar um editor propriedade, termine o nome do seu editor com a palavra "proprerty", de propriedade, por exemplo TAngleProperty

    Ao criar um editor componente termine o nome do seu editor com a palavra "Editor", por exemplo TPieChartEditor

    Ao escrever editores, sempre escreva o editor em uma unidade separada para o seu componente atual. É bom separar o tempo de design e o código de runtime e, além disto, faz seu EXE resultante em tamanho menor (em algumas versões do Delphi o componente pode parar de compilar os aplicativos se você não separá-los). Além disso, separe os pacotes, para que seus usuários possam construir com pacotes runtime, se quiserem!

    Nomeie a unidade do seu editor com o mesmo nome da sua unidade de componente, mas acrescente a palavra "reg" no final, por exemplo, um componente com o nome de unidade "MyComponent.pas" teria como resultado o nome do arquivo do editor sendo "MyComponentreg.pas"

    Finalmente, quando se escreve um editor componente / editor propriedade para o seu componente, tire a frase RegisterComponents da unidade de seu componente, e em sua unidade de editor do componente. Dessa forma o componente não será registrado sem que o editor também seja registrado.

O editor de propriedade

Editores de propriedade são utilizados pelo IDE para permitir a edição especial de propriedades individuais dentro de um componente. Alguns editores são muito simples, alguns são muito mais complicados. O Delphi já tem um número padrão de editores de propriedade, alguns das quais são:

TIntegerProperty. Utilizado para introduzir números inteiros.

TCharProperty. Utilizado para introduzir um único personagem.

TEnumProperty. Usado para selecionar um elemento individual em um e tipo numerado (alTop, alClient etc). TBoolProperty.Usados para selecionar "Verdadeiro" ou "falso" para propriedades Boolean.

TFloatProperty.
Utilizado para introduzir números de ponto flutuantes (Flutuação tipo Variável / Extensão etc. O tipo "Real" não deve ser utilizado para o componente propriedades).

TStringProperty.
Utilizado para entrada de strings, com um máximo de 255 caracteres.

TSetProperty. Utilizado para inclusão ou exclusão de elementos individuais de Definir Propriedade. Cada elemento é apresentado como uma sub-propriedade Booleana. Definir o valor como "verdadeiro" inclui o elemento, fixá-lo em "Falso" o exclui.

TClassProperty. Esta é a classe base a descender a partir de quando você deseja criar um editor personalizado para ser invocado para propriedades de uma determinada classe (quando você tem uma classe como uma propriedade, como TImage.Picture).

Todos esses editores de propriedade descendem direta ou indiretamente de TPropertyEditor. TPropertyEditor tem muitas propriedades e métodos, os mais significativos são.

function AllEqual: Boolean; virtual;
function GetAttributes: TPropertyAttributes; virtual;
procedure Edit; virtual;
function GetValue: string; virtual;
procedure GetValues(Proc: TGetStrProc); virtual;

 


AllEqual
Quando vários componentes são selecionados, o objeto inspetor filtra sua lista de propriedades apenas para as que todos os componentes selecionados têm em comum. Se o valor de cada componente para uma determinada propriedade (por exemplo, largura) é o mesmo, o valor será exibido, caso contrário, nenhum valor será mostrado. AllEqual é a rotina que determina se cada valor é idêntico. 

function TStringProperty.AllEqual: Boolean;
var
  I: Integer;
  V: string;
begin
  Result := False;
  if PropCount > 1 then
  begin
    V := GetStrValue;
    for I := 1 to PropCount - 1 do
      if GetStrValueAt(I) <> V then Exit;
  end;
  Result := True;
end;


No exemplo acima TStringProperty compara cada valor (utilizando GetStrValueAt) com o valor do primeiro elemento da lista (utilizando GetStrValue, GetStrValueAt (0) teria feito o mesmo). O tamanho da lista é determinado por PropCount, este devolve a quantia total de componentes selecionados.

GetAttributes
GetAttributes é chamado pelo IDE quando ele precisa de reunir informações sobre o editor de propriedade. O objeto inspetor apresenta um editor apropriado com base nas informações fornecidas. O resultado de GetAttributes (TPropertyAttributes) é um conjunto, de forma que ele pode conter uma combinação dos seguintes valores (esta não é uma lista completa)

paDialog
Diz ao inspetor objeto para mostrar um botão [...] após o nome da propriedade, quando o usuário clicar neste botão, o método Editar é disparado.

paSubProperties
Diz ao objeto inspetor para mostrar um botão de expandir [+] antes de nome da propriedade, clicar neste botão irá mostrar uma lista de sub propriedades expandidas (geralmente as propriedades publicadas de uma classe de propriedades).

paValueList
O objeto inspetor irá mostrar um combobox com uma lista de valores, esta lista é determinada pelo IDE ao chamar o método GetValues.
NOTA: O método GetValues, e não o método GetValue que é completamente diferente.

paSortList
Se combinadas com paValueList, os valores indicados serão ordenados por ordem alfabética.

paMultiSelect
Isto especifica ao IDE que a propriedade está autorizada a ser exibida quando múltiplos componentes são selecionados. Este item não está presente para os editores como TClassProperty.

paAutoUpdate
Faz com que o método SetValue seja chamado de cada vez que o valor é alterado dentro do objeto inspetor, em vez de esperar que o usuário pressione ou edite uma outra propriedade. Isto é usado para propriedades "Caption" e "Texto", para dar uma representação ao vivo do valor que o usuário está digitando.

paReadOnly
Se este elemento está incluído no valor do objeto inspetor, ele é somente para leitura. Isto é tipicamente usado em conjunto com paDialog. GetValue seria ignorado para retornar uma representação descritiva da propriedade.

Edit

Este método é chamado quando o botão [...] para a propriedade é clicado. Este botão aparece se o elemento paDialog está incluído dentro do resultado de GetAttributes.

GetValue
Este método é chamado quando o objeto inspetor tem que saber como exibir a propriedade como uma seqüência. Isto é tipicamente usado quando [paDialog, paReadOnly] são especificados no resultado de GetAttributes.

GetValues
Este método é chamado quando o objeto inspetor tem que recuperar uma lista de valores que deve ser apresentada quando paValueList está especificado no resultado de GetAttributes. GetValues passa um parâmetro chamado "Proc", que é do tipo TGetStrProc. GetStrProc é declarada como procedimento TGetStrProc = (const S: string) do objeto;

O IDE espera que o "Proc", seja chamado uma vez para cada valor que deverá ser exibido no objeto inspetor para esta propriedade.

procedure THintProperty.GetValues(Proc: TGetStrProc);
begin
  Proc('First item to display');
  Proc('Second item to display');
end;


O exemplo a seguir mostra como fornecer uma lista de valores padrão para a propriedade "Hint" de todos os componentes, embora ainda permitindo que o usuário coloque um valor que não está na lista.
 

type
  THintProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(String), nil, 
'Hint', THintProperty);
end;

{ THintProperty }

function THintProperty.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paValueList, paSortList];
end;

procedure THintProperty.GetValues(Proc: TGetStrProc);
begin
  Proc('This is a required entry');
  Proc('Press F1 for more information');
  Proc('This value is read-only');
end;


Primeiro, GetAttributes é ignorado, e [paValueList, paSortList] estão incluídos no resultado. Depois, GetValues é substituído e três valores são adicionados à lista suspensa ao chamar o procedimento "Proc".

Cadastramento de editores de propriedade

Finalmente a propriedade está registrada usando editor RegisterPropertyEditor. RegisterPropertyEditor tem quatro parâmetros:

PropertyType: PTypeInfo

Exige um ponteiro para um registro TTypeInfo. Isto soa muito mais complexo do que realmente é, tudo o que nós precisamos fazer é adicionar TypInfo à nossa cláusula de uso, e usar a função TypeInfo para recuperar o ponteiro para nós. TypeInfo (SomeVariableType)

ComponentClass: TClass
Esta é a classe base que deve aplicar-se a este editor. O editor será aplicável a esta classe e a quaisquer classes que descendem da mesma. Se nada for especificado, este editor será aplicado a qualquer classe.

const PropertyName: string
Se esse editor só deverá aplicar-se a uma propriedade específica, então o nome da propriedade deve ser especificado aqui. Se o editor deve ser aplicável a qualquer propriedade do tipo especificado na PropertyType este valor deve ser ''.

EditorClass: TPropertyEditorClass
Esta é a classe que foi criada para lidar com a propriedade. No exemplo acima é a classe THintProperty.

Usando Incorretamente o RegisterPropertyEditor

É importante quando se utiliza RegisterPropertyEditor que você forneça as informações corretas. Fornecer informações incorretas pode significar tanto que o seu editor afeta propriedades incorretas (por exemplo: Todas as propriedades string) ou componentes incorretos.

No outro extremo, fixar os parâmetros incorretamente pode significar que apenas uma propriedade específica de um componente específico (e descendentes) esteja associada ao seu editor. Esta não parece ser muito similar a um problema no início, mas componentes descendentes podem querer implementar propriedades adicionais do mesmo tipo. Como estas propriedades terão, evidentemente, um nome diferente, elas não terão o editor propriedade correto atribuído a elas.

Um exemplo de editor mal registrado já existe no VCL. O editor padrão para TCollection foi registrado para todas as classes descendentes de TComponent. O problema é que a classe mais baixa capaz de ser exibida no objeto inspetor é TPersistent (a classe que descende de TComponent).

Se um componente tem uma propriedade de tipo TPersistent (que por omissão expõe suas sub-unidades em uma lista expansível), e uma de suas propriedades é do tipo TCollection, o resultado é um botão [...] no objeto inspetor que não faz nada quando clicado (como vimos na segunda parte da série deste artigo).

A solução para este problema parece bastante simples. Ao invés de a nossa sub-propriedade ser descendente de TPersistent, nós podíamos descender de TComponent. No entanto, o comportamento padrão para uma propriedade do tipo TComponent (Conforme determinado pelo editor de propriedade TComponentProperty) é mostrar uma lista de outros componentes, em vez de as propriedades de um sub-componente embutido.

A verdadeira solução é realmente simples, mas só se você souber como escrever um editor de propriedade.

Passo 1:

type
  TExpandingRecord = class(TPersistent)

 


Deveria ser alterado para ler

type
  TExpandingRecord = class(TComponent)


Passo 2: Crie um editor de propriedade assim

type
  TExpandingRecordProperty = class(TClassProperty)
  public
    function GetAttributes : TPropertyAttributes; override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents(
'Article', [TExpandingComponent]);
  RegisterPropertyEditor(TypeInfo(TExpandingRecord), nil, 
'', TExpandingRecordProperty);
end;

{ TExpandingRecordProperty }

function TExpandingRecordProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paReadOnly, paSubProperties];
end;


Etapa 3: Remova a chamada RegisterComponents da unidade do componente, e, em vez disso, registre-a na unidade do editor. Dessa forma, podemos assegurar que o componente não será registrado sem o componente.

Agora os nossos imóveis do tipo TExpandingRecord vão mostrar como uma expansão de propriedade (devido a nós retornando paSubProperties a partir de GetAttributes), e o editor padrão para TCollection que irá funcionar como o proprietário da propriedade TCollection é um TComponent.

Diálogo de editores de propriedade

Na maioria das vezes, ao criar um editor de propriedade personalizada, o objetivo é fornecer um meio de interagir com os gráficos da propriedade.

Este primeiro exemplo é uma maneira muito simples que permite ao usuário inserir várias linhas na propriedade "Caption" de um TLabel. Embora este exemplo não seja muito complicado, ele demonstra como incluir um formulário dentro do seu editor.

Passo 1:
Selecione Arquivo, Novo aplicativo no menu principal. Isso criará um formulário, nomeie o formulário "fmLabelEdit", acrescente um TMemo ao formulário chamado memCaption. Adicione dois botões, "OK" e "Cancelar", com as propriedades ModalResult definidas para mrOK e mrCancel respectivamente.

Passo 2:
Adicionar DsgnIntf e TypInfo à sua cláusula de uso.

Passo 3:
Adicionar o seguinte código para a sua unidade de editor de propriedade.

TCaptionProperty = class(TStringProperty)
public
  function GetAttributes: TPropertyAttributes; override;
  procedure Edit; override;
end;


E registre o editor de propriedade assim

procedure Register;

implementation
{$R *.DFM}

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(TCaption), TLabel, 'Caption', TCaptionProperty);
end;


Etapa 4:
Adicione o seguinte código para que o objeto inspetor exibia o botão editar [...] após a propriedade nome.

function TCaptionProperty.GetAttributes: TPropertyAttributes;
begin
  Result := inherited GetAttributes + [paDialog];
end;


Passo 5:
Por fim, criamos um exemplo de nosso editor formulário, defina os conteúdos do memorando para a legenda (caption) atual e, em seguida, mostrar o formulário modalmente.

procedure TCaptionProperty.Edit;
var
  I: Integer;
begin
  with TfmLabelEdit.Create(Application) do
  try
    memCaption.Lines.Text := GetStrValue;
    ShowModal;

  {Se o ModalResult no formulário é mrOK, nós precisamos definer a propriedade "Caption" de cada TLabel.}

if ModalResult = mrOK then
      for I:=0 to PropCount-1 do
        TLabel(GetComponent(I)).Caption := memCaption.Lines.Text;
  finally
    Free;
  end;
end;


Passo 6:
Instale o pacote na unidade e, em seguida, experimente o novo editor!

Editores de propriedade avançados

Qualquer pessoa que alguma vez usou TActionList ou TDataSet (TTable / TQuery) terá passado pelo exemplo a seguir, talvez até mesmo sem perceber.

O editor ActionList é obviamente um editor personalizado, pois ele permite agrupamento de ações, enquanto que o FieldsEditor de TDataSet pode, à primeira vista, parecer um editor padrão, porém, após uma inspeção mais minuciosa tem um menu popup com itens como "Adicionar campos". No entanto, a característica mais notável destes dois editores não é que eles são editores de diálogo personalizados (semelhante ao que abrangemos anteriormente), mas o fato de que os itens que eles criam estão incluídos na frase da classe principal da unidade atual.

type
  TForm1 = class(TForm)
    ActionList1: TActionList;
    Action1: TAction;
    Action2: TAction;
  private
    
{ declarações privadas }
  public
    
{ declarações públicas}
  end;


A vantagem disto é que o IDE é feito ciente destes itens, portanto, permite-lhes ser selecionado a partir de uma lista de objetos quando a propriedade de um componente lhes exige.

Na ilustração acima, duas ações são adicionados a um TActionList, clicando no "Action" da propriedade Button1 aparece uma lista de ações acrescentadas. As duas ações também são adicionadas à frase da classe do formulário, e, portanto, pode ser referida pelo nome (Action1, Action2).

O truque aqui reside inteiramente no editor de propriedade e não no componente. Quando um editor de propriedade é disparado (ou seja, o método Edit é chamado), a propriedade Designer contém uma referência válida para um IFormDesigner (TFormDesigner em Delphi 4). Muitas das funções desta interface não estão dentro do escopo deste artigo, se quiser saber mais sobre as capacidades desta interface, eu recomendaria um livro chamado Delphi Developer's Handbook, por Marco Cantu.
 
Alguns dos métodos incluem

function MethodExists(const Name: string): Boolean;
procedure RenameMethod(const CurName, NewName: string);
procedure SelectComponent(Instance: TPersistent);
procedure ShowMethod(const Name: string);
function GetComponent(const Name: string): TComponent;
function CreateComponent(ComponentClass: TComponentClass; Parent: TComponent; Left, Top, Width, Height: Integer): TComponent;
; Esquerda, Superior, Largura, Altura: Integer): TComponent; 


Algumas das chamadas acima são bastante elementares, MethodExists, por exemplo, retornará Verdadeiro ou Falso, dependendo se um método nome já existe ou não no âmbito da modalidade da unidade atual (FormCreate, Button1Click etc). ShowMethod irá mover o cursor para o método nomeado, e RenameMethod vai mudar o nome de um método.

Os dois métodos que são de interesse para utilização neste momento são:

CreateComponent
Dado uma classe de componente, uma origem para segurar o componente, e as posições / dimensões, o designer irá criar uma instância da classe, como se o desenvolvedor o tivesse selecionado a partir da paleta do componente e acrescentada ao próprio formulário.

Modified
Informa o designer que algo foi modificado (uma propriedade, etc). Isto altera o estado da unidade para que o IDE saiba que deve ser salvo antes de fechar (ele também habilita o botão salvar no IDE).

Ao adicionar itens ao nosso array, tudo o que temos que fazer é pegar TMyProperty.Designer para criar um componente em nosso nome. Este componente será então adicionado à forma e qualquer propriedade que se refere a uma classe deste tipo terá automaticamente consciência disso. No caso de TActionList e TDataSet os componentes que são adicionados ao formulário não são visíveis no tempo de design, o componente proprietário atua como uma espécie de "gerente" para os componentes.

Durante o tempo de design, você não vai ver um componente TAction ou TField na paleta componente, que possivelmente fazer você suspeitar que eles não estejam registrados, mas o IDE ainda é capaz de criar instâncias desses componentes (e eles também não são visíveis). A resposta não é que eles não estão registrados, este comportamento é resultado de "como" o componente está registrado.

Considerando que RegisterComponents irá adicionar os seus componentes
à paleta de componentes, o método RegisterNoIcon irá registrar seu componente sem adicioná-lo à paleta de componentes, registrar desta forma também diz ao IDE que o componente não deve ser exibido durante a concepção de tempo. 

No exemplo a seguir nós vamos criar um componente chamado um TWavSound (um componente adicional chamado TWavButton está incluído no código-fonte que acompanha este artigo como um exemplo). TWavSound vai simplesmente dispor de dados a partir de um arquivo WAV, e reproduzir o som procurado. Apesar de que seria simples para nós, uma colocar um TWavSound no nosso formulário para cada som que exige wav, o nosso formulário poderia em breve começar a se tornar incontrolável, portanto, vamos também criar uma classe de gerenciamento chamada TWavList.

Cada técnica utilizada no código fonte para estes componentes foi abordada em duas partes desta série de artigos, assim, o código fonte não será tratado em grande nível de detalhes. Entretanto, eu mostrarei as frases de classe destes componentes só para lhe dar uma idéia de como eles são estruturados.

Nota: Na parte inferior da unidade, dentro da seção de inicialização da unidade você pode notar o seguinte código:

initialization
RegisterClass (TWavSound);

O motivo é que RegisterNoIcon não me parece que fazer um trabalho completo. Apesar de ele nos permitir criar instâncias dos componentes do nosso editor de propriedade, alguma coisa parece dar errado quando um projeto é re-carregado contendo estes componentes. A caixa de mensagem "classe não registrada" é exibida, e o projeto está corrompido. Além disso, registrar a classe desta forma parece para corrigir o problema.

TWavSound

type
  PWavData = ^TWavData;
  TWavData = packed record
    Size: Longint;
    Data: array[0..0] of byte;
  end;

  TWavSound = class(TComponent)
  
private
    FWavData: PWavData;
    FWav: TWav;
    
procedure ReadWavData(Stream: TStream);
    
procedure WriteWavData(Stream: TStream);
  
protected
    
procedure DefineProperties(Filer: TFiler); override;
  
public
    
destructor Destroy; override;
    
procedure Clear;
    
procedure LoadFromFile(const Filename: TFilename);
    
procedure LoadFromStream(Stream: TStream);
    
procedure Play;
  
published
  
end;


FWavData
Serão utilizados para armazenar o conteúdo do arquivo WAV, uma vez carregado a partir de um stream ou um arquivo.

Clear
Irá liberar a memória que segura o FWavData.

Play
Vai utilizar a chamada API SndPlaySound em MMSystem.pas para tocar os dados no FWavData.Data.

ReadWavData e WriteWavData
Serão utilizados internamente pelo IDE quando ele precisar de leitura / gravação de dados armazenados dentro FWavData.

DefineProperties

Vai especificar uma propriedade "oculta" denominada WavData, e dizer ao IDE que ReadWavData e WriteWavData deverão ser utilizados para a transmissão dos dados.

FWav
Está definido internamente pela classe TWav quando TWav.WavSound está definido para o nosso componente. A razão é que este item coleção terá de ser liberado quando o nosso componente TWavSound é liberado, a fim de parar de apontar para um objeto inválido.

TWavSound

type
  TWav = class(TCollectionItem)
  private
    FWavSound: TWavSound;
    procedure SetWavSound(const Value: TWavSound);
  protected
  public
    procedure Play;
  published
    property WavSound: TWavSound read FWavSound write SetWavSound;
  end;


SetWavSound
Irá garantir que o que o WavSound aponta terá o seu FWav corretamente configurado.

TWavs
É uma implementação padrão de TCollection, portanto, não será abrangido neste artigo. (Veja a parte 2 desta série)

TWavList
TWavList é simplesmente um componente que publica uma propriedade TWavs que nos permite editar a lista de wavs na hora do design.

TWavsProperty

TWavsProperty é o editor de propriedade que foi concebido para lidar com essa classe. Apesar que um editor padrão TCollection seria suficiente (a um ponto), eu decidi criar um novo editor, a fim de permitir a reprodução / compensação de WAVs no momento do design.

Primeiro eu criei uma nova unidade com um formulário dentro. Acrescentei alguns TSpeedButtons e um TListBox para listar os itens.
 
Além disso, devo acrescentar os seguintes itens para a frase da classe do formulário da

FWavs: TWavs;
 FComponent: TComponent;
 TheDesigner: IFormDesigner;


FWavs
Irá conter uma referência do TCollection que estamos editando.

FComponent
Irá conter uma referência para o componente que detém a coleção. Uma vez que o nosso formulário não será mostrado modalmente vamos ter que fechar o nosso formulário se este componente for destruído (usando o método de Notificação do nosso formulário).

TheDesigner
Irá conter uma referência ao objeto do Designer atual passado para o nosso editor de propriedade. Isso será usado para chamar CreateComponent, e para selecionar os nossos TWavSound ocultos no objeto inspetor sempre que um item selecionado na nossa lista.

O editor de propriedade é realmente muito simples.

type
  TWavsProperty = class(TClassProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    function GetDisplayName: string;
    procedure Edit; override;
  end;


O único verdadeiro método que vale a pena mencionar aqui é o método Edit. A implementação dele é

procedure TWavsProperty.Edit;
begin
  if fmWavsEditor = nil then
    fmWavsEditor := TfmWavsEditor.Create(Application);

  with fmWavsEditor do
  begin
    TheDesigner := Self.Designer; 
//Não esqueça do SELF !!
    Caption := Self.GetName;

    //Definir o display, então, mostrar o formulário
    Edit(TComponent(GetComponent(0)), TWavs(GetOrdValue));
  
end;
end;


Primeiro o editor formulário é criado (caso ainda não tenha sido criado).

"TheDesigner"
do formulário está definido para Self.Designer. Não se esqueça que o "Self" aqui como TForm também tem uma propriedade Designer que, neste momento, será nula.

GetComponent
(0) é usado para recuperar o componente que detém a propriedade. FreeNotification é chamada para este componente para assegurar que o nossa formulário seja notificado se o componente for destruído (pera que possamos fechar nosso formulário).

GetOrdValue
é usado para recuperar a classe objeto (as propriedades "WAVs") que está sendo editada, o resultado é typecast como TWavs.

O  método Edit que é chamado, faz parte de TfmWavsEditor, é um método que eu acrescentei que simplesmente limpa o listbox e preenche os itens com os nomes das entradas FWavs. Em seguida, mostra o formulário.

Nota: As versões posteriores do Delphi retornam TPersistent da função GetComponent, portanto, o resultado deve ser typecast para TComponent.

Conversando com IFormDesigner

As duas principais partes deste editor (exceto para limpar o WAV e tocar o WAV) são as partes em que "TheDesigner" interagiu.

A primeira parte a mencionar deve ser a parte em que o botão "Novo" é clicado, um novo item é adicionado à coleção, um novo TWavSound é adicionado à nossa frase de classe do formulário, e finalmente o TWavSound é selecionado para o objeto inspetor.

procedure TfmWavsEditor.sbNewClick(Sender: TObject);
var
  Wav: TWav;
  WavSound: TWavSound;
begin
  
//Adicionar um item para a coleção
  Wav := FWavs.Add;

  //Pedir ao TheDesigner para crier um novo componente TWavSound pra nós
  WavSound := TWavSound(TheDesigner.CreateComponent(TWavSound,
    nil, 0, 0, 0, 0));

  //Definir o Wav (CollectionItem) para apontar para o nosso novo componente TWavSound
  Wav.WavSound := WavSound;

  //Selecionar nosso novo TSoundComponent no objeto inspetor
  
//para que ele possa ser renomeado se desejado
  TheDesigner.SelectComponent(WavSound);

  //Internamente refreca os itens na listbox
  RefreshList;
  lbItems.ItemIndex := FWavs.Count-1;

  //Diz ao IDE que alguma coisa mudou
  TheDesigner.Modified;
end;


A segunda parte é mencionar que o correto TWavSound é selecionado para o objeto inspetor quando se clica em um item na ListBox.

procedure TfmWavsEditor.lbItemsClick(Sender: TObject);
begin
  with lbItems do
    if ItemIndex >=0 then
      TheDesigner.SelectComponent(FWavs[ItemIndex].WavSound);
end;


Evitando o acesso a violações

Finalmente, nós precisamos assegurar que não estamos referenciando um objeto que não é mais válida. Isto é simplesmente alcançado seguindo as duas etapas seguintes

    Certifique-se que o nosso formulário é notificado quando o componente que detém a nossa classe propriedade está destruído.

    Sobreponha o método Notificação do nosso formulário e feche o formulário se o componente relevante é destruído.

Para assegurar que somos comunicados quando o componente for destruído:

procedure TfmWavsEditor.Edit(AComponent: TComponent; AWavs: TWavs);
begin
  
//Primeiro nós precisamos remover a notificação para o componente atual
  if FComponent <> nil then
    FComponent.RemoveFreeNotification(Self);

  //Agora nós precisamos adicionar a notificação para o componente atual
  AComponent.FreeNotification(Self);
  FComponent := AComponent;

  FWavs := AWavs;
  lbItems.ItemIndex := -1;
  RefreshList;

  Show;
end;


O que fazer quando um componente é destruído:

procedure TfmWavsEditor.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if Operation = opRemove then
  begin
    
//Se o componente dominante é destruído
    //nós devemos fechar nosso formulário

    if (AComponent = FComponent) then
      Close
    else
    
//Se o componente que é destruído
    //nós atualizamos a nossa lista para caso afete o nosso  componente

    if (AComponent is TWavSound) then
      RefreshList;
  end;
end;


Conclusão

Neste artigo, nós abrangemos como escrever um editor componente, então passamos para a criação de simples editores de propriedade, finalmente nós cobrimos editores de propriedade mais avançados (incluindo a utilização mínima da interface IFormDesigner). Todas as técnicas demonstradas neste artigo (e mais) têm sido utilizadas nos meus componentes DIB (dispositivo independente bitmap). 

Esses componentes estão disponíveis para download gratuito em http://www.StuckIndoors.com/dib e são open-source (então quaisquer contribuições de desenvolvimento serão muito apreciados).