几秒钟后关闭MessageBox

100
我有一个Windows Forms应用程序VS2010 C#,其中我显示一个MessageBox来展示消息。
我有一个确定按钮,但如果他们离开,我希望在5秒钟后超时并关闭消息框,自动关闭消息框。
有自定义的MessageBox(从Form继承)或其他报告器Forms,但不一定需要是一个Form。
有关此问题的任何建议或示例?
更新:
对于WPF
在C#中自动关闭消息框 自定义MessageBox(使用Form继承)
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#中实现可滚动的消息框

异常报告器
https://stackoverflow.com/questions/49224/good-crash-reporting-library-in-c-sharp

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

解决方案:

也许我认为以下答案是一个很好的解决方案,不需要使用表单。

https://dev59.com/IGUq5IYBdhLWcg3waPtC#14522902
https://dev59.com/IGUq5IYBdhLWcg3waPtC#14522952


1
看一下这个(Windows Phone,但应该是一样的):http://stackoverflow.com/questions/9674122/how-to-make-a-messagebox-disappear-after-3-seconds - jAC
6
他如果不知道就无法尝试。所以停止那种问题。 - Mustafa Ekici
2
你可以将表单创建为“MessageBox”。 - spajce
1
@Kiquenet - 我必须给这个问题点个踩,因为你甚至没有展示出你已经尝试过什么。 - Security Hound
2
@MustafaEkici,我邀请OP展示他已经尝试过什么。我认为他在向SO提问之前一定已经尝试过并失败了。这就是为什么Ramhound和我会对这个问题进行投票否决的原因。你可以阅读http://meta.stackexchange.com/questions/122986/is-it-ok-to-leave-what-have-you-tried-comments - istepaniuk
显示剩余2条评论
15个回答

138

