10

We have many forms in our application, and I need a global event handler to detect when one of the forms is being destroyed (and then take some action).

p.s: I want to avoid adding code to each form that will need to send a message to the main form when it's about to destroy. also most of the forms are created and destroyed dynamicaly at run-time.

I was thinking about maybe use a global TApplicationEvents.

What is the best approach for this?

Vlad
  • 1,383
  • 14
  • 29
  • 1
    Define closed/destroyed. Define global event handler. Do you have a common base class for all your forms? If you don't, then you are about to regret not having done so. – David Heffernan Dec 07 '13 at 19:58
  • create one OnFormClosedEvent in your main form class and assign this handler when you create a new form? You can assign the same event to multiple forms... – whosrdaddy Dec 07 '13 at 19:59
  • @DavidHeffernan, No I don't have base class. I was thinking about maybe use TApplicationEvents – Vlad Dec 07 '13 at 20:08
  • @whosrdaddy, Im trying to avoid adding code to the existing forms. – Vlad Dec 07 '13 at 20:09
  • @Vlad So, which event are you going to use? And are you going to answer my questions? Or do we have to find out the hard way what you mean? – David Heffernan Dec 07 '13 at 20:11
  • @DavidHeffernan, I made an edit. hope now it's clear. – Vlad Dec 07 '13 at 20:19
  • Yes, that's better. Now, which event in TApplicationEvents did you have in mind? – David Heffernan Dec 07 '13 at 20:25
  • @Vlad It might help if you were more specific about what action you want to take when a form is destroyed. Some actions might be better suited to certain solutions than others. – Disillusioned Dec 08 '13 at 14:08
  • @CraigYoung I need to log the form destructors. But I don't want to go all over my framework and add extra code. bummi's answer looks very promissing so far. You got a +1 from me as well for a very good solution. But that will mean I need to change all my code where I call `TMyForm.Create...`. creating a common class seems like a solid solution, but I need a fast solution for now. – Vlad Dec 09 '13 at 22:20
  • @Vald Would I be correct in assuming you want this logging for debugging purposes? Do you also want to log destruction of short-lived modal dialogs? (I.e. created and destroyed within one method.) The question of whether you need to change all your code where you create forms actually depends on how you're creating them. E.g. If you use a factory method such as `Application.CreateForm` or your own to create all forms; or if you have a common owner for all your forms: Both of these would give you a suitable entry point to register for notification without changing all calls to `TMyForm.Create`. – Disillusioned Dec 10 '13 at 09:55
  • @CraigYoung, I must admit I don't have a solid framework with this particular huge project. Only the main form is created with `Application.CreateForm`. most of the forms are modal, and created like: `frm := TMyForm.Create(Application); ... frm.Free;` but other forms are used as children inside other forms where the owner is not TApplication, but the hosting Form itself. – Vlad Dec 11 '13 at 07:04
  • ... If I have had a common base class, I would not had any issue at all. I could however, use your excellent answer in other parts of our projects. How would I use your suggestion if all the form owners was TApplication without changing each `frm := TMyForm.Create(Application);` by hand? – Vlad Dec 11 '13 at 07:05
  • @Vlad Certainly, I'll add a second answer for this special case. (Though perhaps it should rather be a separate question.) – Disillusioned Dec 11 '13 at 21:05

7 Answers7

7

What you are wanting is for the framework to fire an event when a form is destroyed. When a form is destroyed, its destructor is run. So, in order for the framework to fire such an event, it would need to be implemented from within the form's destructor. If you take a look inside TCustomForm.Destroy, you will find that there is not such event.

From this we can conclude that there can be no application wide event fired whenever a form is destroyed. This means that you will have to implement a solution yourself. One obvious way to make this happen is to introduce a common base class for all your forms. Ensure that every form in your program derives ultimately from this common base class. Then arrange for the base class to surface an event that is fired whenever an instance is destroyed.


There seems to be some mis-understanding about what I am saying above. Craig demonstrates how to subscribe to notification of a single form's destruction. The ability to do that does not contradict what I am saying. My point is that there is no mechanism in place that allows you to subscribe to receive notification when any form is destroyed.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 2
    What about the `OnDestroy` event? You could use the `TScreen.OnActiveFormChange` event to assign an `OnDestroy` handler to every available Form. – Remy Lebeau Dec 08 '13 at 04:16
7

Contrary to David's answer, there is a suitable framework. It's built in higher up in the class hierarchy at TComponent. Sir Rufo is on the right track, but you don't need to force your forms to be owned by this object.

You're welcome to write any number of classes that can take specialised action when a form (or any other component for that matter) is destroyed. E.g.

TDestroyedFormLogger = class(TComponent)
protected
  { Write to log file when forms are destroyed. }
  procedure Notification(AComponent: TComponent; Operation: TOperation); override;
end;

TMenuManager = class(TComponent)
protected
  { Remove/hide a menu item corresponding to the form that has been destroyed. }
  procedure Notification(AComponent: TComponent; Operation: TOperation); override;
end;

Now whenever you create a form, simply set a notification as follows (assuming you have given yourself access to suitable instances of the above objects):

LForm := TMyForm.Create(Application);
LForm.FreeNotification(DestroyedFormLogger);
LForm.FreeNotification(MenuManager);

This approach is better than using the OnDestroy event because that permits only 1 observer, whereas FreeNotification permits any number of observers.

NOTE: As with any useful technique, don't force-fit a problem to the technique. There may be a more appropriate technique to your specific problem. E.g. The MenuManager idea might be better solved by using the global Screen object to iterate forms OnPopup.


EDIT: Explanation of Observer Pattern

The TComponent notification mechanism is a built-in implementation of the Observer Pattern for when a component is destroyed. FreeNotification (perhaps not ideally named) is the equivalent of registerObserver and RemoveNotification the equivalent of unregisterObserver.

The whole point of the observer pattern is that the subject being observed (sometimes called publisher) has no type-specific knowledge of the objects that are observing it (sometimes called subscribers). Publishers only know that they are able to call a generic notification method on each registered subscriber (observer). This allows objects to be loosely coupled from those that are watching it. In fact the publisher doesn't even need to be observed at all. Obviously the registration method needs to be called either from the subscribers themselves or from a third-party - otherwise the decoupling objective is defeated.

Observers can be implemented at varying degrees of complexity. The simplest being an event or callback. The most complex being a dispatcher that manages registrations in-between and independent of both publishers and subscribers. The dispatcher might even implement thread switching so that publishers don't even get impacted by performance side-effects of slow subscribers.

TComponent's observer implementation has a limitation that both the publisher and subscriber must inherit from TComponent. Basically any component can register with another component to be notified of its destruction.

Perhaps the most common use of this feature in Delphi is: When component A has a reference to component B; If component B is destroyed, component A is notified so that it can set its reference to nil.

Community
  • 1
  • 1
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • That doesn't contradict me at all. The calls to FreeNotification are not built in. How are you going to make sure they are made for every form? The framework doesn't fire an event when every form is destroyed. You have to make that happen. Unless you add a common base class, or similar, your approach is going to involve duplication on an epic scale. – David Heffernan Dec 08 '13 at 09:10
  • @David You gave the impression that there is no built in functionality to achieve this - which is patently not true. Yes, there's no event (the most trivial form of observer) in `TCustomForm`'s destructor, but there's a more flexible observer in `TComponent`'s destructor. As for where to set up the notifications, that's up to @Vlad. He can control exactly which forms to configure. If he has appropriate factory classes for his dynamically created forms it will be easy. This solution has absolutely no need to change ownership or form hierarchies of existing forms. – Disillusioned Dec 08 '13 at 09:25
  • There's no built in functionality that will exposes an event you can subscribe too. You have to change all the forms. Either the type as I suggested, or at every single constructor as you suggest. Which of those options would you choose? Subscribing to free notifications is better than Remy's OnDestroy because free notifications are multi-cast. I grant you that. But I really don't think you are contradicting me. The asker it looking for an application wide event. There is none. – David Heffernan Dec 08 '13 at 09:34
  • 2
    PS: Taking [Remy's comment](http://stackoverflow.com/questions/20445859/how-to-detect-that-a-form-is-being-destroyed-across-the-application/20451630#comment30553360_20446480) on your answer, `TScreen.OnActiveFormChange` is an option that could be used to assign the FreeNotification's with a minimum of extra code. (Though personally I would prefer the more explicit assignment in a factory method.) – Disillusioned Dec 08 '13 at 09:35
  • 2
    What if the form was never activated? – David Heffernan Dec 08 '13 at 09:37
  • @David Your suggestion is to change the form's class, mine is to call a method on the form. (And doesn't have to be within the constructor.) – Disillusioned Dec 08 '13 at 09:37
  • Yes I know that. I don't see what point you make. Which would you choose? You'd choose the option that forced you to remember to call a method every time you called a constructor? – David Heffernan Dec 08 '13 at 09:52
  • @David I prefer the option that doesn't bloat my forms with extra functionality that isn't ***needed*** for the (currently working) form to do its job. I don't know what "some action" Vlad wishes to "take". But introducing a new level to the class hierarchy simply binds more dependencies to the class which might be undesirable if you wish to reuse the form in a different application. Yes I prefer to explicitly code "I want my form to do optional X". Would you choose the option that forced you to remember to **do something extra to disable optional functionality in cases you don't want it**? – Disillusioned Dec 08 '13 at 10:40
  • So you prefer the option that forces the functionality to be managed at each and every place where you instantiate the object? If each and every object has to do the exact same thing, doesn't that make that thing part of the type? – David Heffernan Dec 08 '13 at 10:44
  • @David For your answer, it might help to reread my previous comment. From the question it seems currently all forms work correctly. So the requirement to "take some action when one of the forms is being destroyed" belongs somewhere else (it's not a form requirement). Hence the _responsibility_ also belongs elsewhere (perhaps the application, a form manager, ...). The forms would only need to change if they did not already implement a suitable notification mechanism. Responsibility is perhaps the most import OO design consideration: Wrong objects doing wrong things leads to tight coupling. – Disillusioned Dec 08 '13 at 11:19
  • All solutions, including what you outline in your answer, are driven from the form's destructor. – David Heffernan Dec 08 '13 at 11:27
  • @David Yes. And your point is? You said the existing destructor didn't support the necessary functionality, I said it did. – Disillusioned Dec 08 '13 at 11:31
  • It doesn't. Because you have to graft on the subscribing mechanism outside. Your idea is not that much better than adding something to every place where the form is destroyed. Now, free notifications would work just great with a common base class. The common base class could register a free notification and it would be all good. – David Heffernan Dec 08 '13 at 11:33
  • @David I think you're missing the point of a publish-subscribe model (observer pattern). The `publisher` (observable) provides an interface for `subscribers` (observers) to `subscribe` (register) to be notified of certain events. Publishers have zero knowledge about what is observing them. It simply sends out `notifications` whenever appropriate to whatever is looking. This decouples publisher and subscriber. If the publisher (form) also does the registration, then it becomes tightly coupled to the subscriber. Attempting to make registration generic just reinvents the `FreeNotification` wheel. – Disillusioned Dec 08 '13 at 11:53
  • You think I don't understand the observer pattern? Really? I think I understand it. What the asker wants is to have a **single observer** listening to an event that occurs when **any** form is destroyed. That's not what you have. The key difference is that there should be a single act the subscribes to the event. The framework does not support that. That's what I said. I find it somewhat galling that your start your answer with "Contrary to David's answer" when you have contradicted nothing I said. – David Heffernan Dec 08 '13 at 12:10
  • @David Yes, really! And only because you said: "The common base class could register a free notification and it would be all good." If the publisher registers on behalf of the subscriber, then it already has a direct reference to the subscriber. So why bother registering at all? Also considering I did contradict you, I believe you're actually galled by the fact that I corrected you. Personally, I find it disappointing that a brilliant expert seems to consider himself incapable of error; and so becomes argumentative just to defend his position. I'm done wasting time here. – Disillusioned Dec 08 '13 at 12:41
  • You don't seem to understand my point that it is inconvenient to have to subscribe every single time you instantiate an object. Yes, everytime you instantiate it. Every single time you call a constructor you need to add this subscription. How do you address that? You've not done so yet. That's my point. I'd like to hear your views on that. What the asker really wants is an event in TApplication. For that to work, code in TCustomForm.Destroy needs to trigger the event. It really needs framework support that is lacking. – David Heffernan Dec 08 '13 at 12:47
  • *And only because you said: "The common base class could register a free notification and it would be all good." If the publisher registers on behalf of the subscriber, then it already has a direct reference to the subscriber. So why bother registering at all?* Fair enough. So you would instead override the destructor and trigger the event there. – David Heffernan Dec 08 '13 at 13:12
  • *Personally, I find it disappointing that a brilliant expert seems to consider himself incapable of error; and so becomes argumentative just to defend his position. I'm done wasting time here.* I admit I am wrong plenty often. I'm sure you've seen me do that in the past. I just don't think I got this one wrong. Are you really denying my right to argue. If your argument is better, it will prevail. Now, I'd still like to see your explanation of why you prefer to force every instantiation of a form object in the program to subscribe individually. Can you justify that viewpoint? – David Heffernan Dec 08 '13 at 13:15
  • 2
    It's disappointing that you didn't answer the points I raised. I don't think you were wasting your time. – David Heffernan Dec 08 '13 at 16:24
  • @David *It's disappointing that you didn't answer the points I raised.* Please reread my answer and **all** my comments. I **have** answered the points you raised. **(1)** I've explained that depending on how the forms are constructed, the registration can be centralised. (I've recently added an answer demonstrating one case.) **(2)** I've answered your question about repeated registration - **reasons included!** Just because you're incredulous of my answer doesn't mean I didn't answer. So, for me to repeat my answers ***would be a waste of time!*** – Disillusioned Dec 11 '13 at 22:28
  • I think it's unlikely that there will be a single call to construct forms. Even with factories there are likely to be many. Which is the weakness of what you suggest. – David Heffernan Dec 12 '13 at 04:31
6

This is not the best practice (have a look at David's answer) but a way to go.


Since every form can have an owner (type TComponent) and this owner gets notified, if a child component is destroyed, just create a global form owner and pass this as the owner of every created form you want to get notified on destroy.

You have to override the TComponent.Notification method and do what you have to (e.g. fire an event)

unit GlobalViewHolder;

interface

  uses
    Forms,
    Classes;

  type
    TComponentNotificationEvent = procedure( Sender : TObject; AComponent : TComponent; Operation : TOperation ) of object;

    TGlobalViewHolder = class( TComponent )
    private
      FOnNotification : TComponentNotificationEvent;
    protected
      procedure Notification( AComponent : TComponent; Operation : TOperation ); override;
    public
      property OnNotification : TComponentNotificationEvent read FOnNotification write FOnNotification;
    end;

  // small and simple singleton :o) 

  function ViewHolder : TGlobalViewHolder;

implementation

  var
    _ViewHolder : TGlobalViewHolder;

  function ViewHolder : TGlobalViewHolder;
    begin
      if not Assigned( _ViewHolder )
      then
        _ViewHolder := TGlobalViewHolder.Create( Application );

      Result := _ViewHolder;
    end;

  { TGlobalViewHolder }

  procedure TGlobalViewHolder.Notification( AComponent : TComponent; Operation : TOperation );
    begin
      inherited;
      if Assigned( OnNotification )
      then
        OnNotification( Self, AComponent, Operation );
    end;

end.

The main form owner is always Application but there is no need to track this.

Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
6

A constraint on modifying code in existing forms, or creation of forms, as can be seen from other answers and comments, leaves hacks and hooks. A local CBT hook, f.i., would be a little work but probably work fine. Below is one of the simpler hacky solutions.

Screen global object holds a list of forms at all times via a regular TList. TList has a virtual Notify procedure which is called every time an item is added/removed. The idea is to employ a TList derivative that overrides this method and use it in the Screen object.

type
  TNotifyList = class(TList)
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  end;

procedure TNotifyList.Notify(Ptr: Pointer; Action: TListNotification);
begin
  inherited;
  if (Action = lnDeleted) and (csDestroying in TForm(Ptr).ComponentState) and
      (TForm(Ptr) <> Application.MainForm) then
    // do not use ShowMessage or any 'TForm' based dialog here
    MessageBox(0,
        PChar(Format('%s [%s]', [TForm(Ptr).ClassName, TForm(Ptr).Name])), '', 0);
end;

Testing for csDestroying is required because the Screen adds/removes forms to its list not only when forms are created/destroyed but also when they are activated etc..

Then make the Screen use this list. This requires an "accessing private fields" hack, as the FForms list is private. You can read about this hack on Hallvard Vassbotn's blog. It also requires "changing the class of an object at run time" hack. You can read about this hack on Hallvard Vassbotn's blog.

type
  THackScreenFForms = class
{$IF CompilerVersion = 15}
    Filler: array [1..72] of Byte;
{$ELSE}
    {$MESSAGE ERROR 'verify/modify field position before compiling'}
{$IFEND}
    Forms: TList;
  end;


procedure TForm1.FormCreate(Sender: TObject);
begin
  PPointer(THackScreenFForms(Screen).Forms)^ := TNotifyList;
end;

Note that the notification will be called for every form destruction. This also includes forms created through MessageDlg, ShowMessage etc..

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • I ended up using your solution. but I believe David's answer deserves to be the accepted one because I strongly think it's the *right* way to go. I hope a made the right desicion by accepting the best answer rather than accepting the one I actually used. Thank you so much for this great answer! – Vlad Dec 11 '13 at 06:51
  • @Vlad - You're welcome! I see your point, I don't think there's anything wrong with an answer you haven't chosen to implement being more helpful to you, in the long run for instance. – Sertac Akyuz Dec 11 '13 at 10:00
  • BTW, How did calculate that Filler size is 72? – Vlad Dec 13 '13 at 17:23
  • 2
    @Vlad - The debugger is able to read private fields. Pause the debugger in a VCL forms application and f.i. add this to a watch `Integer(@Screen.FForms)-Integer(Screen)`. This gives 76 for a D7 application. If you exclude `SizeOf(Screen.FForms)` from it, you'll have the space that fields before FForms occupy. – Sertac Akyuz Dec 13 '13 at 17:32
  • I love your method. I will make this the accepted answer after all. I saw this technique also in TNT Unicode controls, so I'm pretty convinced that this method is solid enough. – Vlad Dec 13 '13 at 17:41
  • It's hackier than it needs to be. Crack the private with a helper, and do runtime subclassing with a virtual method interceptor. But it's essentially the method that I would use. – David Heffernan Dec 13 '13 at 17:52
  • 1
    @David - Class helpers and virtual method interceptors are not available in D7. Still there may be better ways which I'm not aware of.. – Sertac Akyuz Dec 13 '13 at 18:09
  • Ah, forgot about that. In D7 I think what you did is the only way. – David Heffernan Dec 13 '13 at 18:19
3

Personally I'd prefer David Heffernan's solution since all my forms are allways based on a template and it would be the cleanest and easiest to implement way.
But coming from you demand
p.s: I want to avoid adding code to each form that will need to send a message to the main form when it's about to destroy. also most of the forms are created and destroyed dynamicaly at run-time.
you would be able to patch Destroy to an own method.
I'd take the latest called destructor in the chain and patch TObject.Destroy to TMyClass.Destroy. The place to implement should be the project.
The code for patching is taken from David Heffernan 's answer on Patch routine call in delphi and only included to keep the answer complete, credits regarding this code go there.

program AInformOnCloseForms;

uses
  Forms,
  Classes,
  Windows,
  Dialogs,
  Unit3 in 'Unit3.pas' {Mainform},
  Unit4 in 'Unit4.pas' {Form2};

{$R *.res}

//   PatchCode and RedirectProcedure are taken from David Heffernans answer
//   https://stackoverflow.com/a/8978266/1699210
//   on "Patch routine call in delphi" , credits regarding this code go there
procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

type

TMyClass=Class(TObject) // Dummy to handle "events"
  public
  Destructor Destroy;override;
End;


destructor TMyClass.Destroy;
begin
                                          // pervent recursion from call to Showmessage
 if (Self.InheritsFrom(TCustomForm)) and (Self.ClassName<>'TTaskMessageDialog') then
      Showmessage(Self.ClassName);
end;



begin
  RedirectProcedure(@TObject.Destroy,@TMyClass.Destroy);

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainform, Mainform);
  Application.CreateForm(TForm2, Form2);
  Application.Run;

end.
Community
  • 1
  • 1
bummi
  • 27,123
  • 14
  • 62
  • 101
  • 2
    very nice indeed. but why did you declare `TMyClass=Class(TObject)` and not `TMyClass=Class(TCustomForm)`? (which works just fine) – Vlad Dec 09 '13 at 22:16
  • 2
    Also, how do you revers the patch? something like `UnPatchCode`? – Vlad Dec 09 '13 at 22:25
  • @Vlad You would need to modify `Patch` to return the original memory before the call to `Move`. And then `UnpatchCode` would undo the original change and restore the original. Why would you need to do that though? – David Heffernan Dec 10 '13 at 09:26
  • @DavidHeffernan, I prefer to be on the safe side. In this answer: http://stackoverflow.com/a/6905461/1177944 it shows you need to UnHook the patch. I still don't understand why bummi declare TMyClass as TObject rather than TCustomForm. is that important? – Vlad Dec 11 '13 at 06:54
  • Sometimes you need to unhook, often you don't. I use quite a few hooks in my program to fix RTL/VCL problems and never unhook. – David Heffernan Dec 11 '13 at 07:25
  • 1
    @Vlad, responding to the second part of your question, try to add `ReportMemoryLeaksOnShutDown := true;` before RedirectProcedure if you are using an other ancestor than TObject. Since you are replacing the procedure you would have to reimplement anything done in the destructor if this class. – bummi Dec 11 '13 at 07:26
1

As per Vlad's request this expands on my original answer by explaining how to register all forms owned by Application without any changes to the construction of each form. I.e. forms created using TMyForm.Create(Application); and by implication also Application.CreateForm(TMyForm, MyForm);.

The original answer doesn't specify any particular means of registering for FreeNotification because the options vary according to how forms are created. Since the question answered did not put any constraints on how the forms are created, the original answer is more appropriate in the general case.

If we could ensure that Application referred to a custom subclass of TApplication, the problem would be fairly easy to solve by overriding TApplication.Notification;. That's not possible, so this special case leverages the fact that the component ownership framework notifies all owned components when another component is added or removed. So basically all we need is a component tracker also owned by Application and we can react on its "sibling" notifications.

The following test case will demonstrate that new notifications work.

procedure TComponentTrackerTests.TestNewNotifications;
var
  LComponentTracker: TComponentTracker;
  LInitialFormCount: Integer;
  LForm: TObject;
begin
  LComponentTracker := TComponentTracker.Create(Application);
  try
    LComponentTracker.OnComponentNotification := CountOwnedForms;
    LInitialFormCount := FOwnedFormCount;
    LForm := TForm.Create(Application);
    CheckEquals(LInitialFormCount + 1, FOwnedFormCount, 'Form added');
    LForm.Free;

    CheckEquals(LInitialFormCount, FOwnedFormCount, 'Form removed');
  finally
    LComponentTracker.Free;
  end;
end;

procedure TComponentTrackerTests.CountOwnedForms(AComponent: TComponent; AOperation: TOperation);
begin
  if (AComponent is TCustomForm) then
  begin
    case AOperation of
      opInsert: Inc(FOwnedFormCount);
      opRemove: Dec(FOwnedFormCount);
    end;
  end;
end;

TComponentTracker is implemented as follows:

TComponentNotificationEvent = procedure (AComponent: TComponent; AOperation: TOperation) of object;

TComponentTracker = class(TComponent)
private
  FOnComponentNotification: TComponentNotificationEvent;
  procedure SetOnComponentNotification(const Value: TComponentNotificationEvent);
  procedure DoComponentNotification(AComponent: TComponent; AOperation: TOperation);
protected
  procedure Notification(AComponent: TComponent; AOperation: TOperation); override;
public
  property OnComponentNotification: TComponentNotificationEvent read FOnComponentNotification write SetOnComponentNotification;
end;

procedure TComponentTracker.DoComponentNotification(AComponent: TComponent; AOperation: TOperation);
begin
  if Assigned(FOnComponentNotification) then
  begin
    FOnComponentNotification(AComponent, AOperation);
  end;
end;

procedure TComponentTracker.Notification(AComponent: TComponent; AOperation: TOperation);
begin
  inherited Notification(AComponent, AOperation);
  DoComponentNotification(AComponent, AOperation);
end;

procedure TComponentTracker.SetOnComponentNotification(const Value: TComponentNotificationEvent);
var
  LComponent: TComponent;
begin
  FOnComponentNotification := Value;
  if Assigned(Value) then
  begin
    { Report all currently owned components }
    for LComponent in Owner do
    begin
      DoComponentNotification(LComponent, opInsert);
    end;
  end;
end;

WARNING

You could implement anything you choose in the OnComponentNotification event handler. This would include logging that the form is "destroyed". However, such a simplistic approach would actually be flawed because TComponent.InsertComponent allows a component's owner to be changed without destroying it.

Therefore to accurately report destruction, you would have to combine this with using FreeNotification as in my first answer.

This is quite easily done by setting LComponentTracker.OnComponentNotification := FDestructionLogger.RegisterFreeNotification; where RegisterFreeNotification is implemented as follows:

procedure TDestructionLogger.RegisterFreeNotification(AComponent: TComponent; AOperation: TOperation);
begin
  if (AComponent is TCustomForm) then
  begin
    case AOperation of
      opInsert: AComponent.FreeNotification(Self);
    end;
  end;
end;
Community
  • 1
  • 1
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • This is a bit complicated for me, but never the less, I have much to learn from your answers! :) – Vlad Dec 13 '13 at 17:46
0

A very simple approach could be keeping track of the Form count. When it lowers, then there is a Form destroyed. Check in Application.OnIdle:

procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  if Screen.CustomFormCount < FFormCount then
    FormDestroyed;
  if FFormCount <> Screen.CustomFormCount then
    FFormCount := Screen.CustomFormCount;
end;

Depending on what action should be taken, you can loop through Screen.CustomForms to determine which Form was destroyed.

NGLN
  • 43,011
  • 8
  • 105
  • 200