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.
Related
I want to know the best way to free a TList filled with record.
I have the following record:
type
TPkBill = record
PkBill: integer;
Constructor Create(c_PkBill: integer);
constructor TPkBill.Create(c_PkBill: integer);
begin
PkBill := c_PkBill;
end;
I create the list and fill it with the record:
procedure TfrmProject.lvBillDblClick(Sender: TObject);
var
i, iCount: integer;
item: TListItem;
oPkBill: TPkBill;
lstPkBill: Tlist;
begin
iCount := 0;
lstPkBill:= TList.Create;
//if an item is selected in lv
if (lvBill.ItemIndex = -1) then begin exit; end
else
begin
//Loop through all items and get selected item
for i := 0 to lvBill.Items.Count - 1 do
begin
item := lvBill.Items.Item[i];
if(item.Selected = true)then
begin
//create new item
oPkBill := TPkBill.Create(StrToInt(lvBill.Items[i].Caption));
//add it to a list
lstPkBill.Add(TObject(oPkBill));
//add up
iCount := iCount +1;
end;
end;
//Now we have a list ok pkBill
if(iCount > 1)then //other stuff I do
end
I want to be able to free the TList and also to free the record.
Here is what I already tried:
for i := 0 to lstPkBill.Count - 1 do
begin
//TObject(TPkBill(lstPkBill[i])).Free; //Acces violation at adress..
//FreeMem(TPkBill(lstPkBill[i])); //Incompatible types
//FreeMem(TObject(lstPkBill[i])); //Incompatible types
end;
lstPkBill.Clear;
FreeAndNil(lstPkBill);
Thanks you for the help, it's appreciated!
You have defined a record with a constructor. Calling a record constructor does not allocate memory on the heap, like a class constructor does. Your oPkBill variable exists on the stack. Calling oPkBill := TPkBill.Create(...) merely populates the members of that variable. You are then type-casting that entire variable (which only contains one Integer member) into a TObject pointer. You are not actually allocating any memory on the heap for the list item, so there is no need to free them.
I suspect what you were actually trying to do is something more like this:
type
PPkBill = ^TPkBill;
TPkBill = record
PkBill: integer;
Constructor Create(c_PkBill: integer);
end;
constructor TPkBill.Create(c_PkBill: integer);
begin
PkBill := c_PkBill;
end;
procedure TfrmProject.lvBillDblClick(Sender: TObject);
var
i: Integer;
item: TListItem;
oPkBill: PPkBill;
lstPkBill: TList;
begin
if lvBill.ItemIndex = -1 then Exit;
//an item is selected in lv
lstPkBill := TList.Create;
try
//Loop through all items and get selected items
for i := 0 to lvBill.Items.Count - 1 do
begin
item := lvBill.Items.Item[i];
if item.Selected then
begin
//create new item
New(oPkBill);
try
oPkBill^ := TPkBill.Create(StrToInt(lvBill.Items[i].Caption));
//add it to a list
lstPkBill.Add(oPkBill);
except
Dispose(oPkBill);
raise;
end;
end;
end;
//Now we have a list ok pkBill
if (lstPkBill.Count > 1) then
begin
//other stuff I do
end;
finally
for i := 0 to lstPkBill.Count - 1 do
Dispose(PPkBill(lstPkBill[i]));
lstPkBill.Free;
end;
end;
I have a function to update a cxGrid made with help from answers to Loop through records on a cxgrid and update a field/column
But it is sometimes acting a bit strange. If I open the form with the cxGrid and click the columnheader without doing anything else, the records are updateted OK. But if the 'selectorbar' is moved away from the top, the record marked is not updated.
I am sure it is a property that needs to be changed, but which one.
The variable fSelected is set to False at FormShow and is ther so that the user can unselect records as well.
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
BookMark : TBookMark;
Contact: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
BookMark := qryContacts.GetBookmark;
qryContacts.DisableControls;
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
Contact := grdContactsView1.DataController.Values[Index, 4];
if grdContactsView1.DataController.LocateByKey(Contact) then
begin
qryContacts.Edit;
qryContacts.FieldByName('fldcontact_selected').AsBoolean := fSelected;
qryContacts.Post;
end;
end;
finally
qryContacts.EnableControls;
qryContacts.GotoBookmark(BookMark);
qryContacts.FreeBookmark(BookMark);
end;
Screen.Cursor := crDefault;
end;
end;
Delphi XE7, DevExpress 14.2.2, UniDAC 5.5.12 for DB access
Comment:
I have ended up with the following solution based on the answer and input from MartynA
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
MarkedRecord: variant;
CurrentRecord: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
Index := grdContactsView1.DataController.FocusedRecordIndex;
MarkedRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
CurrentRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
if grdContactsView1.DataController.LocateByKey(CurrentRecord) then
begin
grdContactsView1.DataController.Edit;
grdContactsView1.DataController.SetEditValue(colContactSelected.ID, fSelected, evsText);
grdContactsView1.DataController.Post;
end;
end;
finally
grdContactsView1.DataController.LocateByKey(MarkedRecord);
end;
Screen.Cursor := crDefault;
end;
end;
I can reproduce your problem using the sample project I posted in my answer to your other q.
Try this: Add a TMemo to your form, and inside the 'if grdContactsView1.DataController.LocateByKey(Contact) then' block, write the value of a row-unique datafield and the Selected datafield value to the memo.
Then, what I get when some row other than the top row is selected is that one row is listed twice in the memo, with Selected both false and true, and one of the rows in the filter isn't listed at all, which I think accounts for the behaviour you're seeing. If I then comment out the .Edit .. .Post lines, it correctly lists all the rows in the filter.
So evidently doing the Selected field changes inside a block which iterated the FilteredRecordIndex property of the DBTableView is what's causing the problem.
Personally, I find that it goes a bit against the grain to modify dataset rows in code via a DB-aware control (because you usually end up fighting the DB-awareness of the control), but in this case, it's straightforward to do the processing via the DBTableView of the cxGrid.
procedure TForm1.ProcessFilteredRecords;
var
PrevV,
V : Variant;
i,
Index: Integer;
S : String;
begin
// First, pick up a reference to the current record
// so that we can return to it afterwards
Index := cxGrid1DBTableView1.DataController.FocusedRecordIndex;
PrevV := cxGrid1DBTableView1.DataController.Values[Index, 0];
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
cxGrid1DBTableView1.DataController.Edit;
// 2 is the index of my Selected column in the grid
if cxGrid1DBTableView1.DataController.SetEditValue(2, True, evsText) then
Caption := 'OK'
else
Caption := 'Failed';
cxGrid1DBTableView1.DataController.Post;
end;
end;
finally
if cxGrid1DBTableView1.DataController.LocateByKey(PrevV) then
Caption := 'OK'
else
Caption := 'Failed';
end;
end;
Another way to avoid the problem is to change the Selected states in two steps:
Iterate the FilteredRecordIndex to build a list of rows to change - in your case this would be a list of guids
Then, iterate the list of rows and update their Selected states.
Code:
procedure TForm1.ProcessFilteredRecords;
var
V : Variant;
i,
Index: Integer;
BM : TBookMark;
S : String;
TL : TStringList;
begin
Memo1.Lines.Clear;
TL := TStringList.Create;
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
if CDS1.FieldByName('Selected').AsBoolean then
S := 'True'
else
S := 'False';
S := CDS1.FieldByName('Name').AsString + ' ' + S;
Memo1.Lines.Add(S);
TL.Add(CDS1.FieldByName('Guid').AsString);
end;
end;
try
BM := CDS1.GetBookMark;
CDS1.DisableControls;
for i := 0 to TL.Count - 1 do begin
if CDS1.Locate('guid', TL[i], []) then begin
CDS1.Edit;
CDS1.FieldByName('Selected').AsBoolean := True;
CDS1.Post;
end
end;
finally
CDS1.EnableControls;
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
end;
finally
TL.Free;
end;
end;
Like you, I was expecting that changing a property or two of the cxGrid might avoid the problem without any code, but I haven't been able to find anything which does.
Hi i am having a problem with incremental search in delphi.
I Have looked at this http://delphi.about.com/od/vclusing/a/lb_incremental.htm
But this doesn't work in firemonkey so i came up with this :
for I := 0 to lstbxMapList.Items.Count-1 do
begin
if lstbxMapList.Items[i] = edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := True;
end;
if lstbxMapList.Items[I] <> edtSearch.Text then
begin
lstbxMapList.ItemByIndex(i).Visible := False;
end;
end;
When i use this the listbox is just blank.
You're hiding every item that doesn't exactly match edtSearch.Text. Try this instead (tested in XE3):
// Add StrUtils to your uses clause for `StartsText`
uses
StrUtils;
procedure TForm1.edtSearchChange(Sender: TObject);
var
i: Integer;
NewIndex: Integer;
begin
NewIndex := -1;
for i := 0 to lstBxMapList.Items.Count - 1 do
if StartsText(Edit1.Text, lstBxMapList.Items[i]) then
begin
NewIndex := i;
Break;
end;
// Set to matching index if found, or -1 if not
lstBxMapList.ItemIndex := NewIndex;
end;
Following from Kens answer, if you want to hide items as per your question, just set the Visible property but note that since the expression of an if statement returns a boolean and Visible is a boolean property it's possible to greatly simplify things. Note also that I've also used ContainsText which will match the string anywhere in the item text:
procedure TForm1.edtSearchChange(Sender: TObject);
var
Item: TListBoxItem;
begin
for Item in lstbxMapList.ListItems do
Item.Visible := ContainsText(Item.Text.ToLower, Edit1.Text.ToLower);
end;
I have grid panel 16 x 4 like this one:
Sometimes i want to hide some rows and to move bottom rows up. When I set component visible property to false the layout is not updated:
Nevertheless the row size type is set to auto:
Why the component don't set row height to zero when there is nothing to display?
Why the component don't set row height to zero when there is nothing to display ?
Because the row is considered as empty only if there are no components in all columns in that row, not if they're visible or not. So the same returns the IsRowEmpty method. To workaround this, you'd need to be notified by the cell component about its visibility change. When this notification is generated, you can check the row just like the IsRowEmpty method does, except you'll check if the controls are visible, not if they're assigned. Based on the result of such method, you can then set the size of the Value to 0 to hide the row.
With a help of interposed class, the method for checking if all controls in a row or column are visible, you might write something like this. Those methods return True, when all existing controls in a certain row or column are visible, False otherwise:
uses
ExtCtrls, Consts;
type
TGridPanel = class(ExtCtrls.TGridPanel)
public
function IsColContentVisible(ACol: Integer): Boolean;
function IsRowContentVisible(ARow: Integer): Boolean;
end;
implementation
function TGridPanel.IsColContentVisible(ACol: Integer): Boolean;
var
I: Integer;
Control: TControl;
begin
Result := False;
if (ACol > -1) and (ACol < ColumnCollection.Count) then
begin
for I := 0 to ColumnCollection.Count -1 do
begin
Control := ControlCollection.Controls[I, ACol];
if Assigned(Control) and not Control.Visible then
Exit;
end;
Result := True;
end
else
raise EGridPanelException.CreateFmt(sInvalidColumnIndex, [ACol]);
end;
function TGridPanel.IsRowContentVisible(ARow: Integer): Boolean;
var
I: Integer;
Control: TControl;
begin
Result := False;
if (ARow > -1) and (ARow < RowCollection.Count) then
begin
for I := 0 to ColumnCollection.Count -1 do
begin
Control := ControlCollection.Controls[I, ARow];
if Assigned(Control) and not Control.Visible then
Exit;
end;
Result := True;
end
else
raise EGridPanelException.CreateFmt(sInvalidRowIndex, [ARow]);
end;
And the usage shown for the first row:
procedure TForm1.Button1Click(Sender: TObject);
begin
// after you update visibility of controls in the first row...
// if any of the controls in the first row is not visible, change the
// row's height to 0, what makes it hidden, otherwise set certain height
if not GridPanel1.IsRowContentVisible(0) then
GridPanel1.RowCollection[0].Value := 0
else
GridPanel1.RowCollection[0].Value := 50;
end;
I've got a hacky solution ... keeping the Autosizing
Procedure ShowHideControlFromGrid(C:TControl);
begin
if C.Parent = nil then
begin
c.Parent := TWinControl(c.Tag)
end
else
begin
c.Tag := NativeInt(C.Parent);
c.Parent := nil;
end;
end;
procedure TForm4.Button1Click(Sender: TObject);
begin // e.g. Call
ShowHideControlFromGrid(Edit5);
ShowHideControlFromGrid(Edit6);
ShowHideControlFromGrid(Edit7);
ShowHideControlFromGrid(Label1);
end;
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.