将MessageBox模态设置为单个窗体

3
我希望显示一个 MessageBox,它对单个窗体是模态的,并允许线程上的其他顶层窗口保持活动状态。WinForms会禁用当前线程上的所有窗口,无论是显示自定义模态窗体还是使用 MessageBox 类。
我已经尝试了多种不同方法来解决这个问题,包括重新启用任何其他顶层窗口(使用 Win32 的 EnableWindow 函数)和调用本机的 MessageBox 函数。这些方法都有效,但会使键盘快捷键失效,包括用于在控件之间移动的 Tab 键、用于取消打开菜单的 Escape 键以及最重要的菜单快捷键。
我可以通过在显示 MessageBox 时安装键盘钩子,然后直接在活动窗体上调用 ProcessCmdKey 来使菜单快捷键起作用,但其他键盘快捷键仍不能正常工作。我猜我可以继续添加更多的 hack 以使这些其他情况工作,但我想知道是否有更好的方法。 注意: 这不是关于 非自动阻止,而是关于不禁用当前线程上的所有窗口。解决方案可能类似,但并非完全相同。

1
@CodeCaster:ShowDialog也会禁用其他顶级对话框,并且存在相同的键盘问题。我也非常希望使用适当的MessageBox而不是实现自己的,看起来不太正确。 - John Hall
请注意在单独的线程中显示消息框的答案! - TaW
2个回答

2
你在这里面对抗的基本问题是,MessageBox.Show()会启动自己的消息循环以使自己模态。这个消息循环内置于Windows中,并且完全不知道Winforms消息循环的样子。因此,在使用MessageBox时,Winforms的任何特殊操作都不会发生。其中包括键盘处理:检测助记符、实现导航并调用ProcessCmdKey()等方法,以便窗体可以实现自己的快捷键。通常情况下这不是问题,因为它应该是模态的并忽略用户输入。
唯一实际的解决方法是在其自己的线程上显示消息框。这在winapi中是正式允许的,窗口的所有者可以是另一个线程拥有的窗口。但这是Microsoft在添加到.NET 2.0的代码中未实现的规则,该代码检测线程错误。绕过该代码需要一个IWin32Window作为消息框所有者,而该IWin32Window不是Control。
将以下代码粘贴到项目中,添加一个新类:
using System;
using System.Threading;
using System.Windows.Forms;

public class NonModalMessageBox : IWin32Window {
    public NonModalMessageBox(Form owner, Action<IWin32Window> display) {
        this.handle = owner.Handle;
        var t = new Thread(new ThreadStart(() => display(this)));
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }
    public IntPtr Handle {
        get { return handle; }
    }
    private IntPtr handle;
}

并且像这样使用:

        new NonModalMessageBox(this, (owner) => {
            MessageBox.Show(owner, "Testing", "Non-modal");
        });

当消息框显示时,应禁用的表单对象是this。如果FUD太强大,那么我无法让你感到更好,你确实需要重新使用自己的Form类重新实现MessageBox,这样你就可以使用Show()而不是ShowDialog()。已经完成了。


不用担心代码的可读性问题,这比我之前尝试的要干净得多。 - John Hall

0
你可以传递一个对窗体的引用,在打开对话框时将Enabled属性设置为false,当对话框关闭时再将其设置为true。

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