【问题标题】:How can I distinguish between UI Close and forced shutdown with Delphi如何区分 UI 关闭和使用 Delphi 强制关闭
【发布时间】:2015-03-05 19:03:54
【问题描述】:

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

我试过这段代码:

private
procedure WMEndSession(var Message: TWMEndSession); message WM_ENDSESSION;

...

procedure TxxxForm.WMEndSession(var Message: TWMEndSession);
begin
  gShuttingDown := Message.EndSession;
end;

...

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;

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

【问题讨论】:

  • 如果我没记错的话,你想要 WM_QUERY_ENDSESSION。
  • 我尝试添加:`procedure TxxxForm.WMQueryEndSession(var Message: TWMQueryEndSession);开始 gShuttingDown := True; end;` 但是当我从任务管理器“结束任务”时它仍然发出关闭查询对话框。
  • @Günther the Beautiful 是的,这就是答案。而不是试图检测强制关闭,我应该检测用户关闭。我将我的 gShuttingDown 变量设置为 True,并在我得到一个命令类型为 SC_CLOSE 的 WM_SYSCOMMAND 时设置 if False(用户关闭程序的所有方式)。完美运行。
  • ProcessMessages 调用的意义何在?

标签: delphi shutdown delphi-xe4 application-shutdown


【解决方案1】:

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

procedure TCustomForm.WMQueryEndSession(var Message: TWMQueryEndSession);
begin
  Message.Result := Integer(CloseQuery);
end;

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

每个应用程序应在收到此消息后立即返回 TRUE 或 FALSE,并将任何清理操作推迟到收到 WM_ENDSESSION 消息。
应用程序可以在关机时显示一个用户界面,提示用户输入信息,但不建议这样做五秒后,系统会显示有关阻止关闭的应用程序的信息,并允许用户终止它们。例如,Windows XP 显示一个对话框,而 Windows Vista 显示一个全屏显示有关阻止关闭的应用程序的附加信息。如果您的应用程序必须阻止或推迟系统关闭,请使用ShutdownBlockReasonCreate 函数。有关详细信息,请参阅 Windows Vista 的关机更改。

如果用户未能在 5 秒内对弹出的对话框做出响应,他们将不得不终止应用程序或取消关机。不幸的是,由于CloseQuery 通常用于提示用户,所以这样做不是一个好主意。
注意:这在 Vista 之前是完全可以接受的,上面的代码可能更多的是遗留问题。不过,如果CloseQuery至少调整一下,说明关闭请求的来源就好了,便于处理Query End Session的具体情况。

因此,鉴于我不希望弹出任何提示以响应 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;

继续您问题的第二部分。
通过任务管理器对 End Task 不起作用的原因是任务管理器不使用与关闭相同的机制。它只是向您的应用程序发送WM_CLOSE 消息。

因此,您的应用程序的行为方式与“正常”关闭时完全相同是完全可以接受的。其实你可以用记事本试试看:

  • 打开记事本
  • 输入一些文字
  • 进入任务管理器,点击记事本的结束任务
  • 您会看到记事本会提示确认,并且任务管理器会显示一个对话框,说明记事本正在等待用户的响应。 (至少在 Win7 中。)

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

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

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

让这三个人都按照你想要的方式行事有点棘手。

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

最后的想法

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

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

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

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

是的。也许这过于简单化了,但这个想法在移动设备上非常有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-19
    • 2011-02-28
    • 2012-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多