Закройте MessageBox через несколько секунд

У меня есть приложение Windows Forms VS2010 C#, где я отображаю MessageBox для показа сообщения.

У меня есть кнопка "ОК", но если они уходят, я хочу установить таймаут и закрыть окно сообщения, скажем, через 5 секунд, автоматически закрыть окно сообщения.

Существуют пользовательские MessageBox (которые унаследованы от Form) или другие формы-репортеры, но было бы интересно, если бы не Form.

Любые предложения или образцы об этом?

Обновлено:

Для WPF
Автоматически закрывать окно сообщения в C#

Пользовательский MessageBox (используя наследование формы)
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/

Прокручиваемый MessageBox
Прокручиваемый MessageBox в C#

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

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

Решение:

Может быть, я думаю, что следующие ответы являются хорошим решением, без использования формы.

/questions/21227247/zakrojte-messagebox-cherez-neskolko-sekund/21227286#21227286
/questions/21227247/zakrojte-messagebox-cherez-neskolko-sekund/21227276#21227276

15 ответов

Решение

Попробуйте следующий подход:

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

Где AutoClosingMessageBox Класс реализован следующим образом:

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

Обновление: если вы хотите получить возвращаемое значение базового MessageBox, когда пользователь выбирает что-то до истечения времени ожидания, вы можете использовать следующую версию этого кода:

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

Еще одно обновление

Я проверил дело @ Джек с YesNo кнопки и обнаружили, что подход с отправкой WM_CLOSE сообщение не работает вообще.
Я предоставлю исправление в контексте отдельной библиотеки AutoclosingMessageBox. Эта библиотека содержит переработанный подход и, я полагаю, может быть кому-то полезна.
Это также доступно через пакет NuGet:

Install-Package AutoClosingMessageBox

Примечания к выпуску (v1.0.0.2):
- Новый API-интерфейс Show(IWin32Owner) для поддержки наиболее популярных сценариев (в контексте # 1);
- Новый API Factory(), обеспечивающий полный контроль над отображением MessageBox;

Решение, которое работает в 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);

В зависимости от того, что закрытие формы, которой принадлежит окно сообщения, также закроет окно.

Для элементов управления Windows Forms требуется доступ к ним в том же потоке, который их создал. С помощью TaskScheduler.FromCurrentSynchronizationContext() будет гарантировать, что при условии, что приведенный выше пример кода выполняется в потоке пользовательского интерфейса или в потоке, созданном пользователем. Пример не будет работать правильно, если код выполняется в потоке из пула потоков (например, обратный вызов таймера) или пула задач (например, для задачи, созданной с помощью TaskFactory.StartNew или же Task.Run с параметрами по умолчанию).

AppActivate!

Если вы не возражаете немного запятнать свои ссылки, вы можете включить Microsoft.Visualbasic, и использовать этот очень короткий путь.

Показать MessageBox

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

Функция CloseIt:

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

А теперь иди мыть руки!

Вы можете попробовать это:

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

Метод System.Windows.MessageBox.Show() имеет перегрузку, которая принимает окно-владелец в качестве первого параметра. Если мы создадим Окно невидимого владельца, которое мы затем закроем через указанное время, его дочернее окно сообщения также закроется.

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

Все идет нормально. Но как мы можем закрыть окно, если поток пользовательского интерфейса блокируется окном сообщения и элементы управления пользовательского интерфейса не могут быть доступны из рабочего потока? Ответ таков: отправив сообщение окна WM_CLOSE дескриптору окна владельца:

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

А вот импорт метода SendMessage для Windows API:

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}

У RogerB в CodeProject есть одно из самых изящных решений для этого ответа, и он сделал это еще в '04, и он все еще не работает

В основном, вы идете сюда, чтобы его проект и скачать файл CS. На случай, если эта ссылка когда-нибудь умрет, у меня есть резервная копия. Добавьте CS-файл в свой проект или скопируйте / вставьте код куда-нибудь, если вы этого хотите.

Затем все, что вам нужно сделать, это переключиться

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

в

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

И ты в порядке.

Я знаю, что этому вопросу 8 лет, однако для этой цели было и есть лучшее решение. Он всегда был и остается: User32.dll!MessageBoxTimeout.

Это недокументированная функция, используемая Microsoft Windows, и она делает именно то, что вы хотите, и даже больше. Он также поддерживает разные языки.

C # Импорт:

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

Как использовать в C#:

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

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

Работай умом, а не силой.

Я сделал это вот так

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

Код DMitryG "получить возвращаемое значение базового MessageBox"есть ошибка, поэтому timerResult никогда не возвращается правильно (MessageBox.Show звонок возвращается ПОСЛЕ OnTimerElapsed завершается). Мое исправление ниже:

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

Существует проект codeproject, доступный ЗДЕСЬ, который обеспечивает эту функциональность.

Следуя многим потокам здесь, на SO и других платах, это нельзя сделать с помощью обычного MessageBox.

Редактировать:

У меня есть идея, которая немного эмм да...

Используйте таймер и начните, когда появится MessageBox. Если ваш MessageBox слушает только кнопку OK (только 1 возможность), используйте OnTick-Event для эмуляции ESC-Press с SendKeys.Send("{ESC}"); и затем остановите таймер.

Использование EndDialog вместо отправки WM_CLOSE:

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

У библиотеки Vb.net есть простое решение, использующее для этого класс взаимодействия:

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

Если кому-то нужен AutoClosingMessageBox в С ++, я реализовал эквивалентный код, вот ссылка на 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);
}

У меня был немного другой вариант использования: мне нужно было разрешить пользователю отменить другую задачу во время ее выполнения. Таким образом, я сделал асинхронную версию методов формы, использованных выше:

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

Использование для отмены по времени:

      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
}

Мое собственное использование:

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

Существует недокументированный API в user32.dll с именем MessageBoxTimeout(), но для него требуется Windows XP или более поздняя версия.

Другие вопросы по тегам