Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MessageBox modal to a single form

Tags:

c#

winforms

I want to display a MessageBox that is modal to just a single form, and allows other top level windows on the thread to remain active. WinForms disables all windows on the current thread, whether displaying a custom modal form or using the MessageBox class.

I've tried a number of different approaches to solving the problem, including re-enabling any other top level windows (using the Win32 EnableWindow function) and p/invoking the native MessageBox function. Both of these approaches work, but leave keyboard shortcuts inoperable, including tab to move between controls, escape to cancel an open menu and most importantly menu shortcut keys.

I can make menu shortcut keys work by installing a keyboard hook when I display the MessageBox and then directly calling ProcessCmdKey on the active form, but other keyboard shortcuts still don't work. I guess I could press on and add more hacks to make these other cases work, but I was wondering if there was a better way.

Note, this is not about being non-blocking, but about not disabling all windows on the current thread. It happens that the solution may be similar, however.

like image 972
John Hall Avatar asked Nov 21 '25 13:11

John Hall


1 Answers

The basic problem you are battling here is that MessageBox.Show() pumps its own message loop to make itself modal. This message loop is built into Windows and is thoroughly unaware of what the Winforms message loop looks like. So anything special that Winforms does in its message loop just won't happen when you use MessageBox. Which is keyboard handling: detecting mnemonics, implementing navigation and calling methods like ProcessCmdKey() so that a form can implement its own shortcut keystrokes. Not normally a problem since it is supposed to be modal and ignore user input.

The only practical way to revive this is to display the message box on its own thread. This is formally allowed in the winapi, the owner of a window can be a window owned by another thread. But that's a rule that Microsoft did not implement when they added the code to .NET 2.0 that detects threading mistakes. Working around that code requires an IWin32Window as the message box owner that is not also a Control.

Add a new class to your project and paste this code:

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;
}

And use it like this:

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

Where this the Form object that should be disabled while the message box is displayed. There's little I can do to make you feel better about this hack, if the FUD is too overpowering then you really do need to re-implement MessageBox with your own Form class so you can use Show() instead of ShowDialog(). It has been done.

like image 148
Hans Passant Avatar answered Nov 23 '25 03:11

Hans Passant



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!