1

If a user tries to Close the application, I would like to display a Close Query confirmation dialog. If the system shuts down or the task or process is being ended from the Task Manager, I just want to clean up and close.

I tried this code:

private
procedure WMEndSession(var Message: TWMEndSession); message WM_ENDSESSION;

...

procedure TxxxForm.WMEndSession(var Message: TWMEndSession);
begin
  gShuttingDown := Message.EndSession;
end;

...

procedure TxxxForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  Application.ProcessMessages;
  if gShuttingDown then
    CanClose := True
  else
    CanClose := MessageDlg('Are you sure you want to stop the close?', mtConfirmation,
       [mbYes, mbNO], 0) = mrYes;
end;

The popup confirmation is showing whether I close the program or terminate the process from the Task Manager.

  • 1
    Possible Answer: http://stackoverflow.com/a/15619656/2298252 – Günther the Beautiful Mar 05 '15 at 19:12
  • If I remember correctly, you want WM_QUERY_ENDSESSION. – Disillusioned Mar 05 '15 at 19:42
  • I tried adding: ` procedure TxxxForm.WMQueryEndSession(var Message: TWMQueryEndSession); begin gShuttingDown := True; end;` but it still issued the close query dialog when I "End Task" from the Task Manager. – Kevin Davidson Mar 05 '15 at 20:05
  • @ Günther the Beautiful YES, that was the answer. Rather than trying to detect forced closes, I should be detecting user closes. I set my gShuttingDown variable to True, and make if False when I get a WM_SYSCOMMAND with a command type of SC_CLOSE (all the ways a user closes the program). Works perfectly. – Kevin Davidson Mar 05 '15 at 20:26
  • 3
    What's the point of that ProcessMessages call? – David Heffernan Mar 05 '15 at 20:31

1 Answers1

6

WM_QUERYENDSESSION is the correct message to handle. This is sent before WM_ENDSESSION and as per the following VCL code, you can see that WMQueryEndSession immediately calls CloseQuery.

procedure TCustomForm.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  Message.Result := Integer(CloseQuery);
end;

One could consider the above implementation to be a small bug in the VCL code, because as the WM_QUERYENDSESSION documentation states (emphasis mine):

Each application should return TRUE or FALSE immediately upon receiving this message, and defer any cleanup operations until it receives the WM_ENDSESSION message.
Applications can display a user interface prompting the user for information at shutdown, however it is not recommended. After five seconds, the system displays information about the applications that are preventing shutdown and allows the user to terminate them. For example, Windows XP displays a dialog box, while Windows Vista displays a full screen with additional information about the applications blocking shutdown. If your application must block or postpone system shutdown, use the ShutdownBlockReasonCreate function. For more information, see Shutdown Changes for Windows Vista.

If the user fails to respond to a dialog that happens to pop-up within 5 seconds, they would have to terminate the app or cancel shutdown. Unfortunately, since CloseQuery is typically used to prompt the user so it's not a good idea to do so.
NOTE: This was perfectly acceptable pre-Vista, and the above code is probably more of a legacy issue. However, it would be good if CloseQuery were at least adjusted to indicate the source of the close request, making it easy to deal with the specific case of Query End Session.

So given that I do not want any prompts popping up in reponse to WM_QUERYENDSESSION, I actually do the following:

procedure TxxxForm.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  Message.Result := 1;
  { Do not call inherited bc VCL may prevent/delay shutdown via 
    calls to: CloseQuery and CallTerminateProcs (in older 
    versions of Delphi).
    As per Vista requirements, this should not be done. Use
    ShutdownBlockReasonCreate and ShutdownBlockReasonDestroy to
    control when shutdown is allowed. }
end;

Moving on to the second part of your question.
The reason the above doesn't work for End Task via Task Manager is that Task Manager doesn't use the same mechanism as shutdown. It simply sends a WM_CLOSE message to your application.

So it's perfectly acceptable for your application to behave in exactly the same way as it would when closed "normally". In fact, you can give this a try with Notepad:

  • Open Notepad
  • Type some text
  • Go to Task Manager and click End Task for Notepad.
  • You'll observe that Notepad will prompt for confirmation, and Task Manager will show a dialog that Notepad is waiting for a response from the user. (At least in Win7.)

However, if you do want/need to prevent the prompt when closing from Task Manager, you can do so. But the code will have to deal a number of special cases, and may not behave in exactly the same way on all versions of Windows. (So you'll have to ask yourself if it's really worth the effort.)

You have to cater for at least 3 situations:

  1. Close from a custom menu or button within your application.
  2. Close from standard Windows commands: X on top right, Sys Menu or double-click top left corner, Alt+F4 key combination.
  3. And finally closing from Task Manager.

Getting all 3 to behave the way you want gets a little tricky.

  • Option 3 sends a WM_CLOSE. So you'd have to let "default" behaviour close without prompt. And use a Flag to enable prompting. In this way option 3 can close "silently".
  • Option 2 first sends WM_SYSCOMMAND with SC_CLOSE followed by WM_CLOSE. So you can set your Flag in response to the first message. Therefore your user will get a prompt.
  • Option 1 will most likely be implemented by calling Close. But to ensure you get a prompt, you'll have to set your Flag first.
  • Don't forget to clear your Flag if your user chooses not to close the application.

A Final Thought

I personally find "close confirmation" quite annoying. Of course, I am in the habit of saving often, so don't risk losing data.

Basically, the only justifiable reason to prompt on close is to prevent accidental data loss of unsaved data. And you can create a more pleasant user experience if you follow this rule:

When an app opens, always restore it to exactly the same state it was in when it closed.

I.e. if there was unsaved data for a document, store it in temporary files. When the app reopens, the document is automatically placed in the same "edit" state it was in at shutdown.

(Yes. Perhaps this is an oversimplification, but the idea works quite nicely on mobile devices.)

Disillusioned
  • 14,635
  • 3
  • 43
  • 77