请尝试以下方法:

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

  • 新增Show(IWin32Owner) API以支持最常见的场景(在#1 的上下文中);
  • 新增AutoClosingMessageBox.Factory() API,可提供对消息框显示的完全控制;

版本说明(v1.0.0.3):

  • 新增倒计时功能(在#4 的上下文中);
  • NET.6 迁移;

我看到你将lpClassName传递为null,难道MessageBox窗口没有特殊的类名吗?如果有的话,可以使用它来减小关闭其他具有相同标题的窗口的机会。 - George Birbilis
1
@GeorgeBirbilis 谢谢,这样就有意义了... 在这种情况下,您可以使用#32770值作为类名。 - DmitryG
糟糕,我的构造函数出了错。看起来我复制/粘贴了一个修改过的版本,其中它是公共的。但是,正如您所说,计时器确实有泄漏的可能性...事实上,我在调试时刚刚看到它泄漏了。我在OnTimerElapsed中设置了断点,然后使用5秒超时调用了AutoClosingMessageBox.Show()。尽可能快地按下“确定”按钮关闭对话框。但是5秒后它仍然触发了我的断点。 - soapergem
2
如果按钮是 System.Windows.Forms.MessageBoxButtons.YesNo,那么它对我不起作用。 - Jack
1
@Jack 很抱歉回复晚了,我已经检查了带有“YesNo”按钮的情况 - 你是完全正确的 - 它不起作用。我将在上下文中提供修复程序AutoclosingMessageBox库。其中包含重新设计的方法,我相信会很有用。谢谢! - DmitryG
显示剩余10条评论

46
一个适用于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()将确保,假设上面的示例代码在UI线程或用户创建的线程上执行。如果代码在来自线程池(例如计时器回调)或任务池(例如使用TaskFactory.StartNew或默认参数运行的Task.Run创建的任务)的线程上执行,则示例将无法正常工作。


什么是.NET版本?它的TaskScheduler是什么? - Kiquenet
1
@Kiquenet,.NET 4.0及更高版本。TaskTaskScheduler来自于mscorlib.dll中的命名空间System.Threading.Tasks,因此不需要其他程序集引用。 - BSharp
2
很棒的解决方案!还有一个补充...在创建新窗体后,立即添加BringToFront以便在Winforms应用程序中正常工作。否则,对话框有时会显示在当前活动窗体后面,即对用户不可见。 var w = new Form() { Size = new Size(0, 0) }; w.BringToFront(); - Developer63
@Developer63 我无法重现你的经历。即使在 MessageBox.Show() 之前立即调用 w.SentToBack(),对话框仍然显示在主窗体的顶部。在 .NET 4.5 和 4.7.1 上进行了测试。 - BSharp
@KwentRell 你是正确的。在.NET 4.0中,Task还没有Delay - BSharp
显示剩余2条评论

19

应用程序激活!

如果您不介意混淆您的引用,可以包含Microsoft.Visualbasic并使用这种非常简短的方法。

显示消息框

    (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

你可以尝试这个方法:

[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
请参考以下两个链接,了解SendMessage和PostMessage之间的区别以及它们与MV中主线程同步的等效性:https://dev59.com/c3A75IYBdhLWcg3wSm-4https://dev59.com/iUzSa4cB1Zd3GeqPm3Um - George Birbilis
1
在某些情况下,“timer”在经过时间间隔事件之前被垃圾回收,因此可以将其转换为类成员。 - datchung

11

System.Windows.MessageBox.Show()方法有一个重载,它将owner Window作为第一个参数。如果我们创建一个不可见的owner Window,然后在指定时间后关闭它,那么它的子消息框也会关闭。

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

目前为止,一切都很好。但是如果UI线程被消息框阻塞并且无法从工作线程访问UI控件,我们如何关闭窗口呢?答案是 - 通过向所有者窗口句柄发送一个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);
}

窗口类型是用于Windows Forms的吗? - Kiquenet
为什么需要向隐藏的父窗口发送消息来关闭它?难道你不能直接调用一些“Close”方法或以其他方式处理它吗? - George Birbilis
为了回答自己的问题,那个 WPF 窗口的 OwnedWindows 属性似乎显示 0 个窗口,并且 Close 方法无法关闭消息框子窗口。 - George Birbilis
2
绝妙的解决方案。在 System.WindowsSystem.Windows.Forms 中存在一些命名重叠,这让我花了一些时间来弄清楚。您将需要以下内容:SystemSystem.Runtime.InteropServicesSystem.Threading.TasksSystem.WindowsSystem.Windows.InteropSystem.Windows.Media - m3tikn0b

10

RogerB 在 CodeProject 上提供了一个最棒的解决方案,他在 2004 年就已经完成了,并且至今仍然很流行。

基本上,你需要 前往他的项目并下载 CS 文件。如果链接失效了,我在这里备份了一个 gist。将 CS 文件添加到你的项目中,或者如果你更喜欢这样做,可以将代码复制/粘贴到其他地方。

然后,你只需要切换

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

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

然后你就可以开始了。


2
你的例子中缺少了 .Show 扩展名... 应该这样写: DialogResult result = MessageBoxEx.Show("文本","标题", MessageBoxButtons.CHOICE, 计时器毫秒数) - Edd

7

我知道这个问题已经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);

工作要更聪明,而不是更努力。


2
我来到这里寻找这个解决方案,因为我知道vbscript总是允许这样做的(在那里称为弹出窗口),所以一定有一个基础的本地函数。有点困惑为什么这不是最佳答案之一。甚至可以使用WinForms MessageBoxIcon等枚举类型作为uiFlags值的计算基础。 - Syberdoor
@Syberdoor 谢谢,可能是因为我发帖太晚了哈哈。 - Mecanik
2
这个解决方案对我来说立即生效,并且在一个类中完成。我删除了对_NativeFunctions_的引用,因为它全部保留在本地。这似乎比其他答案更容易,因为我不需要下载任何其他软件,而且是可靠的。 - Joshua McVey

2

有一个可用的codeproject项目HERE提供了这个功能。

在SO和其他论坛上跟随许多线程,这不能通过普通的MessageBox完成。

编辑:

我有一个想法,有点嗯。。。

使用计时器并在MessageBox出现时启动它。 如果您的MessageBox只监听OK按钮(仅有1种可能性),则使用OnTick事件模拟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);
}

2

I did it like this

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接