tags:

views:

1230

answers:

6

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.

+6  A: 

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.

Loesje
Why not set the Parent property instead of the InsertControl and RemoveControl?
Lars Truijens
Simply because in the question it was stated that the parent-property didn't work. And I remembered that I had some code written once using InsertControl and RemoveControl. So I simply didn't try the Parent property.
Loesje
+1  A: 

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.

Jim McKeeth
A: 

Loesje code worked inside TMyForm.AfterConstruction method, but not in TMyFrame.AfterConstruction. Where should I place it when using this with a TFrame object?

+5  A: 

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.

François
This won't help, since he wanted to replace the TEdit with another type of component. (some descendent) For the rest this is exactly the same the first answer, except that you wrapped it into a function.Maybe that the AfterConstruction tip is usefull, though.
Loesje
Actually, the same idea works with a subclass, see updated version. Got me not reading carefully though, as I had missed the "subclassed"...
François
2 further comments:1. It's best not to drill down to IsertControl/RemoveControl unless really necessary. SetParent does all that's needed. 2. And I beg to differ about being the same: it is much more generic and flexible using the class factory approach with TControlClass.
François
A: 

I tried the code in an overridden TMyForm.AfterConstruction and that worked fine. So maybe François' caution is right. Try to remove the Edit1.Free. But I doubt that this is the point, since AfterConstruction is called after all constructors are done.

But what is your problem now? 'Doesn't work' is not very descriptive. Do you get an AV? Or maybe it just doesn't do anything? In that case, are you sure the code gets executed? (Try adding a breakpoint)

Loesje
A: 

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.