Duplicating components at Run-Time - delphi

Is there a simple way to duplicate all child components under parent component, including their published properties?
For example:
TPanel
TLabel
TEdit
TListView
TSpecialClassX
Of course the most important factor, it should duplicate any new component which I drop on the TPanel without modifying the code under normal circumstances.
I've heard of the RTTI, but never used it actually. Any ideas?

You can propably use the CLoneProperties routine from the answer to "Replace visual component at runtime", after you have created the dup components in a loop thru the parent's controls.
Update: some working code....
. I assume from your question that you want to duplicate the Controls that are contained in a WinControl (as a Parent is a TWinControl).
. As I did not know if you also wanted to hook the duplicated controls with the same Event Handlers as the originals, I made an option for that.
. And you may want to give a proper meaningful Name to the duplicated controls.
uses
TypInfo;
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
procedure CloneEvents(Source, Dest: TControl);
var
I: Integer;
PropList: TPropList;
begin
for I := 0 to GetPropList(Source.ClassInfo, [tkMethod], #PropList) - 1 do
SetMethodProp(Dest, PropList[I], GetMethodProp(Source, PropList[I]));
end;
procedure DuplicateChildren(const ParentSource: TWinControl;
const WithEvents: Boolean = True);
var
I: Integer;
CurrentControl, ClonedControl: TControl;
begin
for I := ParentSource.ControlCount - 1 downto 0 do
begin
CurrentControl := ParentSource.Controls[I];
ClonedControl := TControlClass(CurrentControl.ClassType).Create(CurrentControl.Owner);
ClonedControl.Parent := ParentSource;
CloneProperties(CurrentControl, ClonedControl);
ClonedControl.Name := CurrentControl.Name + '_';
if WithEvents then
CloneEvents(CurrentControl, ClonedControl);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DuplicateChildren(Panel1);
end;

have a read of this page
Run-Time Type Information In Delphi - Can It Do Anything For You?
Noting the section Copying Properties From A Component To Another
which has a unit, RTTIUnit with a Procedure, which seems to do part of what you want but i don't think it will copy any child components with out extra code.
(i think its ok to paste it here...)
procedure CopyObject(ObjFrom, ObjTo: TObject);
var
PropInfos: PPropList;
PropInfo: PPropInfo;
Count, Loop: Integer;
OrdVal: Longint;
StrVal: String;
FloatVal: Extended;
MethodVal: TMethod;
begin
//{ Iterate thru all published fields and properties of source }
//{ copying them to target }
//{ Find out how many properties we'll be considering }
Count := GetPropList(ObjFrom.ClassInfo, tkAny, nil);
//{ Allocate memory to hold their RTTI data }
GetMem(PropInfos, Count * SizeOf(PPropInfo));
try
//{ Get hold of the property list in our new buffer }
GetPropList(ObjFrom.ClassInfo, tkAny, PropInfos);
//{ Loop through all the selected properties }
for Loop := 0 to Count - 1 do
begin
PropInfo := GetPropInfo(ObjTo.ClassInfo, PropInfos^[Loop]^.Name);
// { Check the general type of the property }
//{ and read/write it in an appropriate way }
case PropInfos^[Loop]^.PropType^.Kind of
tkInteger, tkChar, tkEnumeration,
tkSet, tkClass{$ifdef Win32}, tkWChar{$endif}:
begin
OrdVal := GetOrdProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetOrdProp(ObjTo, PropInfo, OrdVal);
end;
tkFloat:
begin
FloatVal := GetFloatProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetFloatProp(ObjTo, PropInfo, FloatVal);
end;
{$ifndef DelphiLessThan3}
tkWString,
{$endif}
{$ifdef Win32}
tkLString,
{$endif}
tkString:
begin
{ Avoid copying 'Name' - components must have unique names }
if UpperCase(PropInfos^[Loop]^.Name) = 'NAME' then
Continue;
StrVal := GetStrProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetStrProp(ObjTo, PropInfo, StrVal);
end;
tkMethod:
begin
MethodVal := GetMethodProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetMethodProp(ObjTo, PropInfo, MethodVal);
end
end
end
finally
FreeMem(PropInfos, Count * SizeOf(PPropInfo));
end;
end;

You can write the source component into a stream and read it back into the target component.
MemStream := TMemoryStream.Create;
try
MemStream.WriteComponent(Source);
MemStream.Position := 0;
MemStream.ReadComponent(Target);
finally
MemStream.Free;
end;
You may get problems with duplicate component names though.

It's actually fairly easy to duplicate existing components at runtime. The difficult part is to copy all of their published properties to the new (duplicated) objects.
I'm sorry, but my code example is in C++Builder. The VCL is the same, just a different language. It shouldn't be too much trouble to translate it Delphi:
for (i = 0; i < ComponentCount; ++i) {
TControl *Comp = dynamic_cast<TControl *>(Components[i]);
if (Comp) {
if (Comp->ClassNameIs("TLabel")) {
TLabel *OldLabel = dynamic_cast<TDBEdit *>(Components[i]);
TLabel *NewLabel = new TLabel(this); // new label
// copy properties from old to new
NewLabel->Top = OldLabel->Top;
NewLabel->Left = OldLabel->Left;
NewLabel->Caption = Oldlabel->Caption
// and so on...
} else if (Comp->ClassNameIs("TPanel")) {
// copy a TPanel object
}
Maybe somebody has a better method of copying all of the published properties of the old control to the new one.

Related

how to retain connections between controls when copying?

i want to ask how to retain controlls when im making a copy of a control. for example i have an edit box that can be controlled with a slider for value change. when i make a copy using this code i achieve a copy of the items but the slider stops controlling editbox values. how can i fix that?
TypInfo;
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
procedure CloneEvents(Source, Dest: TControl);
var
I: Integer;
PropList: TPropList;
begin
for I := 0 to GetPropList(Source.ClassInfo, [tkMethod], #PropList) - 1 do
SetMethodProp(Dest, PropList[I], GetMethodProp(Source, PropList[I]));
end;
procedure DuplicateChildren(const ParentSource: TWinControl;
const WithEvents: Boolean = True);
var
I: Integer;
CurrentControl, ClonedControl: TControl;
begin
for I := ParentSource.ControlCount - 1 downto 0 do
begin
CurrentControl := ParentSource.Controls[I];
ClonedControl := TControlClass(CurrentControl.ClassType).Create(CurrentControl.Owner);
ClonedControl.Parent := ParentSource;
CloneProperties(CurrentControl, ClonedControl);
ClonedControl.Name := CurrentControl.Name + '_';
if WithEvents then
CloneEvents(CurrentControl, ClonedControl);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DuplicateChildren(Panel1);
end;
Unless I'm misunderstanding you, your CloneProperties doesn't seem to have anything to do with the question you're asking. In your example of an edit control E1 and a slider S1, you can clone both of them to produce E2 and S2, but somewhere in your code there must be a statement that changes the value in E1 depending on the value of S1. However, in the way you've most likely written it, that statement doesn't apply to E2 and S2.
The simplest way around that is to write a method which takes the component instances and links the operation of the two together. e.g.
procedure TForm1.SetEditControlFromSlider(AnEdit : TEdit; ASlider : { TWhatever the slider actually is);
begin
// Set AnEdit's value from ASlider's properties
end;
Then, you can call this with Edit/Slider pairs like this
SetEditControlFromSlider(E1, S1);
[...]
SetEditControlFromSlider(E2, S2);
I can imagine you might not like having to do that.
IMO, the cleanest solution is to avoid attempting to clone components altogether and create a TFrame containing the Edit, Slider and the code that connects them, and then add to your form as many instances of the frame as you need. It's as easy as falling off a log.
type
TEditFrame = class(TFrame) // needs to be in its own unit, Used by your form
Edit1: TEdit;
TrackBar1: TTrackBar;
procedure TrackBar1Change(Sender: TObject);
private
public
end;
[...]
procedure TEditFrame.TrackBar1Change(Sender: TObject);
begin
Edit1.Text := IntToStr(TrackBar1.Position)
end;
Then, you can add clones of the frame to TForm1 by
procedure TForm1.Button1Click(Sender: TObject);
var
AFrame : TEditFrame;
begin
Inc(FrameCount); // Field of TForm1
AFrame := TEditFrame.Create(Self);
AFrame.Name := AFrame.Name + IntToStr(FrameCount);
AFrame.Parent := Self;
AFrame.Top := AFrame.Height * FrameCount;
end;
Note that because the code which links the two components, TrackBar1Change, it compiled into the frame's unit, it is automatically shared by every instance of the frame you create, without any need to "clone" the code.

Transforming online listview object - Delphi

I wonder how caught a row of a listview and transform object.
I carry an .xml file and play in a listview , after loading this file you need to double-click in a row, take all of the data line and throw in a LabelEdit , as shown in the code below .
procedure TForm1.LstbxDadosDblClick(Sender: TObject);
begin
if Assigned(TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex])) then
begin
with TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex]) do
begin
EdtPara.Text := Para;
EdtDe.Text := De;
EdtCabecalho.Text := Cabecalho;
EdtCorpo.Text := Corpo;
end;
end;
end;
TMensagem = class
private
FCorpo: String;
FCabecalho: String;
FPara: String;
FDe: String;
public
property Para : String read FPara write FPara;
property De : String read FDe write FDe;
property Cabecalho: String read FCabecalho write FCabecalho;
property Corpo : String read FCorpo write FCorpo;
end;
Many ways to edit an object where the current object can change at any time (like with a double click). Here is one of the easiest: save when the current object changes and save at the very end. Here is a quick and dirty solution.
Add a member to the form or global in the implementation section
FLastMensagem: TMensagem;
May want to initialize to nil on create or initialization (left to you). Now in the event save data when the TMensagem object changes
procedure TForm1.LstbxDadosDblClick(Sender: TObject);
var
LNewMensagem: TMensagem;
begin
LNewMensagem := TMensagem(LstbxDados.Items.Objects[LstbxDados.ItemIndex]));
if Assigned(LNewMensagem) then
begin
// When we switch, capture the dialog before updating it
if Assigned(FMensagem) and (LNewMensagem <> FLastMensagem) then
begin
FLastMensagem.Para := EdtPara.Text;
FLastMensagem.De := EdtDe.Text;
FLastMensagem.Cabecalho := EdtCabecalho.Text;
FLastMensagem.Corpo := EdtCorpo.Text;
end;
EdtPara.Text := LNewMensagem.Para;
EdtDe.Text := LNewMensagem.De;
EdtCabecalho.Text := LNewMensagem.Cabecalho;
EdtCorpo.Text := LNewMensagem.Corpo;
//Set the last dblclicked
FLastMensagem := LNewMensagem
end;
end;
Of course the very last edit needs to be saved, that you can do in say a form close (not sure what your full design is). For example
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Assigned(FLastMensagem) then
begin
FLastMensagem.Para := EdtPara.Text;
FLastMensagem.De := EdtDe.Text;
FLastMensagem.Cabecalho := EdtCabecalho.Text;
FLastMensagem.Corpo := EdtCorpo.Text;
end;
end;

