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

时间:2016-10-01 13:45:50

标签: delphi firemonkey

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

2 个答案:

答案 0 :(得分:15)

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}

答案 1 :(得分:1)

在(相对)最新版本中,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不提供跨平台关闭通知解决方案(否则您将使用它,不是吗?)。 / p>

(如果你实际上只是针对Windows,为什么你在地球上使用FMX?)