Michael Landenberger
2022-02-06 10:50:14 UTC
Hallo,
meine Anwendung hat ein Formular mit Controls drauf. Die Controls sind z. T.
eigene, von TWinControl abgeleitete Klassen. Die Controls müssen von Zeit zu
Zeit dem Formular, auf dem sie sitzen, etwas mitteilen. Ich habe das so
gelöst, dass jedes dieser Controls in diesen Fällen ein Ereignis auslöst, das
von einem Handler des Formulars behandelt wird. Also ungefähr so:
//Control
type
TMyControl = class (TWinControl)
private
FOnUpdate : TNotifyEvent;
public
property OnUpdate : TNotifyEvent read FOnUpdate write FOnUpdate;
end;
//Formular
procedure TMyForm.FormCreate (Sender : TObject);
begin
MyControl.OnUpdate := ControlUpdate;
...
end;
procedure TMyForm.ControlUpdate (Sender : TObject);
begin
...
end;
Jedes Mal, wenn sich in MyControl etwas ändert, kann es das Ereignis OnUpdate
auslösen und das Formular reagiert darauf. So weit, so schön.
Jetzt ist es allerdings so, dass die Controls das Ereignis OnUpdate auch in
ihrem Destruktor nochmal auslösen. D. h. kurz bevor sie selbst freigegeben
werden, teilen sie dem Formular noch schnell ihren letzten Status mit (das
Formular speichert den dann in der Registry). Dabei kam es zu unerklärlichen
Schutzverletzungen, außerdem wurde der letzte Status nicht in die Registry
eingetragen. Jetzt habe ich herausgefunden, warum:
Im Destroy-Handler des Formulars (!) wird ein Objekt freigegeben, das für das
Schreiben in die Registry zuständig ist. Dummerweise löst aber das Formular
sein OnDestroy-Ereignis aus, *bevor* es die Controls darauf freigibt. Wenn
diese dann ihrerseits freigegeben werden, lösen sie ihr OnUpdate-Ereignis aus.
Die zugehörige Ereignisbehandlungsroutine auf dem Formular versucht jetzt, die
Status-Daten des Controls in die Registry zu schreiben, wofür es auf das
besagte Registry-Objekt zugreifen muss. Das aber wurde bereits beim vorher (!)
eingetretenen OnDestroy-Events des Formulars freigegeben. Ergebnis: Peng.
Lange Rede, kurzer Sinn: die Reihenfolge der Freigabe des Formulars und der
Controls darauf ist genau andersrum wie ich das erwartet habe. Ich hätte
gedacht, dass das Formular bei der Freigabe erstmal alle Controls wegräumt und
erst dann sich selbst, wobei es auch erst dann das OnDestroy-Ereignis auslöst.
Ist aber nicht so. Das Setzen der Eigenschaft OldCreateOrder des Formulars
ändert daran auch nichts.
Mein Workaround: ich habe den Destruktor des Formulars überschrieben. Im
überschriebenen Destruktor wird zuerst der vererbte Destruktor aufgerufen,
der wiederum die Controls freigibt. Dabei lösen die ihr OnUpdate-Event aus.
Die aber kann das Formular zu diesem Zeitpunkt noch verarbeiten, denn das
Registry-Objekt existiert dann noch. Erst nach dem Aufruf des vererbten
Destruktors (d. h. erst nach Freigabe der Controls und damit nach dem
letzten OnUpdate-Event) gibt der überschriebene Destruktor auch das
Registry-Objekt frei. Das scheint erstmal zu funktionieren. Jedenfalls
knallt's jetzt nicht mehr und der letzte Status landet auch in der
Registry.
Irgendwie ist mir aber unwohl bei dieser Lösung. Schließlich räumt der
Destruktor des Formulars das ganze Formular weg und damit auch seine
Event-Handler. Der Event-Handler für die OnUpdate-Ereignisse könnte also
nicht mehr mehr gültig sein, wenn die Controls ihre OnUpdate-Events ein
letztes Mal auslösen. Dass o. g. Workaround funktioniert, kann also reiner
Zufall sein.
Die Lösung wäre ein Ereignis, mit dem einem Control mitgeteilt wird, dass
sein Parent (das Formular) gleich freigegeben wird, so dass das Control noch
*vor* der Freigabe des Formulars sein OnUpdate-Ereignis absetzen kann. Das
wäre dann ein Zeitpunkt, zu dem sowohl das Registry-Objekt als auch der
OnUpdate-Handler noch existieren, so dass das OnUpdate-Ereignis sauber
verarbeitet werden kann. Aber welches Ereignis nimmt man dafür? Bin für
sachdienliche Hinweise dankbar.
Gruß
Michael
meine Anwendung hat ein Formular mit Controls drauf. Die Controls sind z. T.
eigene, von TWinControl abgeleitete Klassen. Die Controls müssen von Zeit zu
Zeit dem Formular, auf dem sie sitzen, etwas mitteilen. Ich habe das so
gelöst, dass jedes dieser Controls in diesen Fällen ein Ereignis auslöst, das
von einem Handler des Formulars behandelt wird. Also ungefähr so:
//Control
type
TMyControl = class (TWinControl)
private
FOnUpdate : TNotifyEvent;
public
property OnUpdate : TNotifyEvent read FOnUpdate write FOnUpdate;
end;
//Formular
procedure TMyForm.FormCreate (Sender : TObject);
begin
MyControl.OnUpdate := ControlUpdate;
...
end;
procedure TMyForm.ControlUpdate (Sender : TObject);
begin
...
end;
Jedes Mal, wenn sich in MyControl etwas ändert, kann es das Ereignis OnUpdate
auslösen und das Formular reagiert darauf. So weit, so schön.
Jetzt ist es allerdings so, dass die Controls das Ereignis OnUpdate auch in
ihrem Destruktor nochmal auslösen. D. h. kurz bevor sie selbst freigegeben
werden, teilen sie dem Formular noch schnell ihren letzten Status mit (das
Formular speichert den dann in der Registry). Dabei kam es zu unerklärlichen
Schutzverletzungen, außerdem wurde der letzte Status nicht in die Registry
eingetragen. Jetzt habe ich herausgefunden, warum:
Im Destroy-Handler des Formulars (!) wird ein Objekt freigegeben, das für das
Schreiben in die Registry zuständig ist. Dummerweise löst aber das Formular
sein OnDestroy-Ereignis aus, *bevor* es die Controls darauf freigibt. Wenn
diese dann ihrerseits freigegeben werden, lösen sie ihr OnUpdate-Ereignis aus.
Die zugehörige Ereignisbehandlungsroutine auf dem Formular versucht jetzt, die
Status-Daten des Controls in die Registry zu schreiben, wofür es auf das
besagte Registry-Objekt zugreifen muss. Das aber wurde bereits beim vorher (!)
eingetretenen OnDestroy-Events des Formulars freigegeben. Ergebnis: Peng.
Lange Rede, kurzer Sinn: die Reihenfolge der Freigabe des Formulars und der
Controls darauf ist genau andersrum wie ich das erwartet habe. Ich hätte
gedacht, dass das Formular bei der Freigabe erstmal alle Controls wegräumt und
erst dann sich selbst, wobei es auch erst dann das OnDestroy-Ereignis auslöst.
Ist aber nicht so. Das Setzen der Eigenschaft OldCreateOrder des Formulars
ändert daran auch nichts.
Mein Workaround: ich habe den Destruktor des Formulars überschrieben. Im
überschriebenen Destruktor wird zuerst der vererbte Destruktor aufgerufen,
der wiederum die Controls freigibt. Dabei lösen die ihr OnUpdate-Event aus.
Die aber kann das Formular zu diesem Zeitpunkt noch verarbeiten, denn das
Registry-Objekt existiert dann noch. Erst nach dem Aufruf des vererbten
Destruktors (d. h. erst nach Freigabe der Controls und damit nach dem
letzten OnUpdate-Event) gibt der überschriebene Destruktor auch das
Registry-Objekt frei. Das scheint erstmal zu funktionieren. Jedenfalls
knallt's jetzt nicht mehr und der letzte Status landet auch in der
Registry.
Irgendwie ist mir aber unwohl bei dieser Lösung. Schließlich räumt der
Destruktor des Formulars das ganze Formular weg und damit auch seine
Event-Handler. Der Event-Handler für die OnUpdate-Ereignisse könnte also
nicht mehr mehr gültig sein, wenn die Controls ihre OnUpdate-Events ein
letztes Mal auslösen. Dass o. g. Workaround funktioniert, kann also reiner
Zufall sein.
Die Lösung wäre ein Ereignis, mit dem einem Control mitgeteilt wird, dass
sein Parent (das Formular) gleich freigegeben wird, so dass das Control noch
*vor* der Freigabe des Formulars sein OnUpdate-Ereignis absetzen kann. Das
wäre dann ein Zeitpunkt, zu dem sowohl das Registry-Objekt als auch der
OnUpdate-Handler noch existieren, so dass das OnUpdate-Ereignis sauber
verarbeitet werden kann. Aber welches Ereignis nimmt man dafür? Bin für
sachdienliche Hinweise dankbar.
Gruß
Michael