Ian Branch 127 Posted May 18, 2020 Hi Team, D10.3.3. I want to detect Windows shutdown in Win 7 and Win 10 in order to ensure data is saved before the App gets clobbered. I have tried a few examples using.. { Private declarations } procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION; plus its associated routine, without success. I suspect the above may be OK in Win 7, haven't tried it yet, but not for Win 10. Anybody got code working as desired in Win 10? Regards & TIA, Ian Share this post Link to post
Angus Robertson 574 Posted May 18, 2020 You should also listen for WM_POWERBROADCAST, but this has to be set-up to work, WM_ENDSESSION and WM_QUIT, lots of ways of closing down. Angus Share this post Link to post
Der schöne Günther 316 Posted May 18, 2020 Not sure what you need WM_POWERBROADCAST for, but just working with WM_ENDSESSION and WM_QUERYENDSESSION has worked well for several kiosk applications, for several years now. Share this post Link to post
Lars Fosdal 1792 Posted May 18, 2020 You also have WM_WTSSESSION_CHANGE that give some messages that allows you to take precautionary actions.https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsregistersessionnotification https://docs.microsoft.com/en-gb/windows/desktop/TermServ/wm-wtssession-change procedure ON_WM_WTSSESSION_CHANGE(var Message: TMessage); message WM_WTSSESSION_CHANGE; Note that WTS_SESSION_TERMINATE does not actually work, though. Share this post Link to post
Angus Robertson 574 Posted May 18, 2020 Power broadcasts give warnings when the system is about to close down, while designed for battery powered kit, they do work on desktops, my PCs have USB connection to the APC UPS so can close down early. Can not say specifically under what circumstances these messages appear, but belt and braces when saving data... PBT_APMBATTERYLOW: mess := 'Battery power is low' ; PBT_APMQUERYSUSPEND: mess := 'Request for permission to suspend' ; PBT_APMSUSPEND: mess := 'System is suspending operation' ; Angus Share this post Link to post
David Heffernan 2345 Posted May 18, 2020 Getting notification isn't really what concerns you. You know how to do that. What you need to be able to do is block shutdown until you have finished saving any data. The way that is handled changed in Vista. The documentation you need starts here: https://docs.microsoft.com/en-us/windows/win32/shutdown/system-shutdown 2 Share this post Link to post
Ian Branch 127 Posted May 19, 2020 Hi Guys, Thanks to all for your input. I am out of my league here but this is what I have att from Dr Google. ;-). private { Private declarations } DataToBeSaved: Boolean; procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession; // detect Windows shutdown message procedure SaveData; // routine to save data to disk Then.. procedure TMainForm.WMQueryEndSession(var Msg: TWMQueryEndSession); begin if DataToBeSaved then if MessageDlg('Windows is shutting down! First save data changes?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin Msg.Result := 0; // disallow Windows from shutting down beep; SaveData; end; Msg.Result := 1; // allow Windows shutdown end; // Set DataToBeSaved to False after saving data procedure TMainForm.SaveData; begin // Save data to disk files // ... DataToBeSaved := False; end; ..... ..... procedure TMainForm.FormCreate(Sender: TObject); begin // DataToBeSaved := True; // .... .... In my Win 10 dev PC, when I tell windows to shutdown it goes straight to the green Shutting Down screen. I never see the MessageDlg prompt. 😞 Am I missing some other nuance? I noted David's comments and reference. I need this to work on my Win 10 PC as well as the Customer's Win 7, later I hope to be Win 10, PCs. Again, thoughts, suggestions, code appreciated. Regards & TIA, Ian Share this post Link to post
Cristian Peța 103 Posted May 19, 2020 (edited) 4 hours ago, Ian Branch said: I never see the MessageDlg prompt. This is documented here (read Remarks): https://docs.microsoft.com/en-us/windows/win32/shutdown/wm-queryendsession If you really need to stop the shutdown: "Each application should return TRUE or FALSE immediately upon receiving this message" So you must return immediately, not showing a message and wait... But... "Applications should respect the user's intentions and return TRUE" If possible it's better to save data in other place and next time you app is running again ask the user if he want to keep this data. Or even better, if you can, restore the app state exactly how it was at shutdown and you don't need to ask anything. The user will see your app exactly how it was at shutdown. That's how mobile app are supposed to work but it's nice also for desktop. Edited May 19, 2020 by Cristian Peța Share this post Link to post
Ian Branch 127 Posted May 19, 2020 Hi Cristian, Thank you for your input. I don't want to stop it, just delay/pause it while the App writes to the log and closes the database gracefully. Hence the procedure TMainForm.SaveData; Then Windows can shut down. ATT the message display is purely to confirm that something is happening in the routine. Share this post Link to post
Cristian Peța 103 Posted May 19, 2020 (edited) 4 hours ago, Ian Branch said: if MessageDlg('Windows is shutting down! First save data changes?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin This is not just a message. You are waiting for the user to press You or No. And in this time you are not returning to the OS. Then the OS will simply kill you app. Edited May 19, 2020 by Cristian Peța Share this post Link to post
Ian Branch 127 Posted May 19, 2020 Hi Cristian, Yes I know. My issue/point is that I am NOT getting the message. Hence it would seem that the procedure isn't being called. Share this post Link to post
David Heffernan 2345 Posted May 19, 2020 You aren't doing what the documentation I linked to instructs you to do. Share this post Link to post
Cristian Peța 103 Posted May 19, 2020 (edited) 22 minutes ago, Ian Branch said: My issue/point is that I am NOT getting the message. I must repeat: this is documented and is working "as designed" If you want an other behavior and don't want to do what documentation says the you need to find an other OS. Documentation says: "If your application must block or postpone system shutdown, use the ShutdownBlockReasonCreate function." Then when you are finished use ShutdownBlockReasonDestroy. Edited May 19, 2020 by Cristian Peța Share this post Link to post
Ian Branch 127 Posted May 19, 2020 Hi Cristian, Clearly I don't understand the documentation. Hi David, IIUC the code had the 'True' & 'False' around the wrong way?? I also think the Msg.Result := 0; // disallow Windows from shutting down was in the wrong place and should have been before the ShowMessage, AND should have been.. Msg.Result := 1; // disallow Windows from shutting down I'm trying here guys but this is all new to me. Share this post Link to post
Ian Branch 127 Posted May 19, 2020 OK. Didn't see the "ShutdownBlockReasonCreate function" note. Had a look. It's cryptic to me and doesn't really give me any insight as to what is required. 😞 Guys, Let's forget about it. Thank you for your attempts to educate me. I apologise for my lack of knowledge/skill. Share this post Link to post
David Heffernan 2345 Posted May 19, 2020 You can't have read the documentation. Because you aren't calling ShutdownBlockReasonCreate. I know that you want to solve this quickly. But that expectation is unrealistic. It will take you time to wade through this, try it out, read and understand example code, etc. For sure one of us could write you some examples but unless you understand it will you really be able to integrate it into your code? Just because you don't understand this now does not mean that you can't learn it. It just requires self belief and persistence. Share this post Link to post
Cristian Peța 103 Posted May 19, 2020 Here is @David Heffernan's response: https://stackoverflow.com/questions/25536216/delphi-prevent-application-shutdown Share this post Link to post
Ian Branch 127 Posted May 19, 2020 OK. I'll have another try. I have had a look at the article above and am trying to integrate the ShutdownBlockReason.... functions. Where are the declared/defined? Share this post Link to post
Vandrovnik 214 Posted May 19, 2020 7 minutes ago, Ian Branch said: OK. I'll have another try. I have had a look at the article above and am trying to integrate the ShutdownBlockReason.... functions. Where are the declared/defined? Did you read, what Cristian Peța linked? function ShutdownBlockReasonCreate(hWnd: HWND; Reason: LPCWSTR): Bool; stdcall; external user32; function ShutdownBlockReasonDestroy(hWnd: HWND): Bool; stdcall; external user32; Share this post Link to post
Ian Branch 127 Posted May 19, 2020 Yes I found it informative but I get a "E2169 Field definition not allowed after methods or properties" error pointing at the external user32; 😞 type TForm3 = class(TForm) procedure FormShow(Sender: TObject); procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession; // detect Windows shutdown message procedure SaveData; // routine to save data to disk function ShutdownBlockReasonCreate(hWnd: HWND; Reason: LPCWSTR): Bool; stdcall; external user32; function ShutdownBlockReasonDestroy(hWnd: HWND): Bool; stdcall; external user32; The following is as I understand it att.. procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession); begin if DataToBeSaved then begin Msg.Result := lResult(True); // disallow Windows from shutting down SaveData; end; Msg.Result := lResult(False); // allow Windows shutdown end; // Set DataToBeSaved to False after saving data procedure TForm3.FormShow(Sender: TObject); begin DataToBeSaved := True; end; procedure TForm3.SaveData; begin ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...'); // Save data to disk files // ... beep; beep; beep; DataToBeSaved := False; // ShutdownBlockReasonDestroy(Application.MainForm.Handle); end; Share this post Link to post
Vandrovnik 214 Posted May 19, 2020 The declaration must be outside of the form... Share this post Link to post
Ian Branch 127 Posted May 19, 2020 (edited) Ahhhh. Ha! Testing.. Edited May 19, 2020 by Ian Branch Share this post Link to post
Ian Branch 127 Posted May 19, 2020 (edited) Hmmm. OK. I still have something amiss/NQR. On telling the PC to shutdown with the App running Windows immediately goes to the Shutting down screen. Reminder, I am on a Win 10 PC. This is my full code att.. unit Unit3; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs; type TForm3 = class(TForm) procedure FormShow(Sender: TObject); // private { Private declarations } procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession; // detect Windows shutdown message procedure SaveData; // routine to save data to disk public { Public declarations } end; function ShutdownBlockReasonCreate(Handle: hWnd; Reason: LPCWSTR): Bool; stdcall; external user32; function ShutdownBlockReasonDestroy(Handle: hWnd): Bool; stdcall; external user32; var Form3: TForm3; DataToBeSaved: Boolean; implementation {$R *.dfm} procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession); begin if DataToBeSaved then begin Msg.Result := lResult(True); // disallow Windows from shutting down SaveData; end; Msg.Result := lResult(False); // allow Windows shutdown end; // Set DataToBeSaved to False after saving data procedure TForm3.FormShow(Sender: TObject); begin DataToBeSaved := True; end; procedure TForm3.SaveData; begin ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...'); // Save data to disk files // ... beep; beep; beep; DataToBeSaved := False; // ShutdownBlockReasonDestroy(Application.MainForm.Handle); end; end. At least it builds now. 😉 Edited May 19, 2020 by Ian Branch Share this post Link to post
Anders Melander 1783 Posted May 19, 2020 24 minutes ago, Ian Branch said: procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession); begin if DataToBeSaved then begin Msg.Result := lResult(True); // disallow Windows from shutting down SaveData; end; Msg.Result := lResult(False); // allow Windows shutdown end; That's not how you do it. You need to let the message handler return with the status you've set, and then call SaveData. You also seem to be confused about the meaning of the WM_QUERYENDSESSION result value. False means you wish to postpone the shutdown. Something like this: const MSG_SAVEDATA = WM_USER; type TForm3 = class(TForm) ... protected procedure MsgSaveData(var Msg: TMessage); message MSG_SAVEDATA; ... end; procedure TForm3.WMQueryEndSession(var Msg: TWMQueryEndSession); begin Msg.Result := lResult(not DataToBeSaved); if (DataToBeSaved) then PostMessage(Handle, MSG_SAVEDATA, 0, 0); end; procedure TForm3.MsgSaveData(var Msg: TMessage); begin SaveData; end; Share this post Link to post
Remy Lebeau 1394 Posted May 19, 2020 (edited) The WM_QUERYENDSESSION documentation clearly states that data saving should be deferred to the WM_ENDSESSION message, so you don't really need the MSG_SAVEDATA approach: Quote When an application returns TRUE for this message, it receives the WM_ENDSESSION message, regardless of how the other applications respond to the WM_QUERYENDSESSION message. Each application should return TRUE or FALSE immediately upon receiving this message, and defer any cleanup operations until it receives the WM_ENDSESSION message. And the WM_ENDSESSION documentation says: Quote Applications that have unsaved data could save the data to a temporary location and restore it the next time the application starts. It is recommended that applications save their data and state frequently; for example, automatically save data between save operations initiated by the user to reduce the amount of data to be saved at shutdown. System shutdown can be aborted, in which case there would be no need to save any unsaved data, as long as your app has not been terminated yet. WM_QUERYENDSESSION is just asking for permission for the system to be shut down. The actual shutdown has not taken place yet. But, once an actual shutdown begins, unsaved data should be saved automatically. If you want to prompt the user whether unsaved data should be saved, do that in response to WM_CLOSE (or the TForm.OnCloseQuery event) instead: Quote An application can prompt the user for confirmation, prior to destroying a window, by processing the WM_CLOSE message and calling the DestroyWindow function only if the user confirms the choice. If you receive WM_ENDSESSION before receiving WM_CLOSE/OnCloseQuery then simply don't prompt the user. Edited May 19, 2020 by Remy Lebeau Share this post Link to post