2008-10-27 49 views
12

Có cách nào đơn giản để sao chép tất cả các thành phần con trong thành phần chính, bao gồm các thuộc tính đã xuất bản của chúng?Nhân bản các thành phần tại thời gian chạy

Ví dụ:

  • TPanel
    • TLabel
    • TEdit
    • TListView
    • TSpecialClassX

Tất nhiên là yếu tố quan trọng nhất, nó phải sao chép bất kỳ thành phần mới mà tôi thả trên TPanel mà không sửa đổi mã trong các trường hợp bình thường.

Tôi đã nghe về RTTI, nhưng chưa bao giờ sử dụng nó. Ý tưởng nào?

Trả lời

6

có một chi của trang này

Run-Time Type Information In Delphi - Can It Do Anything For You?

Ghi nhận phần Copying Properties From A Component To Another

trong đó có một đơn vị, RTTIUnit với một thủ tục, mà dường như để làm một phần của những gì bạn muốn nhưng tôi don' t nghĩ rằng nó sẽ sao chép bất kỳ thành phần con với mã ngoài. (tôi nghĩ rằng nó ok để dán nó ở đây ...)

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

Thực sự khá dễ dàng để sao chép thành phần hiện tại khi chạy. Phần khó khăn là sao chép tất cả các thuộc tính đã xuất bản của chúng sang các đối tượng mới (trùng lặp).

Tôi xin lỗi, nhưng ví dụ mã của tôi là trong C++ Builder. VCL là như nhau, chỉ là một ngôn ngữ khác. Nó không phải là quá nhiều rắc rối để dịch nó 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 
     } 

Có lẽ ai đó có phương pháp tốt hơn để sao chép tất cả các thuộc tính đã xuất bản của điều khiển cũ sang điều khiển mới.

9

Bạn propably có thể sử dụng CLoneProperties routine from the answer để "Replace visual component at runtime", sau khi bạn đã tạo ra các thành phần dup trong một vòng lặp qua kiểm soát của cha mẹ.

Cập nhật: một số mã làm việc ....

. Tôi giả định từ câu hỏi của bạn rằng bạn muốn nhân đôi các điều khiển được chứa trong một WinControl (như một phụ huynh là một TWinControl).
. Vì tôi không biết liệu bạn có muốn móc các điều khiển trùng lặp với cùng một Trình xử lý sự kiện như các bản gốc, tôi đã thực hiện một tùy chọn cho điều đó hay không.
. Và bạn có thể muốn đặt tên có ý nghĩa thích hợp cho các điều khiển trùng lặp.

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

Giải pháp rất tốt! Cảm ơn bạn đã giúp đỡ! –

3

Bạn có thể viết thành phần nguồn vào luồng và đọc lại thành thành phần đích.

MemStream := TMemoryStream.Create; 
try 
    MemStream.WriteComponent(Source); 
    MemStream.Position := 0; 
    MemStream.ReadComponent(Target); 
finally 
    MemStream.Free; 
end; 

Bạn có thể gặp sự cố với tên thành phần trùng lặp.

+1

@ Uwe, bạn nói đúng rằng tên thành phần trùng lặp sẽ là vấn đề nếu cả Nguồn và Mục tiêu chia sẻ cùng một cấp độ gốc. Một giải pháp là tạm thời đặt tên thành phần Nguồn thành chuỗi trống trước khi ghi nó vào Luồng. Sau khi đọc thành phần Target bạn có, bạn phải tìm một tên thích hợp cho thành phần đích nếu bạn muốn tồn tại thành phần đích vì Delphi không truyền các thành phần với một thuộc tính tên rỗng. – iamjoosy

Các vấn đề liên quan