有没有一种简单的方法可以复制父组件下的所有子组件,包括它们的已发布属性?
例如:
- 面板
- 标签
- 编辑
- 列表视图
- TSpecialClassX
当然,最重要的因素是,它应该复制我在 TPanel 上放置的任何新组件,而在正常情况下不修改代码。
我听说过 RTTI,但实际上从未使用过。有任何想法吗?
在通过父控件循环创建 dup 组件之后, 您可以适当地使用“在运行时替换可视组件”的答案中的 CLoneProperties 例程。
更新:一些工作代码....
. 我从您的问题中假设您想要复制 WinControl 中包含的控件(因为 Parent 是 TWinControl)。
. 由于我不知道您是否还想将复制的控件与与原始控件相同的事件处理程序挂钩,因此我为此做了一个选项。
. 您可能希望为重复的控件提供一个适当的有意义的名称。
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;
阅读此页面
它有一个单元,带有过程的 RTTIUnit,它似乎可以完成您想要的部分工作,但我认为它不会复制任何子组件而无需额外的代码。 (我认为可以将其粘贴在这里...)
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;
您可以将源组件写入流并将其读回目标组件。
MemStream := TMemoryStream.Create;
try
MemStream.WriteComponent(Source);
MemStream.Position := 0;
MemStream.ReadComponent(Target);
finally
MemStream.Free;
end;
但是,您可能会遇到重复组件名称的问题。
在运行时复制现有组件实际上相当容易。困难的部分是将所有已发布的属性复制到新的(复制的)对象中。
抱歉,我的代码示例在 C++Builder 中。VCL 是一样的,只是语言不同。用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
}
也许有人有更好的方法将旧控件的所有已发布属性复制到新控件。