Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 852
Gianluca Colombo Avatar asked Oct 01 '16 13:10

Gianluca Colombo


2 Answers

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}
like image 157
Sertac Akyuz Avatar answered Nov 16 '22 02:11

Sertac Akyuz


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 ?)

like image 44
Deltics Avatar answered Nov 16 '22 02:11

Deltics