I need to intercept Windows shutdown, and execute some DB query, before that my application will close. I'm using Delphi XE10 under Windows 10 on a FMX project
What I tried is the code below, but it doesn't work
private
{ Private declarations }
{$IFDEF MSWINDOWS}
procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
procedure WMEndSession(var Msg : TWMQueryEndSession); message WM_ENDSESSION ;
{$ENDIF}
end;
procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
var
lista:TStringList;
begin
{$IFDEF MSWINDOWS}
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' event WMQueryEndSession');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
finally
lista.Free;
end;
{$ENDIF}
inherited;
end;
procedure TfMain.WMEndSession(var Msg: TWMQueryEndSession);
var
lista:TStringList;
begin
{$IFDEF MSWINDOWS}
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' WMEndSession');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
finally
lista.Free;
end;
{$ENDIF}
inherited;
end;
procedure TfMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var lista:TStringList;
begin
{$IFDEF MSWINDOWS}
CanClose:=false;
try
lista:=TStringList.Create;
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' FormCloseQuery');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
SincroClose();
lista.Add(FOrmatDateTime('DD/MM/YYYY HH:NN:SS',now)+' Done');
Lista.SaveToFile(froot+formatdatetime('YYMMDDHHNNSSZZZ',now)+'.log');
CanClose:=true;
finally
lista.Free;
end;
{$ENDIF}
end;
Only normal closing application, will work fine ,under FormCloseQuery event, but when Windows is shutting down, My application will close without saving any data
FormCloseQuery
works because it's exposed by the framework. Your application does not save any data when Windows is shutting down because your message handlers are never called. Message handling is only available to VCL applications, fmx applications have a different mechanism for messaging as documented.
Brief explanation here implies that it is possible to receive notifications from the OS in fmx framework. However I don't know if this includes shutdown notifications and if it is possible to set your return, as the documentation mentions the message object to be read only.
Until you find out how fmx messaging mechanism works and if it meets the requirements, you can subclass your form's window by conventional means. Below example uses SetWindowSubclass
.
...
protected
{$IFDEF MSWINDOWS}
procedure CreateHandle; override;
procedure DestroyHandle; override;
procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
{$ENDIF}
...
implementation
{$IFDEF MSWINDOWS}
uses
FMX.Platform.Win, Winapi.Commctrl;
function SubclassProc(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM;
uIdSubclass: UINT_PTR; RefData: DWORD_PTR): LRESULT; stdcall;
var
Self: TfMain;
Message: TMessage;
begin
Result := DefSubclassProc(Wnd, Msg, wParam, lParam);
case Msg of
WM_QUERYENDSESSION, WM_ENDSESSION:
begin
Self := TfMain(RefData);
Message.Msg := Msg;
Message.WParam := wParam;
Message.LParam := lParam;
Message.Result := Result;
Self.Dispatch(Message);
Result := Message.Result;
end;
end;
end;
procedure TfMain.CreateHandle;
var
Wnd: HWND;
begin
inherited;
Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
SetWindowSubclass(Wnd, SubclassProc, 1, DWORD_PTR(Self));
end;
procedure TfMain.DestroyHandle;
var
Wnd: HWND;
begin
Wnd := WindowHandleToPlatform(Self.Handle).Wnd;
RemoveWindowSubclass(Wnd, SubclassProc, 1);
inherited;
end;
procedure TfMain.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
// do not call inherited here, there's no inherited handling
end;
procedure TfMain.WMEndSession(var Msg: TWMEndSession);
begin
// do not call inherited here, there's no inherited handling
end;
var
ICC: TInitCommonControlsEx;
initialization
ICC.dwSize := SizeOf(ICC);
ICC.dwICC := ICC_STANDARD_CLASSES;
InitCommonControlsEx(ICC);
{$ENDIF}
There have been a number of changes in this area in Windows in (relatively) recent releases - i.e. going back to Windows XP. In addition, the way that Delphi windows are managed by the Application have been changed to better behave with respect to other OS changes and things are different again in FMX.
WM_QUERYENDSESSION is now only sent to top-level windows. If your application is a VCL application and has MainFormOnTaskbar
set TRUE then your application main form is a top-level window and should receive the message. If MainFormOnTaskbar
is set FALSE, or if your form is not the main form (despite the name) then it is not a top-level window and will not receive the message.
If your application uses FMX then you will need to dig around inside the FMX.Platform.Win
WindowService to determine exactly how the parenting of your main form is determined. Based on an inspection of [XE4] FMX source, things appear to have gone backwards in this area (relative to the VCL) and there are some ugly code smells here.
The problems that the finer details in this area cause are that from Vista onward, WM_QUERYENDSESSION is no longer sent to applications without any visible top-level windows. Even if your main form is a top-level window, if it is not visible at the point that Windows is shutting down then this could be why you are not receiving the message.
If the problem is that your window is not the top-level window in your application then there should be enough information here to enable you to at least figure out why.
In a VCL application, making your Main Form the taskbar window should address the problem. Whether there is a similar means to address the issue in an FMX application I don't know.
If you do have a valid top-level window and the problem is that your top-level window is (sometimes) not visible then you will need to find some other mechanism to hook into the shutdown process but should be aware that any behaviour that relies on other processes needs to take account of the fact that those other processes themselves amy be shutting down and may not be available.
Of course, all this is highly specific to Windows shutdown notifications. If you are intending to support other platforms with your FMX application then you will need to deal with shutdown behaviour differently there, assuming that FMX does not provide a cross-platform shutdown notification solution (otherwise you would be using that, no?).
(And if you are in fact only targeting Windows, why on Earth are you using FMX ?)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With