如何在 Fmx 项目中获取 windows 关闭事件作为 VCL 项目中的 WM_QUERYENDSESSION 和 WM_ENDSESSION?

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

我需要拦截 Windows 关闭,并执行一些数据库查询,然后我的应用程序将关闭。 我在 FMX 项目

Windows 10 下使用 Delphi XE10

我试过下面的代码,但是不行

  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;

只有正常关闭应用程序,在 FormCloseQuery 事件下才能正常工作,但是当 Windows 关闭时,我的应用程序将关闭而不保存任何数据

在(相对)最近的版本中 Windows 在这方面有一些变化 - 即回到 Windows XP。此外,Delphi windows 由应用程序管理的方式已更改为相对于其他 OS 更改表现得更好,并且 FMX 中的情况又有所不同。

WM_QUERYENDSESSION 现在只发送到顶级 windows。如果您的应用程序是 VCL 应用程序并且 MainFormOnTaskbar 设置为 TRUE 那么您的应用程序主窗体是顶级 window 并且应该接收消息。如果 MainFormOnTaskbar 设置为 FALSE,或者如果您的表单不是主表单(尽管名称如此),那么它不是顶级 window 并且不会收到消息。

如果您的应用程序使用 FMX,那么您将需要深入研究 FMX.Platform.Win WindowService 以确定主窗体的父子关系是如何确定的。根据对 [XE4] FMX 源的检查,这方面的事情似乎已经倒退了(相对于 VCL),这里有一些难看的代码味道。

这方面的细节导致的问题是,从 Vista 开始,WM_QUERYENDSESSION 不再发送到没有任何可见顶层的应用程序 windows。即使您的主窗体是顶级 window,如果它在 Windows 关闭时不可见,那么这可能就是您没有收到消息的原因。

如果问题是您的 window 不是您应用程序中的顶级 window 那么这里应该有足够的信息让您至少找出原因。

在 VCL 应用程序中,将主窗体设为任务栏 window 应该可以解决该问题。我不知道是否有类似的方法可以解决 FMX 应用程序中的问题。

如果您确实有一个有效的顶级 window 而问题是您的顶级 window 是(有时)不可见的,那么您将需要找到一些其他机制来挂钩进入关闭过程,但应该意识到依赖于其他进程的任何行为都需要考虑到这些其他进程本身可能正在关闭并且可能不可用的事实。

当然,所有这些都高度特定于 Windows 关机通知。如果您打算使用 FMX 应用程序支持其他平台,那么您将需要以不同的方式处理关闭行为,假设 FMX 不提供跨平台关闭通知解决方案(否则您将使用它,不是吗?)。

(如果您实际上只针对 Windows,您到底为什么要使用 FMX?)

FormCloseQuery 之所以有效,是因为它由框架公开。当 Windows 关闭时,您的应用程序不会保存任何数据,因为您的消息处理程序从未被调用。消息处理仅适用于 VCL 应用程序,fmx 应用程序具有与 documented.

不同的消息传递机制

简要说明 here 表示可以在 fmx 框架中接收来自 OS 的通知。但是我不知道这是否包括关机通知以及是否可以设置您的 return,因为文档提到消息对象是只读的。

直到您了解 fmx 消息传递机制的工作原理并且如果它满足要求,您可以通过常规方式对表单的 window 进行子类化。下面的示例使用 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}