86

I have a Windows Forms application VS2010 C# where I display a MessageBox for show a message.

I have an okay button, but if they walk away, I want to timeout and close the message box after lets say 5 seconds, automatically close the message box.

There are custom MessageBox (that inherited from Form) or another reporter Forms, but it would be interesting not necessary a Form.

Any suggestions or samples about it?

Updated:

For WPF
Automatically close messagebox in C#

Custom MessageBox (using Form inherit)
http://www.codeproject.com/Articles/17253/A-Custom-Message-Box

http://www.codeproject.com/Articles/327212/Custom-Message-Box-in-VC

http://tutplusplus.blogspot.com.es/2010/07/c-tutorial-create-your-own-custom.html

http://medmondson2011.wordpress.com/2010/04/07/easy-to-use-custom-c-message-box-with-a-configurable-checkbox/

Scrollable MessageBox
A Scrollable MessageBox in C#

Exception Reporter
https://stackoverflow.com/questions/49224/good-crash-reporting-library-in-c-sharp

http://www.codeproject.com/Articles/6895/A-Reusable-Flexible-Error-Reporting-Framework

Solution:

Maybe I think the following answers are good solution, without use a Form.

https://stackoverflow.com/a/14522902/206730
https://stackoverflow.com/a/14522952/206730

Community
  • 1
  • 1
Kiquenet
  • 13,271
  • 31
  • 133
  • 232
  • 1
    Take a look at this (Windows Phone, but should be the same): http://stackoverflow.com/questions/9674122/how-to-make-a-messagebox-disappear-after-3-seconds – jAC Jan 25 '13 at 13:20
  • 6
    @istepaniuk he cant try if he dont know. so stop that kind of questions – Mustafa Ekici Jan 25 '13 at 13:23
  • 1
    You should be able to create a timer and set it to close after a set amount of time – Steven Ackley Jan 25 '13 at 13:27
  • 2
    You can create the Form as a `MessageBox` – spajce Jan 25 '13 at 13:32
  • 1
    Basic code you need is here: http://stackoverflow.com/questions/12090691/closing-openfiledialog-savefiledialog/12092142#12092142 – Hans Passant Jan 25 '13 at 13:45
  • 1
    @Kiquenet - I have to downvote this question since you have not even shown us what you have tried. – Security Hound Jan 25 '13 at 13:47
  • 2
    @MustafaEkici, I was inviting the OP to show what has he tried. I assume he must have tried and failed before actually asking in SO. That's why Ramhound and I downvoted the question. You can read http://meta.stackexchange.com/questions/122986/is-it-ok-to-leave-what-have-you-tried-comments – istepaniuk Jan 25 '13 at 14:12

14 Answers14

126

Try the following approach:

AutoClosingMessageBox.Show("Text", "Caption", 1000);

Where the AutoClosingMessageBox class implemented as following:

public class AutoClosingMessageBox {
    System.Threading.Timer _timeoutTimer;
    string _caption;
    AutoClosingMessageBox(string text, string caption, int timeout) {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);
        using(_timeoutTimer)
            MessageBox.Show(text, caption);
    }
    public static void Show(string text, string caption, int timeout) {
        new AutoClosingMessageBox(text, caption, timeout);
    }
    void OnTimerElapsed(object state) {
        IntPtr mbWnd = FindWindow("#32770", _caption); // lpClassName is #32770 for MessageBox
        if(mbWnd != IntPtr.Zero)
            SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        _timeoutTimer.Dispose();
    }
    const int WM_CLOSE = 0x0010;
    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}

Update: If you want to get the return value of the underlying MessageBox when user selects something before the timeout you can use the following version of this code:

var userResult = AutoClosingMessageBox.Show("Yes or No?", "Caption", 1000, MessageBoxButtons.YesNo);
if(userResult == System.Windows.Forms.DialogResult.Yes) { 
    // do something
}
...
public class AutoClosingMessageBox {
    System.Threading.Timer _timeoutTimer;
    string _caption;
    DialogResult _result;
    DialogResult _timerResult;
    AutoClosingMessageBox(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);
        _timerResult = timerResult;
        using(_timeoutTimer)
            _result = MessageBox.Show(text, caption, buttons);
    }
    public static DialogResult Show(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        return new AutoClosingMessageBox(text, caption, timeout, buttons, timerResult)._result;
    }
    void OnTimerElapsed(object state) {
        IntPtr mbWnd = FindWindow("#32770", _caption); // lpClassName is #32770 for MessageBox
        if(mbWnd != IntPtr.Zero)
            SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        _timeoutTimer.Dispose();
        _result = _timerResult;
    }
    const int WM_CLOSE = 0x0010;
    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}

