Duplicating components and their children at runtime

The following code provides a function called DuplicateComponents that duplicates any given component and its child components at run time. It tries to emulate copying and pasting a component at design time. The new component is created with the same parentage and owner as the original and all new component names are similar (but different) to their original. This is provided as is and may have bugs that I haven't found yet. It is provided because it contains techniques that are not commonly known and may be of use to people struggling with similar problems.

This procedure is very useful when you want to design a section of an interface once that will appear n times at run time. You just design it once visually all on a TPanel or other component as a parent, and then do "newpanel := DuplicateComponents(designedpanel)".


uses

  SysUtils, Windows, Messages, Classes, Graphics, Controls,

  Forms, Dialogs, ExtCtrls, StdCtrls, IniFiles, TypInfo, Debug;

 

type

  TUniqueReader = Class(TReader)

  LastRead: TComponent;

  procedure ComponentRead(Component: TComponent);

  procedure SetNameUnique(

  Reader: TReader;

  Component: TComponent;

  var Name: string

  );

  end;

 

implementation

 

procedure TUniqueReader.ComponentRead(

  Component: TComponent

);

begin

  LastRead := Component;

end;

 

procedure TUniqueReader.SetNameUnique( // sets the name of the read

component to something like "Panel2" if "Panel1" already exists

  Reader: TReader;

  Component: TComponent; // component being read

  var Name: string // Name to use and modify

);

var

  i: Integer;

  tempname: string;

begin

  i := 0;

  tempname := Name;

  while Component.Owner.FindComponent(Name) <> nil do begin

  Inc(i);

  Name := Format('%s%d', [tempname, i]);

  end;

end;

 

 

function DuplicateComponents(

  AComponent: TComponent // original component

): TComponent; // returns created new component

  procedure RegisterComponentClasses(

  AComponent: TComponent

  );

  var

  i : integer;

  begin

  RegisterClass(TPersistentClass(AComponent.ClassType));

  if AComponent is TWinControl then

  if TWinControl(AComponent).ControlCount > 0 then

  for i := 0 to

(TWinControl(AComponent).ControlCount-1) do

 

RegisterComponentClasses(TWinControl(AComponent).Controls[i]);

  end;

 

var

  Stream: TMemoryStream;

  UniqueReader: TUniqueReader;

  Writer: TWriter;

begin

  result := nil;

  UniqueReader := nil;

  Writer := nil;

 

  try

  Stream := TMemoryStream.Create;

  RegisterComponentClasses(AComponent);

 

  try

  Writer := TWriter.Create(Stream, 4096);

  Writer.Root := AComponent.Owner;

  Writer.WriteSignature;

  Writer.WriteComponent(AComponent);

  Writer.WriteListEnd;

  finally

  Writer.Free;

  end;

 

  Stream.Position := 0;

  try

  UniqueReader := TUniqueReader.Create(Stream, 4096); // create reader

  // should probably move these routines into theconstructor

  UniqueReader.OnSetName := UniqueReader.SetNameUnique;

  UniqueReader.LastRead := nil;

 

  if AComponent is TWinControl then

 

UniqueReader.ReadComponents(

// read in components and sub-components

  TWinControl(AComponent).Owner,

  TWinControl(AComponent).Parent,

  UniqueReader.ComponentRead

  )

  else

 

UniqueReader.ReadComponents(

// read in components

  AComponent.Owner,

  nil,

  UniqueReader.ComponentRead

  );

  result := UniqueReader.LastRead;

  finally

  UniqueReader.Free;

  end;

  finally

  Stream.Free;

  end;

end;