Form schliessen und zerstören

Rund um die LCL und andere Komponenten
Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Form schliessen und zerstören

Beitrag von Mathias »

Bei einem Form mit ShowModal ist es kein Problem, Form wird angezeigt, anschliessend wird es freigegeben und zerstört.

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  f:TForm;
begin
  f:=TForm.Create(nil);
  f.ShowModal;
  f.Free;
end


Wie kann ich das lösen, wen das Form mit Show angezeigt wird ?
Folgender Code kann gar nicht funktionieren:

Code: Alles auswählen

procedure TForm1.Button2Click(Sender: TObject);
var
  f:TForm;
begin
  f:=TForm.Create(nil);
  f.Show;
  f.Free;
end

Das wäre der Fall, wen Benutzer zB. das 'X' auf dem erzeugten Form drückt

Ich habe mal folgendes probiert:

Code: Alles auswählen

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    f2: TForm2;
  public 
...
procedure TForm1.Button2Click(Sender: TObject);
begin
  if not Assigned(f2) then begin
    f2 := TForm2.Create(nil);
  end;
  f2.Show;
end;

Dies verhindert, das das Form nur einmal erzeugt wird
Wie kann ich es aber zerstören, wen der Benutzer das 'X' auf f2 drückt ?
Sowas geht wie erwartet nicht:

Code: Alles auswählen

procedure TForm2.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  Free;
end;
.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1430
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Form schliessen und zerstören

Beitrag von fliegermichl »

Das Free brauchst du gar nicht selbst machen.

Bevor das Formular geschlossen wird, wird FormClose mit dem var Parameter "Action" aufgerufen.

Wenn du da dann Action := caFree reinschreibst, wird das Formular automatisch freigegeben.

Edit: Der Parameter wurde irgendwann mal umbenannt nach CloseAction

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: Form schliessen und zerstören

Beitrag von sstvmaster »

Bei mir geht sowas:

Code: Alles auswählen

 
unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    f2: TForm;
    procedure Form2Close(Sender: TObject; var CloseAction: TCloseAction);
  public
 
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.Form2Close(Sender: TObject; var CloseAction: TCloseAction);
begin
  ShowMessage('Close?');
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(f2) then begin
    f2 := TForm.Create(self);
    f2.OnClose:=@Form2Close;
    f2.Caption:='Form2';
  end;
  f2.Show;
end;
 
end.
 


Anstatt: TForm.Create(nil); -> TForm.Create(self);

Dann wird es automatisch zerstört, mit nil gibt es Speicherfehler beim beenden.
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Danke, werde es heut probieren. :wink:
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Ich habe im Anhang eine Minidemo geschrieben.
Wen ich das erste mal auf den Button klicke, kommt wie erwartet "Create" und das 2. Form geht auf.
Wen ich nochmals klicke, kommt "no Create" wie erwartet.

Schliesse ich aber das 2. Form. Und klicke nochmals, kommt auch "no Create", obwohl das 2- Form zerstört ist.
Wie kann ich das verhindern ?
Ich denke, irgendwo muss ich das das 2. Form auf "nil" setzen, aber wo ?

Create_Close_Form.zip
(106.1 KiB) 79-mal heruntergeladen
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Form schliessen und zerstören

Beitrag von m.fuchs »

Du prüfst ob das Feld f auf nil gesetzt ist. Wenn sich dein zweites Form selbst vernichtet, zeigt aber f noch immer auf die Speicherstelle. Ist also nicht nil.
Statt sich selbst zu zerstören, sollte dein zweites Form lieber dem ersten Bescheid sagen, dass es vernichtet werden möchte.
Dann könnte Form1 mit

Code: Alles auswählen

FreeAndNil(f);
sowohl das Setzen auf nil als auch die Sterbehilfe für das zweite Form durchführen.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Form schliessen und zerstören

Beitrag von wp_xyz »

Nicht-modale Formulare haben's in sich. Nach dem Schließen werden sie standardmäßig nicht zerstört, sondern nur versteckt (Visible := false). Hierzu muss man keinen OnClose-Handler des zu schließenden Formulars schreiben. Vorteil ist, dass das Formular bei der nächsten Verwendung genauso wiederkommt, wie es geschlossen war. Nachteil ist, dass der "Schrott" der letzten Eingabe noch drinnen steht, und dass das Formular immer noch Speicher frisst.

