对话框 MessageBox 有时会隐藏在主窗体后面

30

我们的一些非技术用户在应用程序中遇到问题,有时会出现一个对话框MessageBox被显示在主窗体后面,直到他们关闭看不到的消息框为止,应用程序不接受任何输入。

该应用程序是用C#编写的,消息框是标准的,例如代码可以简单地是MessageBox.Show(message, caption),消息框可以由主UI线程创建(即不是某个后台线程)。应用程序可以不必全屏运行,但我们的90%用户都以全屏方式运行。

大多数情况下(也许超过99%),消息框会正确显示,我从未成功地看到它出错了,但我见过有错误发生的机器。

我注意到的一件事是,如果您有一个显示对话框的应用程序,那么当您查看任务管理器时,您通常只会看到应用程序列表中的一个条目。每当消息框被隐藏时,您将会看到两个条目,一个是主应用程序,另一个是这个消息框。

一旦您知道发生了什么,很容易解决这个问题,但我们一些非技术用户会感到困惑,最终关闭他们的计算机。(使用远程桌面的用户更加困惑,因为那样并不能解决问题)。

我认为这与操作系统无关,因为我在Vista上看到它发生过,并被告知它也会在Windows 2003服务器的终端会话中发生。

有人知道为什么会发生这种情况吗?更重要的是,是否有任何方法可以避免它?


我在WPF中也遇到了同样的问题。某些因素会触发对话框和主窗口之间的关系断裂,即使未指定也是如此。将一个Window作为第一个参数提供可以解决这个问题。为了重现它,我在VS调试器中运行了应用程序。调试器处于活动状态似乎经常会破坏这种关系。你可以通过对话框在任务栏上获得自己的条目来确定出现了问题。 - denver
5个回答

29

MessageBox.Show() 方法的一些重载版本将 IWin32Window 参数作为第一个参数。如果您将您的窗体作为第一个参数传入,它应该可以防止出现这种情况。


+1 表示指定父窗口。OP 的问题很可能是由于消息框没有指定父窗口(即桌面窗口)而导致的,有时该窗口会出现在后台。 - casablanca
在运行时和客户机上,有没有确定消息框父级的方法? - sgmoore

1
确认问题。我们要做的是以下内容:
  1. 运行新任务并显示消息框。
  2. 在主UI线程中,当任务仍在运行时 - 在执行DoEvents的循环中等待。类似于这样:

更新于2015-12-17。 昨天重现了问题。在我的情况下,要重现此问题,请将应用程序最小化,“等待”弹出窗口出现(在我们的情况下,它会在一段空闲时间后发生),然后在任务栏上单击主应用程序图标。这将“隐藏”弹出窗口,因此无法将其带到屏幕上。下面的代码经过测试,可以解决问题。但我仍然不明白发生了什么/为什么会发生。

    private static DialogResult ShowMessageBox(
        string message, 
        string caption, 
        MessageBoxButtons buttons, 
        MessageBoxIcon icon)
    {

        var showMessageBoxTask = new Task<DialogResult>(() =>
        {
            var form = new Form() {TopMost = true};

            var result = MessageBox.Show(
                form,
                PrepareMessage(message),
                caption,
                buttons,
                icon);

            form.Dispose();

            return result;
        });

        showMessageBoxTask.Start();

        while (!showMessageBoxTask.IsCompleted && !showMessageBoxTask.IsFaulted)
        {
            Application.DoEvents();
        }

        return showMessageBoxTask.Result;
    }

1

同一个消息框(对于相同的消息)是否总是出现在同一个窗口中?

理想情况下,您应该尝试找到某种方法来随意或至少自动地重现问题。这将使您的调试更容易,并且您可以确信您未来的更改已经修复了错误,而不必等待几周以获取用户的反馈。

如果它总是相同的消息并且在相同的窗口中,由相同的操作引起,并且如果MessageBox调用从用户角度来看相对容易触发,并且如果您的UI相对标准,则可以使用AutoIT脚本自动化UI,并在循环中运行直到出现问题。

此外,您可以创建一个“调试”版本的应用程序,并将其提供给一些用户(最好是那些似乎最经常遇到问题的用户),每次在调用MessageBox之前将StackFrame对象的内容写入日志文件或类似的东西(您可以创建一个包装器来简化这个过程)。

然后,当你的用户遇到问题时,你可以查看日志文件,看看它来自哪里(源代码文件、行、调用堆栈等)。你还可以将其与其他用户的日志进行比较,看看每次消息框是否来自相同位置或者是否有所不同。

如果你的应用程序有很多程序集,可能会有更简单的解决方案(特别是涉及一些 .Net 调试器的情况),你可以在出现问题时将其附加到应用程序上,以便查看调用堆栈等信息,但我只在本机应用程序中使用 OllyDbg 进行过此操作,而没有在 .Net 中尝试过。其他人可能能够进一步扩展这个想法...


它并不总是相同的信息,消息框可以从程序的不同部分创建。然而,最后一次看到这个消息时,我知道了这个消息是从哪里调用的。尝试自动化它的问题在于我认为外部因素会产生影响。当它首次报告时,我认为可能是由用户将应用程序最小化以便在另一个程序上工作,而消息框在应用程序被最小化时创建。但是,如果我这样做,每当我切换回我的应用程序时,消息框也会显示在前面。 - sgmoore
如果你已经排除了线程假设,我认为你可能需要尽可能地排除许多常见嫌疑人。你是否尝试在一个原始的机器(或虚拟机)上运行你的应用程序?虽然在你的环境中可能不太现实,但是尽管很少,但不是不可能某个实用程序或其他软件会尝试在某个时刻窃取焦点或导致窗口发生一些奇怪的事情。此外,看起来你从未亲眼目睹过这个错误。 - Kharlos Dominguez
此外,如果您的用户是公司内部员工,并且愿意接受,您可以记录他们的屏幕,这样下次出现问题时,您就可以观看发生了什么。话虽如此,事实上观察到他们可能会引入一些奇怪的安慰剂效应(例如,如果用户在您的应用程序处理某些内容时访问网站,而现在知道您可以观看录像,他们就不再这样做了)。 - Kharlos Dominguez

0
你说“消息框可以由主UI线程创建”,我想这意味着它们并不总是由主UI线程创建。你的问题听起来像是MessageBox.Show有时会从另一个线程调用。

不是的,我的意思是尽管有些情况是由后台线程创建的消息框触发的,但也会在明确由主 UI 线程创建的消息框上出现。 - sgmoore
你的表单里有没有做什么奇怪的事情?比如 PInvoking SetWindowPos 或者其他类似的操作? - MusiGenesis
不,唯一的常规操作是在主窗体实际显示之前将ShowInTaskbar设置为false,同时显示一个闪屏并初始化程序。在Mainform Load事件结束时,将ShowInTaskbar设置为true。 - sgmoore
当出现这个问题时,在任务管理器中看到两个条目意味着消息框没有从您的 UI 线程中显示(或者发生了一些其他奇怪的事情)。 - MusiGenesis
我确信它是从我的 UI 线程调用的,但我同意您的看法,一些奇怪的事情正在发生。我还怀疑消息框最初链接正确,但后来链接丢失了。我之所以这么说是因为我认为(但不是100%确定)问题发生在他们从另一个应用程序切回我们的应用程序时。 - sgmoore

0
在父窗体中,在 MessageBox.Show() 之前添加以下内容:
this.TopMost = false;

1
请说明您的答案。 - Morse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接