Eliminar y reemplazar un componente visual en tiempo de ejecución
Pregunta
¿Es posible, por ejemplo, reemplazar y liberar un TEdit con un componente subclasificado instanciado (condicionalmente) en tiempo de ejecución?Si es así, ¿cómo y cuándo se debe hacer?Intenté establecer el padre en nulo y llamar a free() en el constructor del formulario y en los métodos AfterConstruction, pero en ambos casos obtuve un error de tiempo de ejecución.
Para ser más específico, recibí un error de infracción de acceso (EAccessViolation).Parece que François tiene razón cuando dice que liberar componentes en la construcción del marco afecta el mantenimiento de los controles de Form.
Solución
Esta rutina más genérica funciona con un formulario o un marco (actualizado para usar una subclase para el nuevo 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;
usando esta rutina para pasar las propiedades al nuevo 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;
úsalo como:
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;
PRECAUCIÓN:Si está haciendo esto dentro de AfterConstruction of the Frame, tenga en cuenta que la construcción del formulario de alojamiento aún no está terminada.
Liberar los controles allí puede causar muchos problemas, ya que estás arruinando la limpieza de los controles del formulario.
Vea lo que obtiene si intenta leer el nuevo Editar título para mostrar en ShowMessage...
En ese caso querrás usar
...ReplaceControl(Editar1, 'Editar2', FALSO)
y luego hacer un
...FreeAndNil(Editar1)
más tarde.
Otros consejos
Tienes que llamar a RemoveControl del padre de TEdit para eliminar el control.Utilice InsertControl para agregar el nuevo 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;
Reemplace TEdit.Create con la clase que desea usar y copie todas las propiedades que necesita como hice con Left y Top.
De hecho, puede usar RTTI (busque en la unidad TypInfo) para clonar todas las propiedades coincidentes.Escribí código para esto hace un tiempo, pero no puedo encontrarlo ahora.Seguiré buscando.