Search code examples
delphifiremonkey

How to get windows shutdown event in Fmx project as WM_QUERYENDSESSION and WM_ENDSESSION on a VCL project?


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


Solution

  • 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}