StringGrid Objects - access violation

I am trying to make use of the Objects property of the Stringgrid inside my descendant and I think I am doing something wrong.
So, I created a simple class for use in my StringGrid cells, something like:
type
TKind = (tkNone,tkStart, tkEnd, tkMiddle);
TMyCell = class(TObject)
private
FKind:string; // TKind: start, middle, end, none
FOfType:string; // new, registered, paid, over, none
FActualDate:TDate;
FTheText:string; // if you want to show a text in it
FIsWeekend:Boolean;
function IsItWeekend(dt:Tdate):boolean;
procedure setKind(s:string);
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create;
property Kind:string read FKind write setKind;
property OfType:string read FOfType write FOfType;
property ActualDate:TDate read FActualDate write FActualDate;
property TheText:string read FTheText write FTheText;
property IsWeekend:Boolean read FIsWeekend write FIsWeekend default false;
{ Public declarations }
published
{ Published declarations }
end;
implementation
procedure TMyCell.setKind(s:string);
begin
FKind:=s;
end;
function TMyCell.IsItWeekend(dt:Tdate):boolean;
begin
if (DayOfTheWeek(dt)=6) or (DayOfTheWeek(dt)=7) then IsItWeekend:=true else IsItWeekend:=false;
end;
constructor TMyCell.Create;
var
i:integer;
a,l,z:word;
dt:TDate;
begin
FKind:='none';
FOfType:='none';
FActualDate:=now;
FTheText:='';
FIsWeekend:=IsItWeekend(FActualDate);
end;
then, in my StringGrid descendant (TMyGrid), I do the following:
TMyGrid = class(TStringGrid)
private
FStartSelection:integer;
FFixedColor:TColor;
FRowCount:integer;
...
published
property ColCount;
property RowCount;
...
constructor TMyGrid.Create(AOwner: TComponent);
var
i:integer;
a,l,z:word;
dt:TDate;
j: Integer;
myCell:TMyCell;
begin
inherited;
...// different settings
RowCount:=5;
for i := 0 to colCount-1 do
for j := 0 to RowCount-1 do
begin
Objects[i, j] := TMyCell.Create;
end;
end;
destructor TMyGrid.Destroy;
var
i,j:integer;
begin
for i := 0 to colCount-1 do
for j := 0 to RowCount-1 do
begin
TMyCell(Objects[i, j]).Free;
end;
inherited;
end;
... // other stuff
procedure Register;
begin
RegisterComponents('mygrid', [TMyGrid]);
end;
The problem is I don't know how do I tell my control that there are more rows when the developer changes the RowCount in the objectInspector before running the app.
So I drop my StrinGrid descendant on a form, and set the rowCount to 10. But my StringGrid does not have Objects created for the new rows, So the cells on ARow=5->9 do not have objects created... because in OnCreate I only set the initial value of the RowCount to 5 and create objects for i:=0 to RowCount-1.
Is there an event or method where I can tell the StringGrid to create the Objects after the developer changes the rowCount in ObjectInspector?
I am sure that this is my problem because, using the above code, when I drop my stringGrid on a form and set it's rowCount (design time or runtime) to 10 then I want to assign a value to Kind property of a cell that is on a Row>4 I get an AccessViolation, but if I do that for a row that is <= 4 the assignment works just fine.
I found something that should help here: http://embarcadero.newsgroups.archived.at/public.delphi.ide/200904/0904193279.html
but I do not know how and where to place this code in my StringGrid descendant class so it would get called when RowCount is changed at designtime/runtime
EDIT
After reading your comments I tried your idea (that seemed to work) to override the SizeChanged (I did not know that method existed, must have skipped it when I serached before).
Anyway, I added this code to my class:
TMyGrid = class(TStringGrid)
private
...
procedure SizeChanged(OldColCount, OldRowCount: Longint); override;
procedure UpdateGridDimensions(NewColCount, NewRowCount: Integer);
...
procedure TMyGrid.SizeChanged(OldColCount, OldRowCount: Longint);
begin
inherited;
if (OldRowCount<>FRowCount)or(OldColCount<>ColCount) then
UpdateGridDimensions(ColCount, FRowCount);
end;
procedure TMyGrid.UpdateGridDimensions(NewColCount, NewRowCount: Integer);
var
C, R: Integer;
Old: Integer;
begin
if NewColCount <> ColCount then
begin
if NewColCount < ColCount then
begin
for R := 0 to RowCount-1 do
begin
for C := ColCount-1 downto NewColCount do
Objects[C, R].Free;
end;
end;
Old := ColCount;
ColCount := NewColCount;
if NewColCount > Old then
begin
for R := 0 to RowCount-1 do
begin
for C := Old to NewColCount-1 do
Objects[C, R] := TMyCell.Create;
end;
end;
end;
if NewRowCount <> RowCount then
begin
if NewRowCount < RowCount then
begin
for C := 0 to ColCount-1 do
begin
for R := RowCount-1 downto NewRowCount do
Objects[C, R].Free;
end;
end;
Old := RowCount;
RowCount := NewColCount;
if NewRowCount > Old then
begin
for C := 0 to ColCount-1 do
begin
for R := Old to NewRowCount-1 do
Objects[C, R] := TMyCell.Create;
end;
end;
end;
end;
but now whenever I drop my control on a form, the rowcount is 93... where do I set that rowCount? Because I DONT.
And still, if I increase the RowCount from 93 to something else like 100, then my Objects exist for the first 93 rows but they do not get created for the 93-100 rows...
So the idea sounded great, but it does not work as I expect it...
Any thoughts?
Am I doing it wrong?
// SizeChanged - Called when the size of the grid has changed.
protected
procedure SizeChanged(OldColCount, OldRowCount: Longint); dynamic;
You can override dynamic method SizeChanged and initialize grid according to new size. You can check is it designtime or not (LU RD suggested link). And as David mentioned, it is better to keep Objects property for consumers of component. Create and use your own TList/TObjectList instead.

