【问题标题】:How to get windows shutdown event in Fmx project as WM_QUERYENDSESSION and WM_ENDSESSION on a VCL project?如何在 VCL 项目中将 Fmx 项目中的 Windows 关闭事件作为 WM_QUERYENDSESSION 和 WM_ENDSESSION 获取?
【发布时间】:2017-02-09 23:13:51
【问题描述】:

我需要拦截 Windows 关闭,并执行一些数据库查询,然后我的应用程序将关闭。 我在 Windows 10 下在 FMX 项目中使用 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 关闭时,我的应用程序将关闭而不保存任何数据

【问题讨论】:

  • @David Hefferman N.B.这个问题不是 [Delphi prevent application shutdown][1] 我不明白的是为什么我的应用程序无法拦截 WM_QUERYENDSESSION 或 WM_ENDSESSION
  • 从已知可以工作的代码开始,你有这些代码,然后从那里开始工作。此外,你的生命周期管理被打破了。尝试前获取。
  • 在 Vista 及更高版本上,使用ShutdownBlockReasonCreate(),这在Application Shutdown Changes in Windows Vista 中进行了讨论。 Windows 关闭不是执行数据库查询的好时机。如果数据库引擎在您的应用程序之前关闭怎么办?在任何情况下,请提供Minimal, Complete, and Verifiable example 证明您没有收到消息。也可以尝试使用TApplication.HookMainWindow() 来接收它们。
  • 消息处理程序用于 VCL。开始here
  • 关键是能够接收这些消息。您的主窗体是一个可见的顶级窗口。您不能使用 FMX 方法挂钩它的 winproc。不过,您可以获得 HWND。然后你就可以使用标准的 winapi 挂钩了。

标签: delphi firemonkey


【解决方案1】:

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

简要说明here 表示可以在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}

【讨论】:

  • 非常感谢。这就是我需要的!它工作正常。
  • 你也可以看看docwiki和另一个discussion
【解决方案2】:

在(相对)最近的版本中,Windows 在这方面发生了许多变化 - 即回到 Windows XP。此外,Delphi 窗口由应用程序管理的方式已更改,以更好地应对其他操作系统更改,FMX 中的情况再次不同。

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

如果您的应用程序使用 FMX,那么您将需要在 FMX.Platform.Win WindowService 内部进行挖掘,以确定您的主窗体的父级是如何确定的。根据对 [XE4] FMX 源的检查,这方面的事情似乎已经倒退(相对于 VCL),并且这里有一些难看的代码气味。

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

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

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

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

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

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

【讨论】:

  • OnMessage 无济于事,因为此消息是同步传递的
  • 我忘了说这个项目不是 VCL 项目而是一个多设备应用程序 FMX 所以我在 tApplication 类上找不到 MainFormOnTaskbar 属性
  • 好点 - 直接交付给 windowproc。但是,鉴于我们现在知道这是一个多设备项目,Windows 特定于该领域的消息传递问题的整个问题似乎完全无关紧要!使用 FMX 提供的跨平台关机通知流程。如果没有,那么......好吧......你有一个完全不同的问题。
  • 是的,有点重磅炸弹,这是一个 FMX 应用程序。如何省略这些信息超出了我的范围。我完全不确定消息指令是否在 FMX 下连接。
  • 我们为什么要猜测?它只需要一个 MCVE。
猜你喜欢
  • 2022-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-03
  • 1970-01-01
  • 2017-07-17
相关资源
最近更新 更多