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

82

У меня есть приложение 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/

Прокручиваемое окно сообщений
Прокручиваемое окно сообщений в C #

Репортер исключений
/programming/49224/good-crash-reporting-library-in-c-sharp

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

Решение:

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

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

Kiquenet
источник
1
Взгляните на это (Windows Phone, но должен быть таким же): stackoverflow.com/questions/9674122/…
jAC
6
@istepaniuk он не может попробовать, если не знает. так что прекратите такие вопросы
Мустафа Экичи
1
У вас должна быть возможность создать таймер и установить его на закрытие через установленный промежуток времени,
Стивен Экли,
2
Вы можете создать форму какMessageBox
spajce
2
@MustafaEkici, я приглашал ОП, чтобы показать, что он пробовал. Я предполагаю, что он, должно быть, пытался и потерпел неудачу, прежде чем на самом деле спросил в SO. Вот почему мы с Ramhound проголосовали против этого вопроса. Вы можете прочитать meta.stackexchange.com/questions/122986/…
istepaniuk

Ответы:

123

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

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@Jack с кнопками и обнаружил, что подход с отправкой WM_CLOSEсообщения вообще не работает.
Я предоставлю исправление в контексте отдельной библиотеки AutoclosingMessageBox . Эта библиотека содержит переработанный подход и, я считаю, может быть кому-то полезна.
Он также доступен через пакет NuGet :

Install-Package AutoClosingMessageBox

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

DmitryG
источник
Лучше использовать System.Threading.Timer или System.Timers.Timer (например, ответ @Jens)? SendMessage против PostMessage?
Kiquenet
@Kiquenet Я считаю, что в этой конкретной ситуации нет существенных различий.
DmitryG
1
@GeorgeBirbilis Спасибо, это может иметь смысл ... В этом случае вы можете использовать #32770значение в качестве имени класса
DmitryG 02
2
У меня не работает, если кнопки естьSystem.Windows.Forms.MessageBoxButtons.YesNo
Джек
1
@Jack Извините за поздний ответ, я проверил корпус с YesNoкнопками - вы абсолютно правы - не работает. Я предоставлю исправление в контексте отдельной библиотеки AutoclosingMessageBox . Он содержит переработанный подход и, я считаю, может быть полезен. Благодаря!
DmitryG
32

Решение, работающее в 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с ними).

BSharp
источник
Какая версия .NET? Что это за TaskScheduler?
Kiquenet
1
@Kiquenet .NET 4.0 и выше. Taskи TaskSchedulerнаходятся из пространства имен System.Threading.Tasksв mscorlib.dll, поэтому дополнительные ссылки на сборку не требуются.
BSharp
Отличное решение! Одно дополнение ... это хорошо работало в приложении Winforms ПОСЛЕ добавления BringToFront для новой формы, сразу после ее создания. В противном случае диалоговые окна иногда отображались позади текущей активной формы, т. Е. Не отображались для пользователя. var w = new Form () {Size = new Size (0, 0)}; w.BringToFront ();
Developer63
@ Developer63 Я не смог воспроизвести ваш опыт. Даже при вызове w.SentToBack()перед MessageBox.Show(), диалоговое окно все еще показано на верхней части главной формы. Проверено на .NET 4.5 и 4.7.1.
BSharp
Это решение может быть хорошим, но он доступен только с .NET 4.5 и выше, а не 4,0 (из Task.Delay) см: stackoverflow.com/questions/17717047/...
KwentRell
16

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

А теперь вымойте руки!

FastAl
источник
11

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

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

Все идет нормально. Но как закрыть окно, если поток пользовательского интерфейса заблокирован окном сообщения, а элементы управления пользовательского интерфейса не могут быть доступны из рабочего потока? Ответ - отправив сообщение Windows 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;
}

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

static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}
Esge
источник
Тип окна для Windows Forms?
Kiquenet
Почему вам нужно отправить сообщение скрытому родительскому окну, чтобы закрыть его? Разве нельзя просто вызвать для него какой-нибудь метод «Close» или избавиться от него другим способом?
Джордж Бирбилис 02
Чтобы ответить на мой собственный вопрос, свойство OwnedWindows этого окна WPF, похоже, показывает 0 окон, а Close не закрывает дочерний элемент сообщения
Джордж Бирбилис
2
Блестящее решение. Есть некоторое совпадение названий, System.Windowsи System.Windows.Formsмне потребовалось время, чтобы разобраться. Вам потребуется следующее: System, System.Runtime.InteropServices, System.Threading.Tasks, System.Windows, System.Windows.Interop,System.Windows.Media
m3tikn0b
10

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

[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);
}
Йенс Гранлунд
источник
2
Лучше использовать System.Threading.Timer или System.Timers.Timer (например, ответ @DmitryG)? SendMessage против PostMessage?
Kiquenet
1
см. stackoverflow.com/questions/3376619/… и, возможно, также stackoverflow.com/questions/2411116/… о разнице между SendMessage и PostMessage
Джордж Бирбилис 02
7

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

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

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

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

к

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

И тебе хорошо идти.

kayleeFrye_onDeck
источник
2
Мне нужно было быстрое решение, и оно отлично сработало! Спасибо, что поделился.
Марк Крам
1
Вы пропустили расширение .Show в своих примерах ... Следует читать: DialogResult result = MessageBoxEx.Show ("Text", "Title", MessageBoxButtons.CHOICE, timer_ms)
Эдд
2

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

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

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

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

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

jAC
источник
1
Концепция таймера - это простой способ ... но необходимо убедиться, что отправленные ключи попадают в ваше приложение, если оно не имеет или теряет фокус. Для этого потребуется SetForegroundWindow, и ответ начнет включать больше кода, но см. «AppActivate» ниже.
FastAl
2

Код 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);
}
OnWeb
источник
1

У библиотеки 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);
}
Дженк
источник
0

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

Грег Виттмейер
источник
0

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

[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
Кавен Ву
источник
0

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

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
источник