2008-09-23 6 views
7

Ist es beispielsweise möglich, ein TEdit durch eine zur Laufzeit instantiierte (bedingt) instanziierte Komponente zu ersetzen und freizugeben? Wenn ja, wie und wann sollte es gemacht werden? Ich habe versucht, das Elternteil auf Null zu setzen und free() in den Formularkonstruktor- und AfterConstruction-Methoden aufzurufen, aber in beiden Fällen habe ich einen Laufzeitfehler bekommen.Entfernen und Ersetzen einer visuellen Komponente zur Laufzeit


Spezifischer sein, ich habe einen Zugriffsverletzung Fehler (EAccessViolation). Es scheint, dass François Recht hat, wenn er sagt, dass das Freigeben von Komponenten bei der Frame-Construction mit der Form-Controls-Verwaltung kollidiert.

Antwort

8

Diese generische Routine funktioniert entweder mit einem Formular oder Frame (aktualisiert, um eine Unterklasse für die neue Steuerung zu verwenden):

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; 

diese Routine mit den Eigenschaften der neuen Steuerung passieren

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; 

Gebrauch mag es:

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; 

VORSICHT: Wenn Sie dies in der AfterConstruction des Frame tun, achten Sie darauf, dass die Erstellung des Hosting-Formulars noch nicht abgeschlossen ist.
Wenn Sie die Steuerelemente dort freigeben, kann dies zu vielen Problemen führen, da Sie Probleme mit der Verwaltung von Formularsteuerelementen haben.
Sehen Sie, was Sie bekommen, wenn Sie versuchen, die neue Beschriftung bearbeiten lesen in der Showmessage anzuzeigen ...
In diesem Fall Sie
... ReplaceControl (Edit1 ‚Edit2‘, Falsch) verwenden wollen
und dann tun Sie eine
... FreeAndNil (Edit1)
später.

+0

Das wird nicht helfen, da er den TEdit durch einen anderen Komponententyp ersetzen wollte. (etwas Abkömmling) Für den Rest ist dies genau die erste Antwort, außer dass Sie es in eine Funktion eingepackt haben. Vielleicht ist der AfterConstruction-Tipp aber hilfreich. – Loesje

+0

Eigentlich funktioniert die gleiche Idee mit einer Unterklasse, siehe aktualisierte Version. Habe mich aber nicht genau durchgelesen, da ich die "Unterklasse" verpasst habe ... –

+0

2 weitere Kommentare: 1. Es ist am besten, nicht auf IsertControl/RemoveControl zu drillen, es sei denn, wirklich nötig. SetParent tut alles was benötigt wird. 2. Und ich möchte darüber differieren, dass es dasselbe ist: es ist viel generischer und flexibler, wenn man den Klassen-Factory-Ansatz mit TControlClass verwendet. –

8

Sie müssen RemoveControl des übergeordneten TEdit-Elements aufrufen, um das Steuerelement zu entfernen. Verwenden Sie InsertControl, um das neue Steuerelement hinzuzufügen.

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; 

ersetzen TEdit.Create auf die Klasse, die Sie verwenden möchten, und kopieren Sie alle Eigenschaften, die Sie brauchen, wie ich mit links und oben tat.

+0

Warum nicht die Parent-Eigenschaft anstelle von InsertControl und RemoveControl festlegen? –

+0

Einfach weil in der Frage gesagt wurde, dass die Eltern-Eigenschaft nicht funktioniert hat. Und ich erinnerte mich, dass ich mit InsertControl und RemoveControl einmal Code geschrieben hatte. Also habe ich einfach die Parent-Eigenschaft nicht versucht. – Loesje

+0

Der Loesje-Code funktionierte in der TMyForm.AfterConstruction-Methode, nicht jedoch in TMyFrame.AfterConstruction. Wo sollte ich es platzieren, wenn ich dies mit einem TFrame-Objekt verwende? – user16120

1

Sie können RTTI (siehe TypInfo-Einheit) verwenden, um alle übereinstimmenden Eigenschaften zu klonen. Ich habe vor einiger Zeit einen Code dafür geschrieben, aber ich kann ihn jetzt nicht finden. Ich werde weiter suchen.

+0

Können Sie bitte die Klonroutine mit TypInfo/Rtti teilen? – menjaraz

+0

@menjaraz Ich glaube [François Antwort] (http://Stackoverflow.com/a/122915/255) ist eine bessere Antwort.Ich müsste den Code neu erstellen, den ich hatte. Im Grunde würde RTTI verwendet, um die alte Komponente und die neue Komponente zu untersuchen und dann die Eigenschaftswerte gemeinsam zu kopieren. –

+0

Danke für die Antwort: François Beitrag ist wertvoll. – menjaraz