Use object in different function Delphi

This is just a very simple question to which i can't find a good clear answer to. I don't quite have the time to read all the documentation for this since i'm in a time crunch.
But here it is.
I have made a new class on top of my TForm class like so:
Bucket = Class
glass: Integer;
steel: Integer;
End;
I then create a couple of objects in a method which belongs to TForm1
procedure TForm1.getMarbles;
var
objPlastic: Bucket;
objAlu: Bucket;
begin
// Initialize objects
objPlastic := Bucket.Create;
objAlu := Bucket.Create;
// Get Values from edtBox
val(Edit1.Text, objPlastic.steel, code);
val(Edit2.Text, objAlu.steel, code);
val(Edit3.Text, objPlastic.glass, code);
val(Edit4.Text, objAlu.glass, code);
end;
My problem is that I don't know how to use these objects in other methods. I tried defining them in every way i know so far in the other methods I want to use them in, but I can't get it to work.
Here is the method and what I have it currently set to (which returns 0 all the time):
procedure TForm1.marbleDrop(kind: string);
var
objPlastic: Bucket;
I: Integer;
begin
objPlastic := Bucket.Create;
if kind= 'plastic' then // the function is receiving this parameter
begin
for I := 0 to objPlastic.glass do
begin
showmessage(inttostr(objPlastic.glass)); //returns 0
end;
end;
end;
Sorry for this kind of question, but i couldn't find a better way.
BTW, this is a simplified version of the code I am using. I did my best to get out any typos since it's a translation of what I am actually using, but it's mainly about the idea. I don't have typos in my code in delphi.
In other to access the objects across methods, you have to either:
declare the objects as members of the Form class:
type
TForm1 = class(TForm);
...
private
objPlastic: Bucket;
objAlu: Bucket;
...
end;
procedure TForm1.getMarbles;
begin
// Initialize objects
if objPlastic = nil then objPlastic := Bucket.Create;
if objAlu = nil then objAlu := Bucket.Create;
// Get Values from edtBox
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objAlu.steel := StrToIntDef(Edit2.Text, 0);
objPlastic.glass := StrToIntDef(Edit3.Text, 0);
objAlu.glass := StrToIntDef(Edit4.Text, 0);
end;
procedure TForm1.marbleDrop(kind: string);
begin
if (kind = 'plastic') and (objPlastic <> nil) then
begin
ShowMessage(IntToStr(objPlastic.glass));
end;
end;
pass them as parameters of the methods themselves:
procedure TForm1.getMarbles(objPlastic, objAlu: Bucket);
begin
// Get Values from edtBox
if objPlastic <> nil then
begin
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objPlastic.glass := StrToIntDef(Edit3.Text, 0);
end;
if objAlu <> nil then
begin
objAlu.steel := StrToIntDef(Edit2.Text, 0);
objAlu.glass := StrToIntDef(Edit4.Text, 0);
end;
end;
procedure TForm1.marbleDrop(objWhichKind: Bucket);
begin
if objWhichKind <> nil then
begin
ShowMessage(IntToStr(objWhichKind.glass));
end;
end;
procedure TForm1.someMethod();
var
objPlastic: Bucket;
begin
objPlastic := Bucket.Create;
getMarbles(objPlastic, nil);
marbleDrop(objPlastic);
objPlastic.Free;
end;
Of course it returns zero. It is another object. You should pass it as you pass any other parameter variable. What you made is similar to
procedure TForm1.Drop1(kind: string);
begin
marbleDrop(); // here kind = "staal"
end;
procedure TForm1.marbleDrop();
var
kind: string;
begin
if kind = 'plastic' then // it is not !!! why ???
begin
....
end;
end;
You also has another problem - Memory leak
val(Edit4.Text, objAlu.glass, code);
end;
You just created two objects - and allocated Heap memory for them.
But you did not freed them. That is garbage left and it will grow and grow and grow - until the program would exhaust all Windows memory and be killed.
If you want to use memory without any accuracy and without "wasting" your time on thinking and learning - you'd better user some managed language running in virtual machine, like PHP, Python, Java and other JVM-based, C# and other .NEt-based.
To make good Delphi code you should have at least some understanding what you CPU does and why.
Specifically in your code you'd better
use records instead of classes
pass them as const- or var-parameters to avoid redundant copying.
Like that:
type TBucket = Record glass, steel: Integer; End;
type TForm1 = class (TForm)
.....
private
var objPlastic, objAlu: TBucket;
(* making variables more global: now they are form-local not function-local *)
......
procedure TForm1.getMarbles;
begin
objPlastic.steel := StrToIntDef(Edit1.Text, 0);
objAlu.steel := ...
Self.objPlastic.glass ... (* adding Self - just for clarity where those variable are taken from *)
Self.objAlu.glass ....
end;
procedure TForm1.marbleDrop(kind: string);
var
I: Integer;
begin
if kind = 'plastic' then // the function is receiving this parameter
begin
for I := 0 to Self.objPlastic.glass do
begin
showmessage(inttostr(objPlastic.glass));
//getting via common parent context - TForm1 object, referenced as Self pseudo-variable
marbleTell(objPlastic); // passing as parameter
end;
end;
end;
procedure TForm1.marbleTell(const arg: TBucket);
// do not forget to use const to pass variable by-reference not by-value
begin
showmessage(inttostr(arg.glass)); // getting via argument
end;

