【问题标题】:Delphi - how do I find out which modal dialog has focus and bring it to the front?Delphi - 我如何找出哪个模态对话框具有焦点并将其置于前面?
【发布时间】:2012-03-21 19:59:20
【问题描述】:

我有一个 Delphi 2006 应用程序,它可以弹出一个模式对话框以响应错误情况。它似乎进入了一种状态,其中一个模式对话框处于打开状态,位于主窗体的前面,但两个窗体都没有响应消息。单击任一都会给出“bonk”。应用程序运行良好,UI 正在更新主窗体,但您无能为力。我猜主窗体下很可能还有另一个模态对话框。我不知道是我的还是 Windows 的。

其他要点:

  • 应用程序响应键盘快捷键 OK。其中一个快捷方式可以优雅地关闭应用程序,这很有效。从那以后,我一直无法重现这种情况。
  • 应用程序有一个托盘图标。这响应鼠标右键单击。如果我从这里最小化应用程序,主窗体会最小化并显示模态对话框,但仍然没有焦点。如果我恢复主窗体,一切照旧,两个窗口都没有焦点。 Alt-tab 也有类似的结果。
  • 平台是 Windows 7
  • 我在创建任何表单之前调用 DisableProcessWindowsGhosting
  • 我用

    打开模态对话框
    ModalDialog.PopupParent := MainForm ;
    ModalDialog.ShowModal ;
    
  • 如果其他模式对话框打开,我会推迟这些错误对话框:

    if (Application.ModalLevel = 0) then
        {open modal dialog}
    

我的问题分为两部分:

有没有办法以编程方式找出哪个窗口有焦点?然后我可以针对这种情况采取一些措施,或者作为最后的手段,我可​​以让他们提供一个快捷键将其置于最前面或采取一些规避措施(取决于对话框),例如将 ModalResult 设置为 mrCancel。

这种情况怎么会出现?通常,当我在主窗体后面得到一个模态对话框时(我可以通过打开模态对话框,从托盘图标最小化应用程序,然后再次恢复应用程序来做到这一点 - 应用程序主窗体恢复在对话框前面,使用对话框仍然保持焦点),我可以通过单击托盘图标再次将其置于前面,或者使用Esc 键将其关闭,但在这种情况下它不起作用。

**更新**

Misha 的修复与 TSaveDialog 等非 Delphi 对话框不同。通过在调用Execute 之前添加Application.ModalPopupMode := pmAuto ;,我也能够让它们正常工作。

“让它工作”是指保存对话框在以下序列之后:

  • 打开保存对话框
  • 从托盘图标最小化应用程序
  • 从托盘图标恢复应用

而它位于没有ModalPopupMode := pmAuto 的主窗体后面。

所以我希望这些更改将有助于解决(尚未重现的)问题。

【问题讨论】:

  • 找出哪个窗口有焦点并采取补救措施无济于事。你需要从源头上解决问题。这意味着了解窗口所有权(即 PopupParent)。
  • 谢谢@David。找出哪个窗口有焦点肯定会有所帮助,因为它可能会告诉我如何重现问题,而我目前无法做到这一点。
  • 一旦你有了它,你应该能够使用像 Spy++ 这样的东西来理解所有权关系。我会尝试从外部调试它,至少在你有重现之前。
  • 您通常可以通过将 {$ifdef DEBUG}Sleep(5000);{endif} 添加到您的调试版本来使其重现 - 需要进行一些实验才能找到最“破坏性”的地方”。这个 z 顺序地狱的神奇触发因素是窗口认为您的应用程序没有响应。

标签: delphi delphi-2006


【解决方案1】:

如果有焦点的窗体响应消息(Form1)的时间过长,导致 Windows 认为 Form1 没有响应,然后 Form1 显示一个模态窗体(Form2),在 Form2 显示并且应用程序再次处理消息之后,Form1 将被带到最前面,从而可能“覆盖”Form2。

把它放在 Application.OnIdle 事件中就可以了:

  if Assigned(Screen.ActiveForm) then
  begin
    if (fsModal in Screen.ActiveForm.FormState) and
       (Application.DialogHandle <= 0)) then 
    begin
      Screen.ActiveForm.BringToFront;
    end;
  end;

【讨论】:

  • 谢谢@Misha。这似乎确实可以解决问题,除非打开非 VCL 对话框,例如加载文件对话框,所以我们还没有完成。我使用了一个计时器,因为如果您打开了上下文菜单,则不会触发 OnIdle 事件(例如)
【解决方案2】:

可以使用GetLastActivePopup查询最后一个活动的弹窗(VCL与否):

function GetTopWindow: HWND;
begin
  Result := GetLastActivePopup(Application.Handle);
  if (Result = 0) or (Result = Application.Handle) or
      not IsWindowVisible(Result) then
    Result := Screen.ActiveCustomForm.Handle;
end;

这在某种程度上是从TApplication.BringToFront复制而来的。

可以通过SetForegroundWindow将该窗口置于最前面:

SetForegroundWindow(GetTopWindow);

请注意,Application.BringToFront 可能完全可以解决问题,但我曾经经历过它无法正常工作,但此后我无法重现这种情况。

【讨论】:

  • 您需要检查Assigned(Screen.ActiveCustomForm)。我正在使用Application.ShowMainForm := False 在托盘中运行我的应用程序,如果我在没有显示任何表单时调用GetTopWindow,它会抛出一个 AV。
【解决方案3】:

GetForegroundWindow() 是您要查找的函数,如果您知道标题或有模态窗口的句柄,就很简单了。

HWND GetForegroundWindow();

检索前景窗口的句柄(与 用户当前正在工作)。系统分配一个稍高的 创建前台窗口的线程的优先级高于它 到其他线程。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms633505%28v=vs.85%29.aspx

【讨论】:

    【解决方案4】:

    我使用了 Misha 的解决方案并进一步工作(使用 NGLN 的代码),以解决 rossmcm 遇到的问题(处理非 VCL 对话框)。

    下面的代码在定时器中运行:

    type
      TCustomFormAccess = class(TCustomForm);
    
    
    if Assigned(Screen.ActiveCustomForm) then
    begin
      if ((fsModal in Screen.ActiveCustomForm.FormState) and
          (Application.DialogHandle <= 0)) then
      begin
        TopWindow := GetLastActivePopup(Application.Handle);
        TopWindowForm := nil;
        for i := 0 to Screen.CustomFormCount - 1 do
        begin
          CustomFormAccess := TCustomFormAccess(Screen.CustomForms[i]);
          if CustomFormAccess.WindowHandle = TopWindow then TopWindowForm := CustomFormAccess;
        end;
        if Assigned(TopWindowForm) and (Screen.ActiveCustomForm.Handle <> TopWindow) then
        begin
          Screen.ActiveCustomForm.BringToFront;
        end;
      end;
    end;
    

    【讨论】:

      猜你喜欢
      • 2011-03-28
      • 1970-01-01
      • 2014-04-04
      • 2010-10-17
      • 2017-11-29
      • 2012-04-12
      • 1970-01-01
      相关资源
      最近更新 更多