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


82

У мене є програма Windows Forms VS2010 C #, де я відображаю MessageBox для показу повідомлення.

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

Існують власні MessageBox (що успадковуються від 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 #

Репортер винятків
/programming/49224/good-crash-reporting-library-in-c-sharp

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

Рішення:

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

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


1
Подивіться на це (Windows Phone, але повинна бути такою ж): stackoverflow.com/questions/9674122 / ...
JAC

6
@istepaniuk він не може спробувати, якщо він не знає. так що зупиніть такі запитання
Мустафа Екічі

1
Ви повинні мати можливість створити таймер і встановити його на закриття через встановлений проміжок часу
Стівен Еклі

2
Ви можете створити форму якMessageBox
spajce

2
@MustafaEkici, я запрошував ОП показати, що він спробував. Я припускаю, що він, мабуть, намагався і не вдався, перш ніж насправді запитувати SO. Ось чому ми з Рамхаундом проголосували за питання. Ви можете прочитати 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);
}

Ще одне оновлення

Я перевірив справу @ Jack за допомогою YesNoкнопок і виявив, що підхід з відправленням WM_CLOSEповідомлення взагалі не працює.
Я надаю виправлення в контексті окремої бібліотеки AutoclosingMessageBox . Ця бібліотека містить перероблений підхід і, я вважаю, може бути корисною комусь.
Він також доступний через пакет NuGet :

Install-Package AutoClosingMessageBox

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


Краще використовувати 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з ними).


Що таке версія .NET? Що це за TaskScheduler?
Кікенет,

1
@Kiquenet .NET 4.0 і новіших версій. Taskі TaskSchedulerз простору імен System.Threading.Tasksу mscorlib.dll, тому додаткові посилання на збірку не потрібні.
BSharp

Чудове рішення! Одне доповнення ... це добре працювало в програмі Winforms, ПІСЛЯ додавання BringToFront для нової форми, одразу після її створення. В іншому випадку діалогові вікна іноді відображаються позаду поточної активної форми, тобто не відображаються користувачеві. var w = нова форма () {Розмір = новий розмір (0, 0)}; w.BringToFront ();
Розробник63

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

А тепер іди мий руки!


11

Метод System.Windows.MessageBox.Show () має перевантаження, яке приймає вікно власника як перший параметр. Якщо ми створимо невидиме вікно власника, яке потім закриємо через певний час, закриється і його вікно повідомлення.

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

Все йде нормально. Але як закрити вікно, якщо потік інтерфейсу заблокований вікном повідомлення, а елементи керування інтерфейсом не можуть бути доступні з робочого потоку? Відповідь - надіславши повідомлення WM_CLOSE windows до дескриптора вікна власника:

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

Тип вікна для Windows Forms?
Кікенет,

Чому вам потрібно надіслати повідомлення у приховане батьківське вікно, щоб закрити його? Чи не можете ви просто викликати на ньому якийсь метод "Закрити" або розпоряджатися іншим способом?
Джордж Бірбіліс

Щоб відповісти на моє власне запитання, властивість OwnedWindows цього WPF Window, здається, відображає 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?
Кікенет

1
див. stackoverflow.com/questions/3376619/… і, можливо, також stackoverflow.com/questions/2411116/… про різницю між SendMessage та PostMessage
Джордж Бірбіліс

7

RogerB у CodeProject має одне з найрізноманітніших рішень для цієї відповіді, і він зробив це ще в '04, і це все ще бангін '

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

Тоді все, що вам потрібно зробити, це переключитися

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

до

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

І вам добре їхати.


2
Мені потрібно було швидке рішення, і це спрацювало чудово! Дякую, що поділились.
Марк Крам

1
Ви пропустили розширення .Show в ваших прикладах ... слід читати: DialogResult результат = MessageBoxEx.Show ( "Текст", "Назва", MessageBoxButtons.CHOICE, timer_ms)
EDD

2

Існує проект codeproject, доступний ТУТ, який забезпечує цю функціональність.

Слідуючи багатьом потокам на SO та інших платах, це неможливо зробити за допомогою звичайного MessageBox.

Редагувати:

У мене є така ідея, що це трохи так ...

Використовуйте таймер і запускайте, коли з’являється MessageBox. Якщо ваш MessageBox слухає лише кнопку OK (лише 1 можливість), використовуйте OnTick-Event для емуляції клавіші ESC і натисніть, SendKeys.Send("{ESC}");а потім зупиніть таймер.


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

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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.