Remove and Replace a visual component at runtime

Is it possible to, for instance, replace and free a TEdit with a subclassed component instantiated (conditionally) at runtime? If so, how and when it should be done? I've tried to set the parent to nil and to call free() in the form constructor and AfterConstruction methods but in both cases I got a runtime error.
Being more specific, I got an Access violation error (EAccessViolation). It seems François is right when he says that freeing components at frame costruction messes with Form controls housekeeping.
This more generic routine works either with a Form or Frame (updated to use a subclass for the new control):
function ReplaceControlEx(AControl: TControl; const AControlClass: TControlClass; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
if AControl = nil then
begin
Result := nil;
Exit;
end;
Result := AControlClass.Create(AControl.Owner);
CloneProperties(AControl, Result);// copy all properties to new control
// Result.Left := AControl.Left; // or copy some properties manually...
// Result.Top := AControl.Top;
Result.Name := ANewName;
Result.Parent := AControl.Parent; // needed for the InsertControl & RemoveControl magic
if IsFreed then
FreeAndNil(AControl);
end;
function ReplaceControl(AControl: TControl; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
if AControl = nil then
Result := nil
else
Result := ReplaceControlEx(AControl, TControlClass(AControl.ClassType), ANewName, IsFreed);
end;
using this routine to pass the properties to the new control
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
use it like:
procedure TFrame1.AfterConstruction;
var
I: Integer;
NewEdit: TMyEdit;
begin
inherited;
NewEdit := ReplaceControlEx(Edit1, TMyEdit, 'Edit2') as TMyEdit;
if Assigned(NewEdit) then
begin
NewEdit.Text := 'My Brand New Edit';
NewEdit.Author := 'Myself';
end;
for I:=0 to ControlCount-1 do
begin
ShowMessage(Controls[I].Name);
end;
end;
CAUTION: If you are doing this inside the AfterConstruction of the Frame, beware that the hosting Form construction is not finished yet.
Freeing Controls there, might cause a lot of problems as you're messing up with Form controls housekeeping.
See what you get if you try to read the new Edit Caption to display in the ShowMessage...
In that case you would want to use
...ReplaceControl(Edit1, 'Edit2', False)
and then do a
...FreeAndNil(Edit1)
later.
You have to call RemoveControl of the TEdit's parent to remove the control. Use InsertControl to add the new control.
var Edit2: TEdit;
begin
Edit2 := TEdit.Create(self);
Edit2.Left := Edit1.Left;
Edit2.Top := Edit2.Top;
Edit1.Parent.Insertcontrol(Edit2);
TWinControl(Edit1.parent).RemoveControl(Edit1);
Edit1.Free;
end;
Replace TEdit.Create to the class you want to use, and copy all properties you need like I did with Left and Top.
You can actually use RTTI (look in the TypInfo unit) to clone all the matching properties. I wrote code for this a while back, but I can't find it now. I'll keep looking.

Resources