WinForms 中实现自动关闭的 MessageBox

本文最后更新于 2023年12月22日 下午

在许多软件应用程序中,经常需要显示一段时间后自动关闭的消息框。这种功能可以为用户提供及时的反馈,同时又不会干扰用户的操作。本文将介绍一个使用 C# 编写的自动关闭消息框的代码示例,并详细解释其实现原理。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private async void ShowMessageBoxInThread(string message, int time = 1000, string title = "提示")
{
await Task.Run(() => AutoClosingMessageBox.Show(message, title, time));
}

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, MessageBoxButtons.OK, MessageBoxIcon.Information);
}

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

实现原理

让我们逐步解释上述代码的实现原理。

ShowMessageBoxInThread 方法

首先,我们有一个私有的异步方法ShowMessageBoxInThread,它接受三个参数:message表示要显示的消息内容,time表示消息框显示的时间(默认为1秒),title表示消息框的标题(默认为”提示”)。

1
2
3
4
private async void ShowMessageBoxInThread(string message, int time = 1000, string title = "提示")
{
await Task.Run(() => AutoClosingMessageBox.Show(message, title, time));
}

在方法内部,我们使用Task.Run创建一个异步任务,并调用AutoClosingMessageBox.Show方法。通过将消息内容、标题和显示时间传递给Show方法,我们将显示一个自动关闭的消息框。

AutoClosingMessageBox 类

AutoClosingMessageBox类是实现自动关闭消息框的核心部分。

首先,它包含了一个私有字段_timeoutTimer和一个表示消息框标题的字段_caption

1
2
3
4
5
6
public class AutoClosingMessageBox
{
System.Threading.Timer _timeoutTimer;
string _caption;
// ...
}

构造函数AutoClosingMessageBox接受消息内容、标题和超时时间作为参数。

1
2
3
4
5
6
7
8
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, MessageBoxButtons.OK, MessageBoxIcon.Information);
}

在构造函数内部,首先设置了一个计时器_timeoutTimer,它在指定的超时时间后触发回调函数OnTimerElapsed。计时器的工作是在超时后关闭消息框。

当初始化定时器时,我们使用 new System.Threading.Timer(OnTimerElapsed, null, timeout, System.Threading.Timeout.Infinite) 这段代码。它创建了一个新的 System.Threading.Timer 实例,并设置了相关参数:

  • OnTimerElapsed 是定时器触发时要执行的回调方法。
  • null 表示传递给回调方法的状态对象,这里我们不需要传递额外的状态信息。
  • timeout 表示定时器的超时时间,即经过多少毫秒后触发回调方法。
  • System.Threading.Timeout.Infinite 表示定时器只触发一次,并不会重复。

using 语句是一种资源管理语句,用于确保在使用完毕后正确释放资源。在这里,我们使用 using 语句来管理 _timeoutTimer 对象的生命周期。当执行到 using 语句的末尾时,会自动调用 _timeoutTimer.Dispose() 方法来释放定时器资源。

接下来,使用MessageBox.Show方法显示消息框,并传递消息内容、标题以及信息图标。

OnTimerElapsed 方法

OnTimerElapsed方法是计时器回调函数,它在超时时被调用。

1
2
3
4
5
6
7
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();
}

OnTimerElapsed方法中,首先通过FindWindow函数查找具有指定标题的消息框的句柄。如果找到了消息框的句柄,就使用SendMessage函数发送关闭消息给消息框,即通过向消息框发送WM_CLOSE消息来关闭它。最后,我们释放计时器资源。

DllImport 特性

代码中还使用了 DllImport 特性,用于声明FindWindowSendMessage方法,以便在 C# 代码中使用这些来自 user32.dll 的本机函数。

1
2
3
4
5
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);

这些特性允许我们直接调用 Windows API 函数,以实现与操作系统交互的功能。

  • const int WM_CLOSE = 0x0010; 定义了一个常量 WM_CLOSE,它代表了关闭窗口的消息代码。
  • [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] 是一个 DllImport 特性,用于指示在 user32.dll 库中查找并导入函数。SetLastError 参数设置为 true,以便在函数调用失败时记录错误状态。
  • static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 声明了一个名为 FindWindow 的本机函数,它在 user32.dll 中查找具有指定类名和窗口名的顶层窗口。它返回找到的窗口的句柄。
  • static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 声明了一个名为 SendMessage 的本机函数,它向指定的窗口发送指定的消息。hWnd 参数是窗口的句柄,Msg 参数是要发送的消息代码,wParamlParam 参数是消息的参数。

这些代码用于在 C# 中与 Windows API 进行交互。通过 FindWindow 函数找到指定标题的窗口句柄,并使用 SendMessage 函数向该窗口发送关闭消息。这样可以实现在定时器回调方法中自动关闭消息框的功能。

使用示例

通过使用以上的代码,我们可以在应用程序中使用ShowMessageBoxInThread方法来显示一个自动关闭的消息框。例如:

1
ShowMessageBoxInThread("操作已完成!", 2000, "成功");

以上代码将在一个新的线程中显示一个带有”成功”标题的消息框,显示内容为”操作已完成!”,并在2秒后自动关闭。

参考链接

Close a MessageBox after several seconds


WinForms 中实现自动关闭的 MessageBox
http://www.loquy.cn/posts/1883a8de.html
作者
loquy
发布于
2023年7月13日
更新于
2023年12月22日
许可协议