Close a MessageBox after several seconds

C#WinformsMessagebox

C# Problem Overview


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
https://stackoverflow.com/questions/4362235/automatically-close-messagebox-in-c-sharp

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
https://stackoverflow.com/questions/4681936/a-scrollable-messagebox-in-c-sharp

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

C# Solutions


Solution 1 - C#

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;

Solution 2 - C#

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).

Solution 3 - C#

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!

Solution 4 - C#

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

Solution 5 - C#

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

Solution 6 - C#

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.

Solution 7 - C#

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.

Solution 8 - C#

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

Solution 9 - C#

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

Solution 10 - C#

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: MessageBoxTimeout() in User32.dll.

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.

Solution 11 - C#

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

Solution 12 - C#

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

Solution 13 - C#

use EndDialog instead of sending WM_CLOSE:

[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);

Solution 14 - C#

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

Solution 15 - C#

I had a slightly different use case in that I needed to allow the user to cancel another task while it was running. As such, I made an async version of the Form methods used above:

public static async Task<DialogResult?> ShowDialogAsync(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, CancellationToken cancellationToken)
{
    // Create a dummy form to parent the messagebox
    var form = new Form();

    // We'll need to dispose of the parent form to destroy the messagbeox
    Action disposeForm = () =>
    {
        if (!form.IsDisposed)
        {
            // Async tasks can resume on a different thread to the one they started on
            // so we might need to invoke this to prevent a cross-thread exception
            if (form.InvokeRequired)
            {
                form.BeginInvoke(new Action(() => form.Dispose()));
            }
            else
            {
                form.Dispose();
            }
        }
    };

    try
    {
        // subscribe to the cancellation and close/dispose of the form if cancellation is required
        using (var cancellationRegistration = cancellationToken.Register(() => disposeForm()))
        {
            var result = await Task.Run<DialogResult>(() => MessageBox.Show(form, text, caption, buttons, icon));

            // If cancellation is requested we return null, otherwise we return the result of the dialog
            if (cancellationToken.IsCancellationRequested)
            {
                return null;
            }
            return result;
        }
    }
    finally
    {
        // we always want to close/dispose the form
        disposeForm();
    }
}

Usage for timed cancellation:

var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2)); // timeout 2 seconds
DialogResult? result = await ShowDialogAsync("some text", "some title", MessageBoxButtons.OK, MessageBoxIcon.Information);
if (result.HasValue)
{
    if (result == MessageBoxButtons.OK)
    {
        // do something
    }
}
else
{
    // the dialog timed out
}

My own usage:

private async void button1_Click(object sender, EventArgs e)
{
    // I've got two cancellation token sources: one for the messagebox, one for the other task
    var cancellationTokenSource = new CancellationTokenSource();
    var otherTaskCancellationTokenSource = new CancellationTokenSource();

    // Show the dialog and also start the other task
    var dialogTask = ShowDialogAsync("The other task is running now.", "Title", MessageBoxButtons.OKCancel, MessageBoxIcon.Information, cancellationTokenSource.Token);
    var otherTask = OtherAsyncWork(otherTaskCancellationTokenSource.Token);

    try
    {
        // wait until either of the tasks complete (i.e. a button is pressed, or OtherAsyncWork completes)
        await Task.WhenAny(dialogTask, otherTask);
    }
    catch (OperationCanceledException)
    {
        // If otherTask got cancelled, we should get rid of the messagebox as it's not relevant anymore
        cancellationTokenSource.Cancel();
    }

    var result = await dialogTask;
    if (result.HasValue && result.Value == DialogResult.Cancel)
    {
        // The user selected cancel so we should cancel the other task
        otherTaskCancellationTokenSource.Cancel();
    }

    try
    {
        // Wait for the other task to complete
        await otherTask;
    }
    catch (OperationCanceledException)
    {
        MessageBox.Show("other task was cancelled.");
    }
}

// Dummy task with 10-second delay
public async Task OtherAsyncWork(CancellationToken cancellationToken)
{
    await Task.Delay(10000, cancellationToken);
    MessageBox.Show("other task completed.");
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKiquenetView Question on Stackoverflow
Solution 1 - C#DmitryGView Answer on Stackoverflow
Solution 2 - C#BSharpView Answer on Stackoverflow
Solution 3 - C#FastAlView Answer on Stackoverflow
Solution 4 - C#Jens GranlundView Answer on Stackoverflow
Solution 5 - C#EsgeView Answer on Stackoverflow
Solution 6 - C#kayleeFrye_onDeckView Answer on Stackoverflow
Solution 7 - C#jACView Answer on Stackoverflow
Solution 8 - C#OnWebView Answer on Stackoverflow
Solution 9 - C#santipianisView Answer on Stackoverflow
Solution 10 - C#MecanikView Answer on Stackoverflow
Solution 11 - C#CenkView Answer on Stackoverflow
Solution 12 - C#Greg WittmeyerView Answer on Stackoverflow
Solution 13 - C#Kaven WuView Answer on Stackoverflow
Solution 14 - C#HaseeB MirView Answer on Stackoverflow
Solution 15 - C#DiplomacyNotWarView Answer on Stackoverflow