Закрийте 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.