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

2

12

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!

Brutality answered 24/5, 2012 at 20:56 Comment(2)
Are you tried trapping the WM_QUERYENDSESSION message?Luftwaffe
I moved my comment to an answer.Salomesalomi
N
9

Your problems stem from the use of OnCloseQuery which is the wrong event to be using. Remy's answer explains how to workaround Windows shutdown being blocked by the default VCL end session message handling. And this in turn is caused by setting CanClose to False in the OnCloseQuery event.

That workaround will get the job done but there's a much simpler way to deal with this. Instead of stopping the form from closing, let it go ahead and close. Remove your OnCloseQuery event altogether. Replace it with an OnClose event.

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caNone;
  Visible := False;
end;

This rather trivial bit of code is enough to make your app minimize to the tray when the main form is closed.

Negus answered 24/5, 2012 at 21:29 Comment(11)
That is because your code is not using the OnCloseQuery event. It is using the OnClose event instead, which is not tied to the WM_QUERYENDSESSION message like OnCloseQuery is.Salomesalomi
@Remy Yes, I'm a little slow on the uptake aren't I?! It would seem that OnClose is the right solution here. No point getting embroiled in end session messages.Negus
I need both WM_QUERYENDSESSION and WM_ENDSESSION - I already use OnCanCloseQuery to place program into tray when WM_CLOSE is triggered.Brutality
No you don't you just need the code in this answer as your main form's OnClose event. Get rid of OnCloseQuery, that's the root of your problem. It's really that simple. Our apps do exactly the same thing. Mine does it with a 2 line event handler. Yours can too.Negus
I understand what you wrote but how with simple FormClose I will know who sent WM_CLOSE event to form? Does Windows sent it over Form Close button or Windows sent it on Windows Shutdown?Brutality
Aha, you mean Windows will close it anyway if I don't touch OnCloseQuery.. Understand. OK. Yes, I can adopt entire code this way.Brutality
You don't need to know whether its user clicking close button or Windows shutting down. When Windows is closing down your app will run its OnClose event and make its main form invisible. Then it will be terminated. And then Windows will go down. You really don't need to do any more than this. I'm sorry to go on about this, but I really believe that this is the better and simpler option.Negus
I agree. I will adopt project to close this way. Thanks for help.Brutality
You are welcome. I'm glad I'm not going completely mad. You had me worried there for a while!! ;-)Negus
There is no WM_CLOSE message issued to the app during Windows shutdown, so the OnClose event will not be triggered, and thus your app will not close to the Tray. It will just terminate normally. Only the OnCloseQuery event is triggered during shutdown, because the VCL ties the WM_QUERYENDSESSION message to that event.Salomesalomi
Is it possible to use this approach if I want to display a message asking the user if he wants to close the form because some editions were not saved? I couldn't see a way, because I need to display the message and check whether I am going to close or not.Dubious
S
17

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;
Salomesalomi answered 24/5, 2012 at 22:7 Comment(3)
David, your and Remy's solutions are identical. All I needed are this messages - and when system sends them. You both help me and I gave you PLUS, but Remy made it in the form of answer so I've accepted is as help. But both solutions are the same. Idea (logic) is almost identical.Brutality
No, my answer is very different. Perhaps you have not refreshed the page. You are talking about my comment. I believe that Remy's code in this answer is far more complicated than you need.Negus
Yes, I was talking about your commend, not answer. I can't do anything with this code you wrote cause I already used identical code to detect closing and instead to halt program I put it into tray. I needed other event then WM_CLOSE - I needed ENDSESSION even cause Win triggers it on Exit. With your code I couldn't made diff did Close button brought this message or Win ShutdownBrutality
N
9

Your problems stem from the use of OnCloseQuery which is the wrong event to be using. Remy's answer explains how to workaround Windows shutdown being blocked by the default VCL end session message handling. And this in turn is caused by setting CanClose to False in the OnCloseQuery event.

That workaround will get the job done but there's a much simpler way to deal with this. Instead of stopping the form from closing, let it go ahead and close. Remove your OnCloseQuery event altogether. Replace it with an OnClose event.

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caNone;
  Visible := False;
end;

This rather trivial bit of code is enough to make your app minimize to the tray when the main form is closed.

Negus answered 24/5, 2012 at 21:29 Comment(11)
That is because your code is not using the OnCloseQuery event. It is using the OnClose event instead, which is not tied to the WM_QUERYENDSESSION message like OnCloseQuery is.Salomesalomi
@Remy Yes, I'm a little slow on the uptake aren't I?! It would seem that OnClose is the right solution here. No point getting embroiled in end session messages.Negus
I need both WM_QUERYENDSESSION and WM_ENDSESSION - I already use OnCanCloseQuery to place program into tray when WM_CLOSE is triggered.Brutality
No you don't you just need the code in this answer as your main form's OnClose event. Get rid of OnCloseQuery, that's the root of your problem. It's really that simple. Our apps do exactly the same thing. Mine does it with a 2 line event handler. Yours can too.Negus
I understand what you wrote but how with simple FormClose I will know who sent WM_CLOSE event to form? Does Windows sent it over Form Close button or Windows sent it on Windows Shutdown?Brutality
Aha, you mean Windows will close it anyway if I don't touch OnCloseQuery.. Understand. OK. Yes, I can adopt entire code this way.Brutality
You don't need to know whether its user clicking close button or Windows shutting down. When Windows is closing down your app will run its OnClose event and make its main form invisible. Then it will be terminated. And then Windows will go down. You really don't need to do any more than this. I'm sorry to go on about this, but I really believe that this is the better and simpler option.Negus
I agree. I will adopt project to close this way. Thanks for help.Brutality
You are welcome. I'm glad I'm not going completely mad. You had me worried there for a while!! ;-)Negus
There is no WM_CLOSE message issued to the app during Windows shutdown, so the OnClose event will not be triggered, and thus your app will not close to the Tray. It will just terminate normally. Only the OnCloseQuery event is triggered during shutdown, because the VCL ties the WM_QUERYENDSESSION message to that event.Salomesalomi
Is it possible to use this approach if I want to display a message asking the user if he wants to close the form because some editions were not saved? I couldn't see a way, because I need to display the message and check whether I am going to close or not.Dubious

© 2022 - 2024 — McMap. All rights reserved.