Дублирование компонентов во время выполнения

Есть ли простой способ дублировать все дочерние компоненты родительского компонента, включая их опубликованные свойства?

Например:

  • TPanel
    • TLabel
    • TEdit
    • TListView
    • TSpecialClassX

Конечно, наиболее важным фактором является то, что он должен дублировать любой новый компонент, который я добавляю на TPanel, без изменения кода при нормальных обстоятельствах.

Я слышал о RTTI, но на самом деле никогда им не пользовался. Любые идеи?


person Atlas    schedule 27.10.2008    source источник


Ответы (4)


прочитайте эту страницу

Информация о типе времени выполнения в Delphi - может ли он что-нибудь для вас сделать?

Обратите внимание на раздел Копирование свойств из компонента в другой

в котором есть модуль 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;
person Community    schedule 27.10.2008

Возможно, вы можете использовать процедуру CLoneProperties из ответа на «Заменить визуальный компонент во время выполнения» после того, как создал дублирующие компоненты в цикле через родительские элементы управления.

Обновление: какой-то рабочий код ....

. Из вашего вопроса я предполагаю, что вы хотите продублировать элементы управления, содержащиеся в WinControl (поскольку родительский элемент - это 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;
person Francesca    schedule 27.10.2008
comment
Это решение, однако, не поддерживает многоуровневый дочерний элемент управления (например: TPanel, содержащий TPanel, содержащий что-либо). - person Didier Cabalé; 17.02.2020
comment
Я делаю что-то не так или это не копирует, например, соединение с ползунком для редактирования? потому что мой слайдер перестал контролировать поле редактирования после копирования - person Rolandas Ulevicius; 27.05.2020

Вы можете записать исходный компонент в поток и прочитать его обратно в целевой компонент.

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

Однако у вас могут возникнуть проблемы с дублированием имен компонентов.

person Uwe Raabe    schedule 27.10.2008
comment
@ Уве, вы правы, что повторяющиеся имена компонентов будут проблемой, если и источник, и цель имеют одного и того же родителя. Одно из решений - временно установить в качестве имени компонента Source пустую строку перед ее записью в Stream. После прочтения целевого компонента вам необходимо найти собственное имя для целевого компонента, если вы хотите сохранить целевой компонент, поскольку Delphi не выполняет потоковую передачу компонентов с пустым свойством имени. - person iamjoosy; 29.11.2011

На самом деле довольно легко дублировать существующие компоненты во время выполнения. Сложность состоит в том, чтобы скопировать все их опубликованные свойства в новые (дублированные) объекты.

Извините, но мой пример кода написан на 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
        }

Может быть, у кого-нибудь есть лучший способ скопировать все опубликованные свойства старого элемента управления в новый.

person Kluge    schedule 27.10.2008