在运行时删除并替换可视化组件

7

是否有可能在运行时使用子类化组件替换和释放TEdit?如果是这样,应该如何做?我尝试在表单构造函数和AfterConstruction方法中将父级设置为nil并调用free(),但在两种情况下都出现了运行时错误。


更具体地说,我遇到了访问冲突错误(EAccessViolation)。当释放帧构造中的组件时,似乎会影响表单控件的管理,弗朗索瓦是正确的。

3个回答

8

您需要调用TEdit的父控件的RemoveControl方法来删除该控件。使用InsertControl方法添加新控件。

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;

将TEdit.Create替换为您想要使用的类,并像我一样复制您需要的所有属性,例如Left和Top。


Loesje的代码在TMyForm.AfterConstruction方法中可以工作,但在TMyFrame.AfterConstruction中却不行。如果我要在TFrame对象中使用它,应该把它放在哪里? - user16120
我在重写的TMyForm.AfterConstruction中尝试了这段代码,效果很好。所以也许弗朗索瓦的警告是正确的。尝试删除Edit1.Free。但我怀疑这不是关键,因为AfterConstruction在所有构造函数完成后调用。但你现在的问题是什么?“不起作用”并不是非常具体的描述。你是否收到AV?或者它只是什么都不做?如果是这种情况,你确定代码被执行了吗?(尝试添加断点) - Loesje
为什么不设置Parent属性,而是使用InsertControl和RemoveControl呢? - Lars Truijens
仅仅因为在问题中提到了父属性无法工作。我记得曾经写过一些使用InsertControl和RemoveControl的代码。所以我就没有尝试使用Parent属性。 - Loesje

8

这个更通用的程序可以与表单或框架一起使用(更新为使用新控件的子类):

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;

使用此程序将属性传递给新控件。
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 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;

注意:如果您正在Frame的AfterConstruction中进行此操作,请注意,主机Form的构造尚未完成。
在那里释放控件可能会导致许多问题,因为您正在干扰Form控件维护。
看看如果您尝试读取新的Edit Caption以在ShowMessage中显示会得到什么...
在这种情况下,您将想使用
...ReplaceControl(Edit1, 'Edit2', False)
然后再进行
...FreeAndNil(Edit1)
处理。


这并没有帮助,因为他想用另一种类型的组件(某个派生类)来替换TEdit。对于其余部分,这与第一个答案完全相同,只是你将其封装成了一个函数。 也许AfterConstruction提示是有用的。 - Loesje
实际上,相同的想法也适用于子类,参见更新版本。然而我没有仔细阅读,因为我错过了“子类化”这一点... - Francesca
2个进一步的评论:
  1. 最好不要深入到IsertControl/RemoveControl,除非真的必要。SetParent做了所有需要的事情。
  2. 我不同意它们是相同的:使用TControlClass类工厂方法更加通用和灵活。
- Francesca

1

你实际上可以使用 RTTI(在 TypInfo 单元中查找)来克隆所有匹配的属性。我之前写过这方面的代码,但现在找不到了。我会继续寻找。


请问您能否分享使用TypInfo/Rtti的克隆程序呢? - menjaraz
@menjaraz 我认为François的回答更好。我需要重新创建我之前的代码。基本上,它将使用RTTI来检查旧组件和新组件,然后复制共同的属性值。 - Jim McKeeth
感谢回答:François的帖子非常有价值。 - menjaraz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接