Wenn man das Formular beim Schließen (Klick auf dem 'x') zerstören will, braucht man das OnClose-Ereignis, in dem man durch den Parameter Action festlegen kann, "wie" das Formular geschlossen wird. Wiegesagt, default ist "caHide", aber zum Zerstören braucht man "caFree". Ein Problem entsteht aber, wenn man im aufrufenden Formular noch eine Variable für das geschlossene Formular hat, so wie du mit "f". Das Formular existiert nun nicht mehr. Wenn man das Formular "f" ein zweites Mal öffnet, ergibt die Prüfung "Assigned(f)" true, denn f wurde beim Zerstören nicht automatisch auf nil gesetzt. Ergebnis: Crash.

Die Lösung ist zum Beispiel, dass man das aufrufende Formular entscheiden lässt, ob f zerstört wird. Das heißt, der OnClose-Event-Handler wird nicht von TForm2 bereitgestellt, sondern von TForm1. Da TForm1 die Variable "f" kennt, kann es sie auf nil setzen;

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(f) then begin
    ShowMessage('Create');
    f:=TForm2.Create(Self);
    f.OnClose := @Form2Close;
  end else begin
    ShowMessage('no Create');
  end;
  f.Show;
end;

procedure TForm1.Form2Close(Sender: TObject; var CloseAction: TCloseAction);
begin
  CloseAction := caFree;
  f := nil;
end;   
Unschön ist an deiner Lösung auch, dass f: TForm2 in der Deklaration von TForm1 steht. Das bewirkt, dass Unit2 in die Uses-Zeile des Interface-Teils von Unit1 aufgenommen werden muss. In dem Beispiel geht es noch, aber wenn TForm2 auch etwas von TForm1 braucht, dann muss auch Unit1 in den Interfaceteil von Unit2, und schon hast du einen schönen Zirkelbezug.

Besser ist nur so wenig wie mögich zu deklarieren. In der Deklaration von TForm1 wird eigentlich nur benötigt, dass es noch ein Formular gibt (f), auf das an mehreren stellen zugegriffen werden muss. Da reicht es, f als TForm zu deklarieren, und Unit2 kann vom Uses des Interface-Teils in das des Implementation-Teils wandern. Dort ist es ausreichend, um dann f als TForm2.Create erzeugen zu können:

Code: Alles auswählen

interface

uses
  ...;  // ohne Unit2;

type

  { TForm1 }

  TForm1 = class(TForm)
  ....
  private
    f:TForm;  // nicht TForm2
     ....
  end;
  
implementation

uses
  Unit2;   // war früher in interface

procedure TForm1.Button1Click(Sender: TObject);
begin
  ...
    f:=TForm2.Create(Self);  // f als TForm2 erzeugen, obwohl es nur als TForm deklariert ist
    ....

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Statt sich selbst zu zerstören, sollte dein zweites Form lieber dem ersten Bescheid sagen, dass es vernichtet werden möchte.
Wie soll ich das bewerkstelligen ?

Einzig was mit in den Sinn gekommen ist, eine globale Variable "binZerstört".
Aber dies geht garantiert eleganter.
Dateianhänge
Create_Close_Form2.zip
(106.14 KiB) 68-mal heruntergeladen
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

charlytango
Beiträge: 843
Registriert: Sa 12. Sep 2015, 12:10
OS, Lazarus, FPC: Laz stable (2.2.6, 3.x)
CPU-Target: Win 32/64, Linux64
Wohnort: Wien

Re: Form schliessen und zerstören

Beitrag von charlytango »

hm.. ich sehe da einige gute mögliche Lösungen für das Erstellen und zerstören von Formularen.

Aber...ergebnisorientiert->
@Mathias: Was beabsichtigst du damit zu tun? Ein Programm mit zwei Formularen erstellen oder eine größere Applikation bei der mehrere unterschiedliche Formulare oder auch mehrere Formulare gleichen Typs zusammen arbeiten sollen?

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Es geht bei mir um den Serial Monitor, welcher in der Embedded GUI Package intergriert ist.

https://github.com/sechshelme/Lazarus-E ... UI_Package

Momentan ist es auf nicht Zerstörung, wen man das Form mit "X" schliesst.

Aber irgendwo ist auch so ein Bug versteckt.
Öffne ich den Monitor, und beende anschliessend Lazarus, geht dies ohne Fehler.
Mache ich aber nac dem Öffnen den Monitor selbst zu und Beende dann Lazarus, gibt es eine Access oder Ähnlich Fehlermeldung.

Kannst es gerne mal testen. Es ist fpc und Lazarus Trunk erforderlich.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Form schliessen und zerstören

Beitrag von m.fuchs »

Mathias hat geschrieben:
Do 30. Apr 2020, 17:00
Statt sich selbst zu zerstören, sollte dein zweites Form lieber dem ersten Bescheid sagen, dass es vernichtet werden möchte.
Wie soll ich das bewerkstelligen? Einzig was mit in den Sinn gekommen ist, eine globale Variable "binZerstört".
Eine Möglichkeit wäre:

Code: Alles auswählen

unit Unit2;
{$MODE ObjFpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs;

type
  TPleaseKillMeAction = procedure(Sender: TForm) of object;

  TForm2 = class(TForm)
    private
      FPleaseKillMeAction: TPleaseKillMeAction;
    public
      constructor Create(TheOwner: TComponent; PleaseKillMeAction: TPleaseKillMeAction); reintroduce;
    published
      procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  end;

var
  Form2: TForm2;

implementation
{$R *.lfm}

procedure TForm2.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  CloseAction := caNone;
  FPleaseKillMeAction(Self);
end;

constructor TForm2.Create(TheOwner: TComponent; PleaseKillMeAction: TPleaseKillMeAction);
begin
  inherited Create(TheOwner);
  FPleaseKillMeAction := PleaseKillMeAction;
end;

end.
Dein TForm2 bekommt einen neuen Constructor, der zusätzlich eine Methode übergeben bekommt. Diese wird in FPleaseKillMeAction gespeichert.
Wird FormClose ausgeführt, dann wird mit caNone keine CloseAction durchgeführt und die übergebene Methode aufgerufen.

Diese muss dann in TForm1 implementiert werden:

Code: Alles auswählen

unit Unit1;
{$MODE ObjFpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Unit2;

type
  TForm1 = class(TForm)
    private
      SubForm: TForm2;
      procedure PleaseKillMe(Sender: TForm);
    published
      Button1: TButton;
      procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation
{$R *.lfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(SubForm) then begin
    ShowMessage('Create');
    SubForm := TForm2.Create(Self, @Self.PleaseKillMe);
  end else begin
    ShowMessage('no Create');
  end;
  SubForm.Show;
end;

procedure TForm1.PleaseKillMe(Sender: TForm);
begin
  if Sender = SubForm then
    FreeAndNil(SubForm);
end;

end.
Beim Erzeugen des zweiten Forms, wird im Constructor ein Zeige auf die Methode TForm1.PleaseKillMe, damit das FormClose im zweiten Form die Methode aufrufen kann.
Die Methode prüft ob der Sender das gleiche Form ist wie SubForm und ruft dessen Destructor auf und setzt SubForm auf nil.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Form schliessen und zerstören

Beitrag von wp_xyz »

Das müsste doch auch direkt im OnClose gehen, ähnlich wie ich es oben schon geschrieben habe:

Code: Alles auswählen

procedure TForm1.Form2Close(Sender: TObject; var CloseAction: TCloseAction);
begin
  CloseAction := caNone;
  if Sender = f then FreeAndNil(f);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not Assigned(f) then begin
    ShowMessage('Create');
    f:=TForm2.Create(Self);
    f.OnClose := @Form2Close;
  end else begin
    ShowMessage('no Create');
  end;
  f.Show;
end;

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Form schliessen und zerstören

Beitrag von m.fuchs »

Stimmt, deutlich kürzer. Ich glaube, ich denke manchmal zu kompliziert.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Aber irgendwo ist auch so ein Bug versteckt.
Öffne ich den Monitor, und beende anschliessend Lazarus, geht dies ohne Fehler.
Mache ich aber nac dem Öffnen den Monitor selbst zu und Beende dann Lazarus, gibt es eine Access oder Ähnlich Fehlermeldung.
Dieses Problem schein gelöst zu sein. Ich habe eine TempStringList in onClose frei gegeben. Dadurch wurde Free 2x aufgerufen. Und knallte es.
Nur komisch, dieser Testcode wird ohne Fehler ausgeführt:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  o: TObject;
begin
  o := TObject.Create;
  Caption := o.ClassName;
  o.Free;
  o.Free;
end;
Nachtrag, wen ich es mit einer StringList mache dann knallts:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  o: TStringList;
begin
  o := TStringList.Create;
  Caption := o.ClassName;
  o.Free;
  o.Free;  // Knall
end;      
Das müsste doch auch direkt im OnClose gehen, ähnlich wie ich es oben schon geschrieben habe:
Probiere ich mal aus. :wink:
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Form schliessen und zerstören

Beitrag von Mathias »

Wieso wurde hier der Sender abgefragt ?

Code: Alles auswählen

if Sender = f then FreeAndNil(f);
Würde ein einfaches

Code: Alles auswählen

  FreeAndNil(f);
nicht reichen ?
Oder ist die Idee dahinter, das man fClose für mehrere SubForms verwenden kann ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten