Detect if Windows closing or application tries to close from system menu (WM_CLOSE)

Ivan Mark picture Ivan Mark · May 24, 2012 · Viewed 11.2k times · Source

I'm having Tray application.

Onj FormCloseQuery I check if program should goto tray and instead of closing it I put it in tray (CanClose := False)

But if Windows tries to close my application because of Windows shutdown I want not to move my app into tray but to close it.

Win7 terminates my app, but XP doesn't close because my app remains in Tray.

How can I detect if Windows is some "shutting down" mode or not?

Thanks!

Answer

Remy Lebeau picture Remy Lebeau · May 25, 2012

If the OnCloseQuery event is triggered in response to a WM_QUERYENDSESSION message, setting CanClose=False will cause the message to return FALSE.

On XP and earlier, that will cancel Windows shutdown. Up to that point, any app that had received a WM_QUERYENDSESSION message will receive a WM_ENDSESSION message with its wParam value set to FALSE telling those apps NOT to terminate themselves. This is why your app goes to the Tray and does not exit during Windows shutdown.

Microsoft changed this behavior in Windows Vista so apps cannot cancel Windows shutdown via WM_QUERYENDSESSION anymore. That is why Windows Vista and later will terminate your app. There is a whole new API introduced if an app needs to stop Windows shutdown on purpose.

This is documented on MSDN:

Application Shutdown Changes in Windows Vista

To do what you are asking, you must intercept the WM_QUERYENDSESSION message directly so you can determine if OnCloseQuery is being called due to Windows shutdown or not. For example:

type
  TForm1 = class(TForm)
  private
    procedure WMQueryEndSession(var Message: TWMQueryEndSession); message WM_QUERYENDSESSION;
    procedure WMEndSession(var Message: TWMEndSession); message WM_ENDSESSION;
  end;

var
  ShuttingDown: Boolean = False;

procedure TForm1.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  ShuttingDown := True;
  inherited;
end;

procedure TForm1.WMEndSession(var Message: TWMEndSession);
begin
  ShuttingDown := Message.EndSession;
  inherited;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := ShuttingDown;
  if not ShuttingDown then
  begin
    // your Tray logic here ...
  end;
end;