Remove and Replace a visual component at runtime

守給你的承諾、 提交于 2019-12-18 13:28:42

问题


Is it possible to, for instance, replace and free a TEdit with a subclassed component instantiated (conditionally) at runtime? If so, how and when it should be done? I've tried to set the parent to nil and to call free() in the form constructor and AfterConstruction methods but in both cases I got a runtime error.


Being more specific, I got an Access violation error (EAccessViolation). It seems François is right when he says that freeing components at frame costruction messes with Form controls housekeeping.


回答1:


This more generic routine works either with a Form or Frame (updated to use a subclass for the new control):

function ReplaceControlEx(AControl: TControl; const AControlClass: TControlClass; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
  if AControl = nil then
  begin
    Result := nil;
    Exit;
  end;
  Result := AControlClass.Create(AControl.Owner);
  CloneProperties(AControl, Result);// copy all properties to new control
  // Result.Left := AControl.Left;   // or copy some properties manually...
  // Result.Top := AControl.Top;
  Result.Name := ANewName;
  Result.Parent := AControl.Parent; // needed for the InsertControl & RemoveControl magic
  if IsFreed then
    FreeAndNil(AControl);
end;

function ReplaceControl(AControl: TControl; const ANewName: string; const IsFreed : Boolean = True): TControl;
begin
  if AControl = nil then
    Result := nil
  else
    Result := ReplaceControlEx(AControl, TControlClass(AControl.ClassType), ANewName, IsFreed);
end;

using this routine to pass the properties to the new control

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;

use it like:

procedure TFrame1.AfterConstruction;
var
  I: Integer;
  NewEdit: TMyEdit;
begin
  inherited;
  NewEdit := ReplaceControlEx(Edit1, TMyEdit, 'Edit2') as TMyEdit;
  if Assigned(NewEdit) then
  begin
    NewEdit.Text := 'My Brand New Edit';
    NewEdit.Author := 'Myself';
  end;
  for I:=0 to ControlCount-1 do
  begin
    ShowMessage(Controls[I].Name);
  end;
end;

CAUTION: If you are doing this inside the AfterConstruction of the Frame, beware that the hosting Form construction is not finished yet.
Freeing Controls there, might cause a lot of problems as you're messing up with Form controls housekeeping.
See what you get if you try to read the new Edit Caption to display in the ShowMessage...
In that case you would want to use
...ReplaceControl(Edit1, 'Edit2', False)
and then do a
...FreeAndNil(Edit1)
later.




回答2:


You have to call RemoveControl of the TEdit's parent to remove the control. Use InsertControl to add the new control.

var Edit2: TEdit;
begin
  Edit2 := TEdit.Create(self);
  Edit2.Left := Edit1.Left;
  Edit2.Top := Edit2.Top;
  Edit1.Parent.Insertcontrol(Edit2);
  TWinControl(Edit1.parent).RemoveControl(Edit1);
  Edit1.Free;
end;

Replace TEdit.Create to the class you want to use, and copy all properties you need like I did with Left and Top.




回答3:


You can actually use RTTI (look in the TypInfo unit) to clone all the matching properties. I wrote code for this a while back, but I can't find it now. I'll keep looking.



来源:https://stackoverflow.com/questions/120858/remove-and-replace-a-visual-component-at-runtime

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!