-2

Is there a way to cancel a message box displayed by MessageBox.Show()?

I am thinking that it is possible by sending a close "message" (WM_?) to the native win32 message queue? How exactly?

Don Box
  • 3,166
  • 3
  • 26
  • 55
  • @EdPlunkett that's WinForms... – Don Box May 18 '18 at 13:35
  • There's a link there to a WPF question, but the solution to that is not good, it wants you to create a custom window to mimic the dialog – Don Box May 18 '18 at 13:37
  • Whoops, I shouldn't have retracted the close vote. [Read the code in the accepted answer](https://stackoverflow.com/questions/14522540/close-a-messagebox-after-several-seconds/20098381#20098381). There's nothing specific to winforms about it. If as you say in your question you want to know "how exactly" to call FindWIndow() and SendMessage(), that answer tells you exactly how. That is, unless WPF doesn't use window class "#32770"; have you tested that? – 15ee8f99-57ff-4f92-890c-b56153 May 18 '18 at 13:40
  • https://stackoverflow.com/a/12555069/17034 – Hans Passant May 18 '18 at 13:42
  • The windowing systems of the Windows API and WPF are unrelated. Which one are you asking about? Regardless of that, though, use UI Automation, if you need to automate a UI. – IInspectable May 18 '18 at 13:42
  • I'm not sure I'd call this UI automation. It's more about getting a message to a window when you don't have a handle to it. – Paul Sanders May 18 '18 at 17:01
  • Possible duplicate of [How to suppress a dialog box displayed by code that I can't change?](https://stackoverflow.com/questions/12532812/how-to-suppress-a-dialog-box-displayed-by-code-that-i-cant-change) – Alexei - check Codidact May 18 '18 at 17:40
  • @PaulSanders: I'm not sure what you'd call the process of interacting with a UI through code. That *is* called UI Automation. You'd set up WinEvents to report creation of a window of interest, and then invoke whichever operation on it you see fit. Obviously, that is assuming a framework based on native `HWND`s. The OP tagged the question with technologies using different windowing systems. Apparently they don't know what their problem is, and remained unresponsive when asked to clarify. – IInspectable May 19 '18 at 12:12
  • @IInspectable sorry for not responding earlier. Yes, I'm fully aware Windows API and WPF are not the same :) I am not sure how UI Automation could work, can you provide some details? – Don Box May 19 '18 at 12:34
  • [UI Automation](https://learn.microsoft.com/en-us/dotnet/framework/ui-automation/ui-automation-overview). It's nice that you explained, that you were fully aware, that the Windows API and WPF are not the same thing. It's not immediately obvious, why you then decided against answering the question that actually was asked: Which one are you using? – IInspectable May 19 '18 at 12:42
  • @IInspectable Ah, OK, I assumed, from the quoted call to `MessageBox.Show`, that it was Winforms, sorry. [This page](http://www.wpf-tutorial.com/dialogs/the-messagebox/), though, seems to imply that even in WPF it's a good old-fashioned Win32 message box behind the scenes. – Paul Sanders May 19 '18 at 16:53
  • @IInspectable my question has WPF tag, which means I work with a WPF app. However Win32 API can be used in WPF too. – Don Box May 20 '18 at 13:12

2 Answers2

2

The simplest solution using WinApi would be:

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);


    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    private const int WM_CLOSE = 0x10;
    private const string MessageBoxTitle = "UniqueTitle123";

    void CloseMessageBox()
    {
        var hwnd = FindWindow(null, MessageBoxTitle);

        if (hwnd != IntPtr.Zero)
        {
            PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        }
    }

You need to invoke messagebox with MessageBoxTitle:

MessageBox.Show("This gonna close itself", MessageBoxTitle);

And then somewhere:

CloseMessageBox();

Example use case:

Task.Run(async () =>
{
    await Task.Delay(2000).ConfigureAwait(false);
    CloseMessageBox();
});

MessageBox.Show("This closes in 2 seconds", MessageBoxTitle);

Be aware that if there is different window with the same title as your message box then calling CloseMessageBox() will close that window instead of your message box. The solution is simple, but choose your msgbox title in a way that there will be very small probability of name collison with other windows in the system f.ex. YourAppName-51245 should be fine.

  • Thanks. Pretty close :) The right way would be to subclass WndProc and get the HWND of the messagebox – Don Box May 18 '18 at 14:42
  • 2
    @DonBox I don't see how you would do that. The call to `MessageBox.Show` is modal - there's no 'form' to subclass. The way I do this is to set up a temporary [windows hook](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx) and then catch `WM_INITDIALOG`. That's probably easier to do in native code than .net. Also, this answer can be improved if you know the class name of message boxes. It's probably #32770, Spy++ would tell you. – Paul Sanders May 18 '18 at 14:59
  • PS: You could also send WM_COMMAND, which would let you simulate a particular button press. – Paul Sanders May 18 '18 at 15:25
  • @PaulSanders I don't think you need a window hook. You can do this: https://stackoverflow.com/questions/624367/how-to-handle-wndproc-messages-in-wpf My idea is to catch an event sent to the Window native HWND to get the dialog's HWND. Once we have the dialog's HWND, we can close the dialog when need it – Don Box May 18 '18 at 15:48
  • @DonBox I think you need to flesh this out a bit. I don't understand it at all. – Paul Sanders May 18 '18 at 16:06
0

I thought I would post a 'windows hook' solution to this as I believe it is the best way of doing it. The answer above is fragile. I have provided code in native C++ since I lack the .net skills to answer in C#. Perhaps someone who has those skills would translate. Code is not threadsafe, note. If you create message boxes on more than one thread then you will have to be a bit cleverer.

static HHOOK my_hook;
static HWND message_box_hwnd;

// Hook proc
LRESULT CALLBACK MyHookProc (int code, WPARAM wParam, LPARAM lParam)
{
    if (code < 0)
        return CallNextHookEx (tdata->tcpHook, code, wParam, lParam);

    CWPSTRUCT *msg = (CWPSTRUCT *) lParam;
    LRESULT result = CallNextHookEx (tdata->tcpHook, code, wParam, lParam);

    if (msg->message == WM_INITDIALOG)
    {
        message_box_hwnd = msg->hwnd;
        UnhookWindowsHookEx (my_hook);
        my_hook = NULL;
    }

    return result;
}

my_hook = SetWindowsHookEx (WH_CALLWNDPROC, MyHookProc, NULL, GetCurrentThreadId ());
MessageBox (...);

And, of course, in your timer proc or whatever:

PostMessage (message_box_hwnd, WM_CLOSE, 0, 0);

or (say):

PostMessage (message_box_hwnd, WM_COMMAND, IDCANCEL, 0);

Not much to it, really.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48