Клонирование компонентов VCL

в 13:16, , рубрики: Delphi, метки:

Embarcadero в FMX заботливо предусмотрело клонирование, которе иногда может приятно упростить жизнь разработчика. VCL же явного инструмента клонирования в run-time не предоставляет.

Для чего это может быть использованно? Конечно же для клонирования визуальных компонентов по шаблону. Приемущества и недостатки данного подхода я предпочту оставить для гуру.

Далее я просто приведу код и свои комментарии:

unit Clonable;

interface

uses
  System.SysUtils, System.Classes, System.TypInfo, Vcl.Controls, StrUtils;

{ extending }
type
  TClonable = class(TComponent)
  private
    procedure CopyComponentProp(Source, Target: TObject; aExcept: array of string);
  public
    function Clone(const AOwner: TComponent; aExcept: array of string): TComponent;
  end;


implementation


procedure TClonable.CopyComponentProp(Source, Target: TObject; aExcept: array of string);
var
  I, Index: Integer;
  PropName: string;
  Source_PropList  , Target_PropList  : PPropList;
  Source_NumProps  , Target_NumProps  : Word;
  Source_PropObject, Target_PropObject: TObject;
  { property list finder }
  function FindProperty(const PropName: string; PropList: PPropList; NumProps: Word): Integer;
  var
    I: Integer;
  begin
    Result:= -1;
    for I:= 0 to NumProps - 1 do
      if CompareStr(PropList^[I]^.Name, PropName) = 0 then begin
        Result:= I;
        Break;
      end;
  end;
begin
  if not Assigned(Source) or not Assigned(Target) then Exit;
  Source_NumProps:= GetTypeData(Source.ClassInfo)^.PropCount;
  Target_NumProps:= GetTypeData(Target.ClassInfo)^.PropCount;
  GetMem(Source_PropList, Source_NumProps * SizeOf(Pointer));
  GetMem(Target_PropList, Target_NumProps * SizeOf(Pointer));
  try
    { property list }
    GetPropInfos(Source.ClassInfo, Source_PropList);
    GetPropInfos(Target.ClassInfo, Target_PropList);
    for I:= 0 to Source_NumProps - 1 do begin
      PropName:= Source_PropList^[I]^.Name;
      if  (AnsiIndexText('None'  , aExcept                ) =  -1) and
         ((AnsiIndexText(PropName, ['Name', 'Left', 'Top']) <> -1) or
          (AnsiIndexText(PropName, aExcept                ) <> -1)) then Continue;
      Index:= FindProperty(PropName, Target_PropList, Target_NumProps);
      if Index = -1 then Continue; {no property found}
      { compare types }
      if Source_PropList^[I]^.PropType^.Kind <> Target_PropList^[Index]^.PropType^.Kind then
        Continue;
      case Source_PropList^[I]^.PropType^^.Kind of
        tkClass:  begin
                    Source_PropObject:= GetObjectProp(Source, Source_PropList^[I    ]);
                    Target_PropObject:= GetObjectProp(Target, Target_PropList^[Index]);
                    CopyComponentProp(Source_PropObject, Target_PropObject, ['None']);
                  end;
        tkMethod: SetMethodProp(Target, PropName, GetMethodProp(Source, PropName));
      else SetPropValue(Target, PropName, GetPropValue(Source, PropName));
      end;
    end;
  finally
    FreeMem(Source_PropList);
    FreeMem(Target_PropList);
  end;
end;


function IsUniqueGlobalNameProc(const Name: string): Boolean;
begin
  if Length(Name) = 0 then
    Result := True
  else
    Result := Not Assigned(FindGlobalComponent(Name));
end;


function TClonable.Clone(const AOwner: TComponent; aExcept: array of string): TComponent;
var
  S: TStream;
  SaveName: string;
  Reader: TReader;
  FSaveIsUniqueGlobalComponentName: TIsUniqueGlobalComponentName;
  I: Integer;
  Child: TComponent;
  LComponent: TComponent;
begin
  { for simple compatible }
  LComponent:=Self;
  { register self type }
  RegisterClass(TPersistentClass(LComponent.ClassType));
  S := TMemoryStream.Create;
  Result := nil;
  try
    { store }
    SaveName := LComponent.Name;
    Self.Name := '';
    S.WriteComponent(LComponent);
    LComponent.Name := SaveName;
    S.Position := 0;
    { load }
    FSaveIsUniqueGlobalComponentName := IsUniqueGlobalComponentNameProc;
    IsUniqueGlobalComponentNameProc := IsUniqueGlobalNameProc;
    try
      Reader := TReader.Create(S, 4096);
      try
        Result := TComponent(Reader.ReadRootComponent(nil));
        if Assigned(AOwner) then
          AOwner.InsertComponent(Result);
      finally
        Reader.Free;
        if not Assigned(Result) then
          Result := TComponentClass(LComponent.ClassType).Create(AOwner);
      end;
    finally
      IsUniqueGlobalComponentNameProc := FSaveIsUniqueGlobalComponentName;
    end;
  finally
    S.Free;
  end;
  {parent}
  if (LComponent is TControl) and (LComponent as TControl).HasParent then
    (Result as TControl).Parent:=(LComponent as TControl).Parent;
  { copy propertys value }
  CopyComponentProp(LComponent, Result, aExcept);
  { childs }
  if (LComponent is TWinControl) and ((LComponent as TWinControl).ControlCount > 0) then
    for I := 0 to (LComponent as TWinControl).ControlCount - 1 do begin
      Child:=
      TClonable(
        (LComponent as TWinControl).
          Controls[I]).
          Clone(LComponent, aExcept);
      if (Child is TControl) then
        (Child as TControl).Parent:=(Result as TWinControl);
    end;
end;

end.

Пример использования:

procedure TForm1.Button1Click(Sender: TObject);
var
  Clone: TPanel;
begin
  Clone:=TPanel(TClonable(Panel1).Clone(Self, []));
  Clone.Top:=Panel1.Top+Panel1.Height;
end;

Описание метода Clone класса TClonable:
function Clone(const AOwner: TComponent; aExcept: array of string): TComponent;

  • AOwner: TComponent — новый владелец клонируемого компонента
  • aExcept: array of string — массив строк, содержащий названия свойств (имеется ввиду PPropList) для исключения при копировании
  • Result — ссылка на новый объект класса TComponent представляюая копию исходного объекта, свойтво Name пустое

На мой взгляд, реализация предоставляет удобный способ копирования компонентов: с предусмотренным механизмом копирования дочерних объектов, которые могут содержать наследники TWinControl; переназначением событий; возможностью исключать ненужные свойства.

Ни в коем случае не претендую на новшество, имею ввиду велосипед, надеюсь на то что не костыль =)

Автор: AndyBW

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js