delphi 如何在一个VCL项目上获取Fmx项目中的Windows关闭事件为WM_QUERYENDSESSION和WM_ENDSESSION?

lrl1mhuk  于 2023-06-05  发布在  Windows
关注(0)|答案(3)|浏览(284)

我需要拦截Windows关机,并执行一些DB查询,在此之前,我的应用程序将关闭。我在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关闭时,我的应用程序将关闭而不保存任何数据

mwngjboj

mwngjboj1#

FormCloseQuery可以工作,因为它是由框架公开的。当Windows关闭时,应用程序不保存任何数据,因为永远不会调用消息处理程序。消息处理仅适用于VCL应用程序,fmx应用程序的消息传递机制与documented不同。
这里的简要说明意味着可以在fmx框架中接收来自OS的通知。然而,我不知道这是否包括关机通知,以及是否可以设置您的返回,因为文档提到消息对象是只读的。
在您发现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}
kdfy810k

kdfy810k2#

在最近的版本中,Windows在这方面有一些变化-即。回到Windows XP。此外, Delphi 窗口由应用程序管理的方式已经改变,以便更好地处理其他操作系统的变化,FMX中的情况再次不同。

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

如果您的应用程序使用FMX,那么您将需要在FMX.Platform.Win WindowService中查找,以确定主窗体的父级是如何确定的。基于对[XE 4] FMX源代码的检查,在这个领域(相对于VCL),事情似乎已经倒退,这里有一些丑陋的代码气味。
这方面的细节导致的问题是,从Vista开始,WM_QUERYENDSESSION不再发送到没有任何可见顶层窗口的应用程序。即使您的主窗体是顶级窗口,如果在Windows关闭时它不可见,则这可能是您没有收到消息的原因。
如果问题是您的窗口不是应用程序中的顶级窗口,那么这里应该有足够的信息,至少可以让您找出原因。
在VCL应用程序中,将主窗体设置为任务栏窗口应该可以解决这个问题。我不知道在FMX应用程序中是否有类似的方法来解决这个问题。
如果你有一个有效的顶层窗口,问题是你的顶层窗口(有时)不可见,那么你需要找到一些其他的机制来挂钩到关闭进程,但应该注意的是,任何依赖于其他进程的行为都需要考虑到这些其他进程本身可能正在关闭,可能不可用。
当然,所有这些都是高度特定于Windows关机通知的。如果你打算用你的FMX应用程序支持其他平台,那么你需要在那里以不同的方式处理关机行为,假设FMX不提供跨平台关机通知解决方案(否则你会使用它,不是吗?).
(And如果你实际上只针对Windows,那么你到底为什么要使用FMX?)

lstz6jyr

lstz6jyr3#

答案很可能是迟来的,但那些将来会面对这个问题的人,那么……表单有一个OnSaveState事件,它只在WM_ENDSESSION事件上调用。是的,默认情况下会处理。WM_QUERYENDSESSION事件也是一样的,但是默认情况下传递1(启用会话终止)。只能通过复制FMX.Platform.Win.pas模块并自行修改来覆盖它

相关问题