Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi TForm Freigeben (https://www.delphipraxis.net/119935-tform-freigeben.html)

Pro_RJ 3. Sep 2008 12:30


TForm Freigeben
 
Hallo,

Ich hab da ml eine einfache Frage zum Thema Forms im BDS 2006:

Ich Habe mir eine eigene Klasse geschrieben :


Delphi-Quellcode:
type
  TMeinForm = class(TForm)
  private
    { Private-Deklarationen }
    U_Ist_Destroy        : Boolean;
    U_Ist_Close          : Boolean;
    U_WarDeactivate      : Boolean;
    U_Activate_Arbeitet  : Boolean;
    U_DeActivate_Arbeitet : Boolean;
.....
  protected
    { Protected-Deklarationen }
....
  public

    Fensternr           : Integer;
    DSFensterEinstellungen : TIBDataSet;
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    { Published-Deklarationen }

  end;

constructor TMeinForm.Create(AOwner: TComponent);
begin
  U_Ist_Destroy        := false;
  U_Ist_Close          := False;
  U_WarDeactivate      := False;
  U_Activate_Arbeitet  := False;
  U_DeActivate_Arbeitet := False;
  // Hier ist das Fenster noch nicht erzeugt
  inherited;
  // Hier ist das Fenster erzeugt -> nach dieser Proc (Create) kommt erst FormCreate aus dem jeweiligen Fenster

  U_Fensternr                            := FensterNr(Self);
  DSFensterEinstellungen                 := TIBDataSet.Create(Self);
.....
end;


destructor TMeinForm.Destroy;
Var I : Integer;
    OBJ : TObject;
    warStamm : Boolean;
begin
  U_Ist_Destroy := true;
 {
  For i := Self.ComponentCount -1 downto 0 do
  Begin
    OBJ := Self.Components[I];
    FreeAndNil(OBJ);
  End;
}
  FreeAndNil(DSFensterEinstellungen );

  inherited;
//  if warStamm then Self := NIL;
end;


Aus dieser Fenstervorlage wird dann ein Fenster gebaut, wie man es in Delphi gewöhnt ist. Mit Panels, Buttons usw.....
bsp:
Delphi-Quellcode:
type
  TForm1 = class(TMeinForm)
    OpenDialog1: TOpenDialog;
    Tabelle_Gestalten: TMenuItem;
    PopUpINS: TMenuItem;
    PopUpDEL: TMenuItem;
    PopUpSAVE: TMenuItem;
    Panel1: TPanel;
    N1: TMenuItem;
....

das Fenster wird mit "Application.CreateForm(TForm1, Form1)" erzeugt und anschliesend mit Form1.Show; angezeigt.

Beim beenden des Fensters wird in FormClose Action auf CAFree gesetzt.

Und jetzt kommt das Phänomen:
Wenn der destructor komplett abgearbeitet worden ist haben einige Objecte auf Form1 haben noch einen Wert. (Form1.OpenDialog1 hat "$2621000").
Obwohl es das Fenster und damit auch semtliche Objecte auf ihm ja nichtmehr geben dürfte.Da Alle Objecte Form1 als Owner haben.

Und genauso ist es auf mit der Variable "Form1". Wenn ich jetzt also Abfragen möchte

Delphi-Quellcode:
 
  if not Assigned(Form1) then Application.CreateForm(TForm1, Form1);
  Form1.show;
bekomme ich nur Leider bekomme ich dann bei "Form1.show;" eine Zugriffsferletzung.
Da Form1 nicht Nil wird es nicht erzeugt. Obwohl es ja nicht da sein dürfte/ ist.

Wenn ich jetzt aber in den destructor nach inherited; Self:= NIL; rein schreibe bekomme ich eine Zugriffsverletzung.(Was ja klar ist, da es ja auch eigentlich unsinn ist)

Ich hoffe ihr könnt mir Dabei helfen, wie ich alle Objecte auf dem Form "Sauber" Zerstören kann und die Variable "Form1" auf NIL bekomme.

mkinzler 3. Sep 2008 12:38

Re: TForm Freigeben
 
Man sollte mit FreeAndNil() Freigeben, wenn man die Variable wiederverwenden will.

Pro_RJ 3. Sep 2008 12:43

Re: TForm Freigeben
 
Aber wann bzw wo?
Im destructor kann ich Form1 nicht auf NIL setzten da anschließend eien Zugriffsverletzung kommt.

mkinzler 3. Sep 2008 12:46

Re: TForm Freigeben
 
Deshalb sollst du den Destruktor auch nicht direkt aufrufgen, sondern immer .Free() aufrufen. In diesem Fall
dann
Delphi-Quellcode:
FreeAndNil( Form1);
statt
Delphi-Quellcode:
Form1.Free();
oder gar
Delphi-Quellcode:
Form1.Destroy();

Pro_RJ 3. Sep 2008 12:48

Re: TForm Freigeben
 
Das Fenster wird aber mit Form1.CLose geschlossen. Und nicht mit Destroy oder Free.

mkinzler 3. Sep 2008 12:52

Re: TForm Freigeben
 
Im Event onCloseAction kannst du Steuern, was beim Close geschehen soll

Pro_RJ 3. Sep 2008 12:55

Re: TForm Freigeben
 
Ich habe bei mir im BDS2006 kein Event "onCloseAction".
Ich habe nur OnClose:

Delphi-Quellcode:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := CAFree;
end;

mkinzler 3. Sep 2008 12:58

Re: TForm Freigeben
 
Den meinte ich :oops:
Dort caHide nehmen, dann wird Fenster nicht geschlossen sondern nur ausgeblendet und kann mit .Show wieder geöffnet werden

Pro_RJ 3. Sep 2008 13:00

Re: TForm Freigeben
 
Ich will das Fenster aber "Richtig" schließen um den Speicher wieder frei zu geben. Da das Programm aus ca 150 Fenstern besteht.

HenKst 3. Sep 2008 13:10

Re: TForm Freigeben
 
Wenn du .free machst ist der Speicher ja Freigegeben, nur stehen halt immernoch Werte drin.
Freigeben heist nur das andere Programme den Speicher neu beschreiben dürfen.

Mit FreeAndNi(form1) wird auch nur der Pointer vom Form auf nil gesetzt.

Wenn du den Speicher neu verwenden willst musst du ihn schon explizit auf 0 bzw das setzen was halt drinstehen soll.
Wenn du auf nicht alocierten speicher zugreifst steht da halt drin was zuletzt reingeschrieben wurde.

Pro_RJ 3. Sep 2008 13:20

Re: TForm Freigeben
 
Zitat:

Zitat von Pro_RJ
Aber wann bzw wo?
Im destructor kann ich Form1 nicht auf NIL setzten da anschließend eien Zugriffsverletzung kommt.


Ich habe es auch gerade mal mit der normalen Klasse TForm getestet. Hier tritt genau das gleiche Phänomen auf.
Ich dachte erst das es an der neuen Klasse liegt aber dem ist nicht so.

Pro_RJ 3. Sep 2008 13:25

Re: TForm Freigeben
 
PS: es ist egal ob das Fenster über Application.CreateForm(TForm1, Form1) oder Form1 := TForm1.Create(); erzeugt wird.
Die Frage ist nur wir kann ich nach dem Schließen des Fensters die Variable Form1 wider auf NIL setzen?

mkinzler 3. Sep 2008 13:28

Re: TForm Freigeben
 
Aus der Klasse heraus nicht.

Pro_RJ 3. Sep 2008 13:31

Re: TForm Freigeben
 
Zitat:

Zitat von mkinzler
Aus der Klasse heraus nicht.

verstehe ich leider nicht ganz :gruebel:
Aber wann könnte ich dann die Variable Form1 wieder auf nil setzten?

mkinzler 3. Sep 2008 13:34

Re: TForm Freigeben
 
Die Klasse kennt die Referenzvariablen, die zur Laufzeit existieren ja nicht.

Pro_RJ 3. Sep 2008 14:03

Re: TForm Freigeben
 
AHHH :idea: :idea: jetzt hab ich verstanden wo das eigentliche Problem liegt.
Gibt es eine möglichkeit die die Referenzvariablen zu ermitteln?
Wer ruft eigentichlich den destructor nach dem FormClose auf?

mkinzler 3. Sep 2008 14:09

Re: TForm Freigeben
 
Die Methode Close

sirius 3. Sep 2008 14:13

Re: TForm Freigeben
 
und warum rufst du anstatt Form1.close nicht freeandnil(form1) auf? Da wird die Form doch auch vorher geschlossen und deine referenz ist nil.

Pro_RJ 3. Sep 2008 14:39

Re: TForm Freigeben
 
Gibt es die möglichkeit sich eine Referenzvariablen zu speichern?

bsp. dem Constructor übergeben : CreatePCB(AOwner : TComponent; Var Reference);
Diese Referenz im Object speichern und bei Destroy diese Referenz auf NIL setzten?
geht sowas?

sirius 3. Sep 2008 14:47

Re: TForm Freigeben
 
Jein, nur unter gewissen Umständen. Aber das ergibt eine schöne Fehlerquelle, wenn diese Umstände nicht eingehalten werden. Und das ist meist der Fall.
==> Nein

Edit: Von wo wird denn das Fenster geschlossen?

Pro_RJ 3. Sep 2008 14:52

Re: TForm Freigeben
 
Die Fenster werden Manuell(Über einen Beenden Button aufgerufen (Methode close)) über das "X oben Rechts" bzw. manchmal auch automatisch aus einer Aktion.
ABetr immer im Fenster selber. extrem selten das ein Fenster mal von einem anderen Fenster gesteuert wird.

Gibt es eine möglichkeit abzuprüfen oder der wert noch gültig ist?
if not Gültig(Form1) then Form1 := TForm1.Create;

sirius 3. Sep 2008 15:01

Re: TForm Freigeben
 
Du köntest ein spezielles Feld in die Klasse einbauen, dass du in einem try-except-Block abfragst. Aber 100% sicher und auch schön ist es nicht.

Wie wär es mit einem Ereignis, was ausgelöst wird, und die Referenz auf nil setzt?

Pro_RJ 3. Sep 2008 15:11

Re: TForm Freigeben
 
das Problem ist, das die Klasse die Referenzen nicht kennt.
Bsp: Ich weiß nicht,wer weiß wo ich Wohne.
Und da bringt mir das Feld leider auch nichts, da es das ja auch nicht mehr gibt.

sirius 3. Sep 2008 15:14

Re: TForm Freigeben
 
Ich verstehe deinen Code nicht. Wieviele Referenzen erwartest du denn und warum?

Pro_RJ 3. Sep 2008 15:17

Re: TForm Freigeben
 
Grundsätzlich erwarte ich nur eine Referenz.

sirius 3. Sep 2008 16:06

Re: TForm Freigeben
 
Na dann brauchts du doch auch nur die auf Null setzen:

Concept-MainForm:
Delphi-Quellcode:
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
    Form2:TForm2; //hier ist die wichtige Reference
    procedure DoClearReference;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if assigned(Form2) then
    Form2.BringToFront
  else
  begin
    Form2:=TForm2.Create(nil);
    Form2.clearReference:=DoClearReference;
    Form2.Show;
  end;
end;

procedure TForm1.DoClearReference;
begin
  Form2:=nil;
end;
Form2:
Delphi-Quellcode:
type
  TForm2 = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
   public
    ClearReference:procedure of object;
  end;

implementation

{$R *.dfm}

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if assigned(clearReference) then ClearReference;
end;

end.

Pro_RJ 3. Sep 2008 16:54

Re: TForm Freigeben
 
die Idee ist an sich nicht verkehrt.
Das Problem ist, das ist bei 150 Fenster weder überschaubar noch pflegbar.
Deswegen würde ich das ja gerne Automatisieren, bzw. dafür sorgen, das sich das ganze selber überwacht

sirius 3. Sep 2008 17:01

Re: TForm Freigeben
 
Wie jetzt? Wenn du 150 Fenster hast, müsstest du ja auch 150 Referenzen haben. Dann baust du sie halt in einer Liste, oder so.

Pro_RJ 3. Sep 2008 17:12

Re: TForm Freigeben
 
ja klar ein Referenz pro Fenster.
Wie kann ich diese Referenzen in einer liste speichern?

sirius 3. Sep 2008 18:10

Re: TForm Freigeben
 
Vielleicht wie folgt:
Delphi-Quellcode:
type
  TForm1 = class(TForm)
    Button1: TButton;
    SpinEdit1: TSpinEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
    FForms:TComponentList;
    procedure DoClearReference(aFensterNr:Integer);
    procedure AddForm(aForm:TMeinForm);
    function ExistsForm(aFensterNr:Integer):boolean;
    function GetForm(aFensterNr:Integer):TMeinForm;
    procedure ClearForm(aFensterNr:Integer);
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var MeinForm:TMeinForm;
begin
  if ExistsForm(SpinEdit1.Value) then
    GetForm(SpinEdit1.Value).BringToFront
  else
  begin
    MeinForm:=TMeinform.Create(self);
    MeinForm.ClearReference:=doClearReference;
    MeinForm.Fensternr:=SpinEdit1.Value;
    AddForm(MeinForm);
    MeinForm.Show;
  end;
end;

procedure TForm1.DoClearReference(aFensterNr:Integer);
begin
  ClearForm(aFensterNr);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FForms.Free;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FForms:=TComponentList.Create(false);
end;

procedure TForm1.AddForm(aForm: TMeinForm);
begin
  FForms.Add(aForm);
end;

procedure TForm1.ClearForm(aFensterNr: Integer);
var i:integer;
begin
  for i:=FForms.count-1 downto 0 do
    if (FForms[i] as TMeinForm).Fensternr=aFensterNr then
      FForms.Delete(i);
end;

function TForm1.ExistsForm(aFensterNr: Integer): boolean;
begin
  result:=GetForm(aFensterNr)<>nil;
end;

function TForm1.GetForm(aFensterNr: Integer): TMeinForm;
var i:integer;
begin
  result:=nil;
  for i:=0 to FForms.Count-1 do
  begin
    result:=(FForms[i] as TMeinForm);
    if result.Fensternr=aFensterNr then break;
    result:=nil;
  end;
end;
Delphi-Quellcode:
type
  TMeinForm = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
   public

    Fensternr           : Integer; //habe ich mal als Erkennung von gleichen Fenstern genommen
    ClearReference:procedure(aFensterNr:Integer) of object;
  end;

implementation

{$R *.dfm}

procedure TMeinForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if assigned(clearReference) then ClearReference(FensterNr);
end;
Ob man die Listenverwaltung nochmal einzeln kapselt wäre sicher angebracht. Ich wollte es jetzt nicht zu umständlich/speziell machen.

Pro_RJ 3. Sep 2008 21:13

Re: TForm Freigeben
 
Vielen dank
ich schaue mal ob ich die idee bei mir einbasteln kann. ich geb dir nochmal info.
Danke aber schonmal für die ideeen

Pro_RJ 5. Sep 2008 08:03

Re: TForm Freigeben
 
Also ich habs jetzt mal bei mir Implementiert:
Ich habe
1. Alle Referenzen (Form1 : TForm1) rausgeschmissen.
2. eine Objectlist erstellt wo ich im Constructor von TMeinForm das neu erzeugte Fenster eintrage und im Destuctor aus dieser Liste wieder rausschmeiße.
3. Jedes DFenster hat eine eindeutige Nummer bekommen, welche ich in dem Object Speichere.
4. Es gibt nur noch eine Function, die mir an Hand der FensterNr das Object oder Nil zurück gibt.
damit ist sichergestellt das keinerlei Referenzen hängen bleiben können.
Einziger nachteil, Wenn ich auf Form1 zugreifen will muss ich jetzt immer TForm1(Fenster(123)) schreiben aber das ist ok.

sirius 5. Sep 2008 08:11

Re: TForm Freigeben
 
Den kleinen Nachteil könnte man jetzt auch noch umgehe,n indem man von der ComponentList eine eigene Liste ableitet, die nach außen hin mit TForm arbeitet. Aber da dürfte der Aufwand höher sein. Ich mach das nur ganz selten und meist, wenn ich sowieso die Listenklasse umschreiben muss.

Sko 10. Aug 2009 07:47

Re: TForm Freigeben
 
Liste der Anhänge anzeigen (Anzahl: 1)
*altenThreadausgrab*

Das gleiche Thema mit dem Freigeben dynamisch erzeugter Forms hatte ich jetzt auch in meinem aktuellen Projekt.

Zitat:

Zitat von sirius
Den kleinen Nachteil könnte man jetzt auch noch umgehe,n indem man von der ComponentList eine eigene Liste ableitet, die nach außen hin mit TForm arbeitet. Aber da dürfte der Aufwand höher sein. Ich mach das nur ganz selten und meist, wenn ich sowieso die Listenklasse umschreiben muss.

Genau das hab ich dafür mal gemacht. In den Anhang hab ich die Unit gepackt, Verbesserungsvorschläge seh ich gerne, so oft hab ich noch keine neuen Klassen von anderen abgeleitet.

Ich hab die Klasse TDynamicFormManager von TObjectList abgeleitet und die Funktionen implementiert die ich brauchte:
  • der DynamicFormManager wird im OnCreate der Anwendung erzeugt und im OnClose wieder freigegeben
  • Forms werden normal erzeugt und mit AddForm() dem FormManager hinzugefügt, Beispiel:
    Delphi-Quellcode:
    procedure TMainForm.BBlubbClick(Sender: TObject);
    var
      AnyForm: TAnyForm;
    begin
      AnyForm := TAnyForm.Create(nil);
      <machwasmitAnyform>;
      DynFormManager.AddForm(AnyForm);
    end;
    AddForm setzt die Tag-Eigenschaft des Fensters auf einen einmaligen Wert (interner Zähler, wird bei jedem Aufruf der Funktion erhöht)
  • die Forms werden mit DeleteFormByInternalIndex() freigegeben. Dazu wird der Funktion einfach der Tag des Fensters übergeben.
  • in meinem Projekt hab ich zwei Arten von dynamischen Forms: welche die nur einmal aufgerufen werden sollen, und welche die mehrmals (mit unterschiedlichen Inhalten) aufgerufen werden können.
    Ob die Form bereits existiert kann im ersten Fall durch GetFormByClassName() und im zweiten Fall durch GetFormByName() geprüft werden. Komplett sieht das bei mir so aus:
    Delphi-Quellcode:
    procedure TMainForm.BBlubbClick(Sender: TObject);
    var
      AnyForm: TAnyForm;
    begin
      AnyForm := TAnyForm(DynFormManager.GetFormByClassName('TAnyForm')); // oder z. B. AnyForm := TAnyForm(DynFormManager.GetFormByName('AnyForm1')); wenn mehrere Forms der Klasse TAnyForm möglich sein sollen
      if Assigned(AnyForm) then
      begin
        AnyForm.Show;
        AnyForm.BringToFront;
      end
      else
      begin
        AnyForm:= TAnyForm.Create(nil);
        <machwasmitAnyForm>
        DynFormManager.AddForm(AnyForm);
        AnyForm.Show;
      end;
    end;
Den Tag der Form als Eigenschaft zur eindeutigen Identifikation der Form zu verwenden hat den Vorteil das man mit TForm arbeiten kann und nicht noch ne Klasse von TForm ableiten muss. Wenn man natürlich den Tag anderweitig verwendet, funktioniert das nicht mehr.


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:44 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz