如何区分UI关闭和使用Delphi强制关闭

Kev*_*son 1 delphi shutdown application-shutdown delphi-xe4

如果用户尝试关闭应用程序,我想显示一个“关闭查询”确认对话框。如果系统关闭或任务管理器中的任务或进程正在结束,我只想清理并关闭。

我尝试了这段代码:

private
procedure WMEndSession(var Message: TWMEndSession); message WM_ENDSESSION;
Run Code Online (Sandbox Code Playgroud)

...

procedure TxxxForm.WMEndSession(var Message: TWMEndSession);
begin
  gShuttingDown := Message.EndSession;
end;
Run Code Online (Sandbox Code Playgroud)

...

procedure TxxxForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  Application.ProcessMessages;
  if gShuttingDown then
    CanClose := True
  else
    CanClose := MessageDlg('Are you sure you want to stop the close?', mtConfirmation,
       [mbYes, mbNO], 0) = mrYes;
end;
Run Code Online (Sandbox Code Playgroud)

弹出确认显示我是从任务管理器中关闭程序还是终止进程。

Dis*_*ned 6

WM_QUERYENDSESSION是正确的消息。这是在发送之前WM_ENDSESSION以及根据以下VCL代码发送的,您可以看到WMQueryEndSession立即调用CloseQuery

procedure TCustomForm.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  Message.Result := Integer(CloseQuery);
end;
Run Code Online (Sandbox Code Playgroud)

可以将上述实现视为VCL代码中的一个小错误,因为正如WM_QUERYENDSESSION文档所述(强调我的意思):

每个应用程序在收到此消息后应立即返回TRUE或FALSE,并推迟任何清理操作,直到收到WM_ENDSESSION消息。
应用程序可以显示一个用户界面,提示用户在关机时提供信息,但是不建议这样做。之后5秒,系统显示有关防止关机和应用程序的信息,用户可以终止它们。例如,Windows XP显示一个对话框,而Windows Vista显示全屏,其中包含有关阻止关机的应用程序的其他信息。如果您的应用程序必须阻止或推迟系统关闭,请使用该ShutdownBlockReasonCreate功能。有关更多信息,请参见Windows Vista的关机更改。

如果用户未能在5秒钟内响应弹出的对话框,则他们将不得不终止应用程序或取消关机。不幸的是,由于CloseQuery通常用于提示用户,所以这样做不是一个好主意。
注意:这是完全可接受的Vista之前的版本,并且上面的代码可能更多是遗留问题。但是,最好CloseQuery至少进行调整以指示关闭请求的来源,这样可以轻松处理查询结束会话的特定情况。

因此,鉴于我不想弹出任何提示以响应WM_QUERYENDSESSION,因此我实际上执行以下操作:

procedure TxxxForm.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  Message.Result := 1;
  { Do not call inherited bc VCL may prevent/delay shutdown via 
    calls to: CloseQuery and CallTerminateProcs (in older 
    versions of Delphi).
    As per Vista requirements, this should not be done. Use
    ShutdownBlockReasonCreate and ShutdownBlockReasonDestroy to
    control when shutdown is allowed. }
end;
Run Code Online (Sandbox Code Playgroud)

转到问题的第二部分。
上述无法End Task通过任务管理器工作的原因是,任务管理器未使用与关机相同的机制。它只是向WM_CLOSE您的应用程序发送一条消息。

因此,应用程序的行为与“正常”关闭时的行为完全相同,这是完全可以接受的。实际上,您可以使用记事本尝试一下:

  • 打开记事本
  • 输入一些文字
  • 转到任务管理器,然后单击End Task记事本。
  • 您将观察到记事本将提示您进行确认,并且任务管理器将显示一个对话框,说明记事本正在等待用户的响应。(至少在Win7中。)

但是,如果确实希望/需要在从任务管理器关闭时阻止提示,则可以这样做。但是该代码将不得不处理许多特殊情况,并且在所有版本的Windows上可能行为方式都不完全相同。(因此,您必须问自己是否真的值得付出努力。)

您必须满足至少3种情况:

  1. 从应用程序中的自定义菜单或按钮关闭。
  2. 从标准Windows命令关闭:X在右上角,系统菜单,或双击左上角的Alt+F4组合键。
  3. 最后从任务管理器关闭。

让所有3个人按照自己的方式进行操作会有些棘手。

  • 选项3发送WM_CLOSE。因此,您必须在没有提示的情况下关闭“默认”行为。并使用Flag启用提示。这样,选项3可以“静默”关闭。
  • 选项2首先发送WM_SYSCOMMANDSC_CLOSE然后发送WM_CLOSE。因此,您可以设置Flag对第一个消息的响应。因此,您的用户将得到提示。
  • 选项1最有可能通过调用来实现Close。但是要确保得到提示,您必须先设置提示Flag
  • 如果您的用户选择不关闭应用程序,请不要忘记清除Flag的信息。

最后的想法

我个人觉得“仔细确认”很烦人。当然,我有经常保存的习惯,所以不要冒险丢失数据。

基本上,提示关闭的唯一正当理由是防止未保存的数据意外丢失。如果遵循以下规则,则可以创建更愉快的用户体验:

当应用打开时,请始终将其恢复到关闭时完全相同的状态

即,如果文档中有未保存的数据,请将其存储在临时文件中。当应用程序重新打开时,文档将自动置于与关闭时相同的“编辑”状态。

是的。也许这是一个过分的简化,但是这个想法在移动设备上效果很好。