Закройте 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
Прокручиваемый 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 или более поздняя версия.