Yet another Update

I have checked the @Jack's case with YesNo buttons and discovered that the approach with sending the WM_CLOSE message does not work at all.
I will provide a fix in the context of the separate AutoclosingMessageBox library. This library contains redesigned approach and, I believe, can be useful to someone.
It also available via NuGet package:

Install-Package AutoClosingMessageBox

Release Notes (v1.0.0.2):
- New Show(IWin32Owner) API to support most popular scenarios (in the context of #1 );
- New Factory() API to provide full control on MessageBox showing;

DmitryG
  • 16,927
  • 1
  • 28
  • 51
  • Better use System.Threading.Timer or System.Timers.Timer (like @Jens answer) ? SendMessage vs PostMessage ? – Kiquenet Jan 28 '13 at 08:47
  • @Kiquenet I believe there are no significant differences in this specific situation. – DmitryG Jan 28 '13 at 09:12
  • @DmitryG Is it possible to let me know how to do the same but with splash screen or a picture instead of message box? – Tak May 30 '14 at 04:57
  • @DmitryG I'm also asking how to display the countdown in a label? – Tak Jun 01 '14 at 01:03
  • @user1460166 about countdown...just change the timer settings to iterate timer-callbacks multiple times and add some code to send the `WM_SETTEXT` message into the window... – DmitryG Jun 02 '14 at 08:42
  • I see you pass null for lpClassName, isn't there any special classname for the MessageBox window? If so, one could use it to make the chance of closing some other window that has the same caption smaller – George Birbilis Sep 02 '15 at 14:13
  • 1
    @GeorgeBirbilis Thanks, it can make a sense... In this case you can use `#32770` value as a class name – DmitryG Sep 02 '15 at 15:13
  • Would be even nicer, if one could get the return value of the underlying `MessageBox` if the user selects something before the timeout. Also, isn't there some risk of closing the wrong window with the same caption? – Marcus Mangelsdorf Feb 05 '16 at 08:17
  • I would recommend two small changes to this code: 1. Make the constructor private so you force people to call the Show method. 2. Make the class implement IDisposable and then wrap the return statement in a using statement; that way the timer doesn't go off after the user has already pressed the button. – soapergem Apr 22 '16 at 20:26
  • @SoaperGEM Please, check my code - the constructor is private. Regarding IDisposable - the last variant of implementation is proffered, and it is not intended to be used with IDisposable rather then with DialogResult. Moreover, there are no scenarios which can produce timer's leak. – DmitryG Apr 22 '16 at 20:53
  • Whoops, my mistake on the constructor. It seems I copy/pasted a modified version which had it public. But there is indeed a chance for the timer to leak as you have it above... in fact I just watched it leak while debugging. I set a breakpoint in OnTimerElapsed, then called AutoClosingMessageBox.Show() with a 5-second timeout. As quickly as I could I pressed the "OK" button, which closes the dialog. But 5 seconds later it still hit my breakpoint. – soapergem Apr 22 '16 at 20:57
  • Ok, this make a sense. I've added a "fix" by wrapping a `MessageBox.Show` with `using(_timeoutTimer)`. Thanks for collaboration! – DmitryG Apr 22 '16 at 20:58
  • 2
    It does not work for me if buttons are `System.Windows.Forms.MessageBoxButtons.YesNo` – Jack Nov 23 '16 at 16:18
  • 1
    @Jack Sorry for late reply, I have checked the case with `YesNo` buttons - you are absolutely correct - it does not work. I will provide a fix in the context the separate [AutoclosingMessageBox](https://github.com/DmitryGaravsky/AutoClosingMessageBox) library. The contains the redesigned approach and, I believe can be useful. Thanks! – DmitryG Apr 24 '17 at 10:45
35

A solution that works in WinForms:

var w = new Form() { Size = new Size(0, 0) };
Task.Delay(TimeSpan.FromSeconds(10))
    .ContinueWith((t) => w.Close(), TaskScheduler.FromCurrentSynchronizationContext());

MessageBox.Show(w, message, caption);

Based on the effect that closing the form that owns the message box will close the box as well.

Windows Forms controls have a requirement that they must be accessed on the same thread that created them. Using TaskScheduler.FromCurrentSynchronizationContext() will ensure that, assuming that the example code above is executed on the UI thread, or an user-created thread. The example will not work correctly if the code is executed on a thread from a thread pool (e.g. a timer callback) or a task pool (e.g. on a task created with TaskFactory.StartNew or Task.Run with default parameters).

BSharp
  • 709
  • 7
  • 10
  • What's version .NET ? What's it TaskScheduler ? – Kiquenet Oct 17 '14 at 11:07
  • 1
    @Kiquenet .NET 4.0 and up. `Task` and `TaskScheduler` are from namespace `System.Threading.Tasks` in mscorlib.dll so no additional assembly references are needed. – BSharp Oct 17 '14 at 22:17
  • 1
    Great solution! One addition... this worked well in a Winforms app, AFTER adding BringToFront for the new form, right after creating it. Otherwise, the dialog boxes sometimes showed *behind* the current active form, i.e., did not appear to the user. var w = new Form() { Size = new Size(0, 0) }; w.BringToFront(); – Developer63 Aug 01 '17 at 22:03
  • @Developer63 I could not reproduce your experience. Even when calling `w.SentToBack()` right before `MessageBox.Show()`, the dialog box still showed on top of the main form. Tested on .NET 4.5 and 4.7.1. – BSharp Feb 28 '18 at 22:59
  • This solution could be nice, but it is only available from .NET 4.5 and up, not 4.0 (because of Task.Delay) see: https://stackoverflow.com/questions/17717047/system-threading-task-does-not-contain-definition – KwentRell Apr 27 '18 at 08:09
  • @KwentRell You are correct. `Task` in .NET 4.0 does not have `Delay` yet. – BSharp May 15 '18 at 22:11
  • How to default to Yes? Now the result is the default which is No. – jw_ Nov 22 '19 at 06:30
17

AppActivate!

If you don't mind muddying your references a bit, you can include Microsoft.Visualbasic, and use this very short way.

Display the MessageBox

    (new System.Threading.Thread(CloseIt)).Start();
    MessageBox.Show("HI");

CloseIt Function:

public void CloseIt()
{
    System.Threading.Thread.Sleep(2000);
    Microsoft.VisualBasic.Interaction.AppActivate( 
         System.Diagnostics.Process.GetCurrentProcess().Id);
    System.Windows.Forms.SendKeys.SendWait(" ");
}

Now go wash your hands!

Community
  • 1
  • 1
FastAl
  • 5,976
  • 2
  • 34
  • 58
11

The System.Windows.MessageBox.Show() method has an overload which takes an owner Window as the first parameter. If we create an invisible owner Window which we then close after a specified time, it's child message box would close as well.

Window owner = CreateAutoCloseWindow(dialogTimeout);
MessageBoxResult result = MessageBox.Show(owner, ...

So far so good. But how do we close a window if the UI thread is blocked by the message box and UI controls can't be accessed from a worker thread? The answer is - by sending a WM_CLOSE windows message to the owner window handle:

Window CreateAutoCloseWindow(TimeSpan timeout)
{
    Window window = new Window()
    {
        WindowStyle = WindowStyle.None,
        WindowState = System.Windows.WindowState.Maximized,
        Background =  System.Windows.Media.Brushes.Transparent, 
        AllowsTransparency = true,
        ShowInTaskbar = false,
        ShowActivated = true,
        Topmost = true
    };

    window.Show();

    IntPtr handle = new WindowInteropHelper(window).Handle;

    Task.Delay((int)timeout.TotalMilliseconds).ContinueWith(
        t => NativeMethods.SendMessage(handle, 0x10 /*WM_CLOSE*/, IntPtr.Zero, IntPtr.Zero));

    return window;
}

And here is the import for the SendMessage Windows API method:

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Esge
  • 170
  • 1
  • 8
  • Window type is for Windows Forms ? – Kiquenet Nov 20 '13 at 19:25
  • Why do you need to send a message to the hidden parent window to close it? Can't you just call some "Close" method on it or dispose it otherways? – George Birbilis Sep 02 '15 at 13:31
  • To answer my own question, the OwnedWindows property of that WPF Window seems to show 0 windows and Close doesn't close the messagebox child – George Birbilis Sep 02 '15 at 13:53
  • 2
    Briliant solution. There is some naming overlap in `System.Windows` and `System.Windows.Forms` that took me some time to figure out. You will need the following: `System`, `System.Runtime.InteropServices`, `System.Threading.Tasks`, `System.Windows`, `System.Windows.Interop`, `System.Windows.Media` – m3tikn0b Dec 09 '15 at 20:31
10

You could try this:

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

[DllImport("user32.Dll")]
static extern int PostMessage(IntPtr hWnd, UInt32 msg, int wParam, int lParam);

private const UInt32 WM_CLOSE = 0x0010;

public void ShowAutoClosingMessageBox(string message, string caption)
{
    var timer = new System.Timers.Timer(5000) { AutoReset = false };
    timer.Elapsed += delegate
    {
        IntPtr hWnd = FindWindowByCaption(IntPtr.Zero, caption);
        if (hWnd.ToInt32() != 0) PostMessage(hWnd, WM_CLOSE, 0, 0);
    };
    timer.Enabled = true;
    MessageBox.Show(message, caption);
}
Jens Granlund
  • 4,630
  • 1
  • 31
  • 29
  • 2
    Better use System.Threading.Timer or System.Timers.Timer (like @DmitryG answer) ? SendMessage vs PostMessage ? – Kiquenet Apr 09 '13 at 09:30
  • 1
    see http://stackoverflow.com/questions/3376619/what-is-the-difference-between-send-message-and-post-message-and-how-these-relat and maybe also http://stackoverflow.com/questions/2411116/equivalent-of-postmessage-in-c-sharp-to-synchronize-with-the-main-thread-with-mv on the difference between SendMessage and PostMessage – George Birbilis Sep 02 '15 at 13:58
8

RogerB over at CodeProject has one of the slickest solutions to this answer, and he did that back in '04, and it's still bangin'

Basically, you go here to his project and download the CS file. In case that link ever dies, I've got a backup gist here. Add the CS file to your project, or copy/paste the code somewhere if you'd rather do that.

Then, all you'd have to do is switch

DialogResult result = MessageBox.Show("Text","Title", MessageBoxButtons.CHOICE)

to

DialogResult result = MessageBoxEx.Show("Text","Title", MessageBoxButtons.CHOICE, timer_ms)

And you're good to go.

kayleeFrye_onDeck
  • 5,531
  • 5
  • 56
  • 62
  • 1
    You missed the .Show extension in your examples... Should read: DialogResult result = MessageBoxEx.Show("Text","Title", MessageBoxButtons.CHOICE, timer_ms) – Edd Dec 07 '18 at 15:57
2

There is an codeproject project avaliable HERE that provides this functuanility.

Following many threads here on SO and other boards this cant be done with the normal MessageBox.

Edit:

I have an idea that is a bit ehmmm yeah..

Use a timer and start in when the MessageBox appears. If your MessageBox only listens to the OK Button (only 1 possibility) then use the OnTick-Event to emulate an ESC-Press with SendKeys.Send("{ESC}"); and then stop the timer.

jAC
  • 4,800
  • 6
  • 37
  • 51
  • 1
    Timer concept is a simple way... but have to ensure the sent keys hit your app if it doesn't have or loses the focus. That would require SetForegroundWindow and the the answer starts to include more code, but see 'AppActivate' below. – FastAl Jan 25 '13 at 16:10
2

DMitryG's code "get the return value of the underlying MessageBox" has a bug so the timerResult is never actually correctly returned (MessageBox.Show call returns AFTER OnTimerElapsed completes). My fix is below:

public class TimedMessageBox {
    System.Threading.Timer _timeoutTimer;
    string _caption;
    DialogResult _result;
    DialogResult _timerResult;
    bool timedOut = false;

    TimedMessageBox(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None)
    {
        _caption = caption;
        _timeoutTimer = new System.Threading.Timer(OnTimerElapsed,
            null, timeout, System.Threading.Timeout.Infinite);
        _timerResult = timerResult;
        using(_timeoutTimer)
            _result = MessageBox.Show(text, caption, buttons);
        if (timedOut) _result = _timerResult;
    }

    public static DialogResult Show(string text, string caption, int timeout, MessageBoxButtons buttons = MessageBoxButtons.OK, DialogResult timerResult = DialogResult.None) {
        return new TimedMessageBox(text, caption, timeout, buttons, timerResult)._result;
    }

    void OnTimerElapsed(object state) {
        IntPtr mbWnd = FindWindow("#32770", _caption); // lpClassName is #32770 for MessageBox
        if(mbWnd != IntPtr.Zero)
            SendMessage(mbWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        _timeoutTimer.Dispose();
        timedOut = true;
    }

    const int WM_CLOSE = 0x0010;
    [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Tomas Kubes
  • 20,134
  • 14
  • 92
  • 132
OnWeb
  • 21
  • 3
1

Vb.net library has a simple solution using interaction class for this:

void MsgPopup(string text, string title, int secs = 3)
{
    dynamic intr = Microsoft.VisualBasic.Interaction.CreateObject("WScript.Shell");
    intr.Popup(text, secs, title);
}

bool MsgPopupYesNo(string text, string title, int secs = 3)
{
    dynamic intr = Microsoft.VisualBasic.Interaction.CreateObject("WScript.Shell");
    int answer = intr.Popup(text, secs, title, (int)Microsoft.VisualBasic.Constants.vbYesNo + (int)Microsoft.VisualBasic.Constants.vbQuestion);
    return (answer == 6);
}
Cenk
  • 51
  • 1
  • 3
0

There is an undocumented API in user32.dll named MessageBoxTimeout() but it requires Windows XP or later.

Greg Wittmeyer
  • 386
  • 3
  • 13
0

use EndDialog instead of sending WM_CLOSE:

[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
Kaven Wu
  • 31
  • 4
0

I did it like this

var owner = new Form { TopMost = true };
Task.Delay(30000).ContinueWith(t => {
owner.Invoke(new Action(()=>
{
      if (!owner.IsDisposed)
      {
          owner.Close();
      }
   }));
});
var dialogRes =  MessageBox.Show(owner, msg, "Info", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
santipianis
  • 41
  • 1
  • 7
0

If anyone wants AutoClosingMessageBox in c++ I have implemented the equivalent code here is the link to gists

static intptr_t MessageBoxHookProc(int nCode, intptr_t wParam, intptr_t lParam)
{
    if (nCode < 0)
        return CallNextHookEx(hHook, nCode, wParam, lParam);

    auto msg = reinterpret_cast<CWPRETSTRUCT*>(lParam);
    auto hook = hHook;

    //Hook Messagebox on Initialization.
    if (!hookCaption.empty() && msg->message == WM_INITDIALOG)
    {
        int nLength = GetWindowTextLength(msg->hwnd);
        char* text = new char[captionLen + 1];

        GetWindowText(msg->hwnd, text, captionLen + 1);

        //If Caption window found Unhook it.
        if (hookCaption == text)
        {
            hookCaption = string("");
            SetTimer(msg->hwnd, (uintptr_t)timerID, hookTimeout, (TIMERPROC)hookTimer);
            UnhookWindowsHookEx(hHook);
            hHook = 0;
        }
    }

    return CallNextHookEx(hook, nCode, wParam, lParam);
}
Suraj Rao
  • 28,186
  • 10
  • 88
  • 94
HaseeB Mir
  • 675
  • 12
  • 17
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/28223243) – costaparas Feb 02 '21 at 06:30
  • Ok will add code snippet as well – HaseeB Mir Feb 02 '21 at 06:31
0

I know this question is 8 year old, however there was and is a better solution for this purpose. It's always been there, and still is: User32.dll!MessageBoxTimeout.

This is an undocumented function used by Microsoft Windows, and it does exactly what you want and even more. It supports different languages as well.

C# Import:

[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxTimeout(IntPtr hWnd, String lpText, String lpCaption, uint uType, Int16 wLanguageId, Int32 dwMilliseconds);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetForegroundWindow();

How to use it in C#:

uint uiFlags = /*MB_OK*/ 0x00000000 | /*MB_SETFOREGROUND*/  0x00010000 | /*MB_SYSTEMMODAL*/ 0x00001000 | /*MB_ICONEXCLAMATION*/ 0x00000030;

NativeFunctions.MessageBoxTimeout(NativeFunctions.GetForegroundWindow(), $"Kitty", $"Hello", uiFlags, 0, 5000);

Work smarter, not harder.

Norbert Boros
  • 1,079
  • 8
  • 31