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;
|