如何在Fmx项目中获取Windows关机事件,类似于VCL项目中的WM_QUERYENDSESSION和WM_ENDSESSION?

14

在我的应用程序关闭之前,我需要截取Windows关机操作,并执行一些数据库查询。

我正在使用Delphi XE10,在Windows 10下的FMX项目中。

我尝试了下面的代码,但它并不起作用。

  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关闭时,我的应用程序将关闭而不保存任何数据


在Vista及更高版本中,请使用ShutdownBlockReasonCreate(),这在Windows Vista中的应用程序关闭更改中有讨论。Windows关闭不是执行数据库查询的好时机。如果DB引擎在您的应用程序之前关闭了呢?无论如何,请提供一个最小、完整和可验证的示例来证明您没有收到消息。还可以尝试使用TApplication.HookMainWindow()来接收它们。 - Remy Lebeau
1
消息处理程序针对VCL。请从这里开始。 - Sertac Akyuz
哦,这是一个FMX项目!现在你告诉我们。 - David Heffernan
对不起,你是正确的,但当你在我的代码中看到 "{$IFDEF MSWINDOWS}" 时,你有什么想法?一个Vcl项目??天哪! - Gianluca Colombo
1
关键是能够接收这些消息。您的主窗体是一个可见的顶级窗口。您不能使用FMX方法挂钩其winproc。但您可以获取HWND。然后,您可以使用标准的winapi挂钩。 - David Heffernan
显示剩余5条评论
3个回答

15

FormCloseQuery之所以可用,是因为它是框架公开的。当Windows关闭时,您的应用程序不保存任何数据,因为您的消息处理程序从未被调用过。消息处理仅适用于VCL应用程序,fmx应用程序具有不同的消息机制,如文档中所述。

这里简要解释了如何在fmx框架中接收来自操作系统的通知。但我不确定其中是否包括关机通知,并且是否可以设置返回值,因为文档提到消息对象是只读的。

在找出fmx消息机制的工作方式及其是否满足要求之前,您可以通过传统方法对窗口进行子类化。下面的示例使用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}

非常感谢。 这正是我需要的!它运行得很好。 - Gianluca Colombo
您还可以查看docwiki和另一个讨论 - Danilo Casa

1
在Windows中,特别是从Windows XP开始,这个领域已经有了很多变化。此外,Delphi窗口由应用程序管理的方式也已经改变,以更好地适应其他操作系统的变化,并且在FMX中又有所不同。
现在仅向顶层窗口发送WM_QUERYENDSESSION。如果您的应用程序是VCL应用程序并且设置了MainFormOnTaskbarTRUE,则您的应用程序主窗体是一个顶层窗口,应该接收消息。如果MainFormOnTaskbar设置为FALSE,或者如果您的窗体不是主窗体(尽管名称如此),则它不是顶层窗口,将不会接收消息。
如果您的应用程序使用FMX,则需要在FMX.Platform.Win WindowService中深入挖掘,以确定您的主窗体的父级关系是如何确定的。基于对[XE4] FMX源代码的检查,在这个领域似乎出现了一些丑陋的代码味道(相对于VCL)。
从Vista开始,没有可见顶级窗口的应用程序将不再收到WM_QUERYENDSESSION消息,这个领域中较细微的问题导致了一些麻烦。即使您的主窗体是一个顶级窗口,如果在Windows关闭时它不可见,那么这可能就是您没有接收到消息的原因。
如果问题是您的窗口不是应用程序中的顶级窗口,则此处应有足够的信息可以让您至少找出原因。
在VCL应用程序中,将主窗体设置为任务栏窗口应该可以解决问题。对于FMX应用程序是否有类似的解决方案我不知道。
如果您确实拥有有效的顶级窗口,而问题是您的顶级窗口(有时)不可见,那么您需要找到其他机制来连接到关闭过程,但应注意任何依赖于其他进程的行为都需要考虑到这些其他进程自身正在关闭并且可能无法使用的事实。
当然,所有这些都与Windows关机通知高度相关。如果您打算在FMX应用程序中支持其他平台,则需要以不同的方式处理关闭行为,假设FMX没有提供跨平台的关闭通知解决方案(否则您将使用该解决方案,不是吗?)。
(如果您只针对Windows,请问为什么要使用FMX?)

我忘了说这个项目不是一个VCL项目,而是一个多设备应用程序FMX,因此我无法在tApplication类上找到MainFormOnTaskbar属性。 - Gianluca Colombo
1
好的观点-直接传递到窗口过程。但是,考虑到我们现在知道这是一个多设备项目,在这个领域中Windows特定的消息传递问题似乎完全无关紧要!请使用FMX提供的跨平台关闭通知过程。如果没有,则......那么您就有了一个完全不同的问题。 - Deltics
是的,这是一款FMX应用程序,有点令人震惊。我不知道为什么会忽略这个信息。我不确定在FMX下消息指令是否已连接。 - David Heffernan
1
为什么我们必须要猜测呢?只需要提供一个MCVE就可以了。 - David Heffernan
当然,你不必猜测,那是我的错误,我为此道歉。我的失望之处在于你说“那些信息怎么可能被省略掉了”。没错!但是如果我来这里寻求帮助,可能是因为我对这个特定的问题不太重要。也许我是初学者?不是吗? - Gianluca Colombo
显示剩余2条评论

0
答案可能有些晚了,但是那些将来会遇到这个问题的人,那么... 该表单具有OnSaveState事件,它只在WM_ENDSESSION事件上调用。是的,默认情况下处理它。 WM_QUERYENDSESSION事件也是如此,但默认情况下传递1(启用会话终止)。它只能通过复制FMX.Platform.Win.pas模块并为自己进行修改来覆盖。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接