WPF 右下角通知弹窗组件:支持自动关闭和倒计时
桌面应用经常需要"非阻塞"的通知提示——不像 MessageBox 那样必须点确定才能继续操作,而是在屏幕角落弹一个通知,过几秒自动消失。
本文分享一个右下角通知弹窗组件,带自动倒计时关闭,支持自定义按钮。
效果预览
- 从屏幕右下角弹出,带淡入/淡出动画
- 标题 + 内容 + 可选的确认/取消按钮
- 支持自动倒计时关闭(显示剩余秒数)
- 纯静态方法调用,一行代码弹出
MNotification.xaml
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| <Window x:Class="WpfApp.Component.MNotification" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="Manual" AllowsTransparency="True" Background="Transparent" SizeToContent="WidthAndHeight" MinWidth="300" MaxWidth="450" Topmost="True" ShowInTaskbar="False">
<Border CornerRadius="8" Background="#F5F5F5" BorderBrush="#CCCCCC" BorderThickness="1" Padding="20"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions>
<Button x:Name="CloseButton" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Top" Width="24" Height="24" Background="Transparent" BorderBrush="Transparent" Content="✕" FontSize="12" Foreground="#999999" Cursor="Hand" Click="OnCloseClick"/>
<TextBlock x:Name="TitleText" Grid.Row="0" Text="提示" FontSize="18" FontWeight="Medium" Foreground="#333333" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,30,0"/>
<TextBlock x:Name="MessageText" Grid.Row="1" Text="这是一条提示信息" FontSize="14" Foreground="#666666" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,20,0,20"/>
<StackPanel Grid.Row="2" x:Name="ButtonPanel" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Collapsed"> <Button x:Name="CancelButton" Content="取消" MinWidth="80" Height="32" Background="Transparent" BorderBrush="#CCCCCC" BorderThickness="1" Foreground="#666666" Margin="0,0,10,0" Click="OnCancelClick"/> <Button x:Name="ConfirmButton" Content="确定" MinWidth="80" Height="32" Background="#2196F3" BorderBrush="Transparent" Foreground="White" Cursor="Hand" Click="OnConfirmClick"/> </StackPanel> </Grid> </Border> </Window>
|
MNotification.xaml.cs
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| using System.Windows; using System.Windows.Media.Animation; using System.Windows.Threading;
namespace WpfApp.Component { public partial class MNotification : Window { #region 字段
private DispatcherTimer? _closeTimer; private int _countdownSeconds = 5;
#endregion
#region 构造
public MNotification() { InitializeComponent(); Loaded += OnLoaded; }
#endregion
#region 静态调用
public static void Show(string title, string message, int autoCloseSeconds = 5, string? confirmText = "确定", string? cancelText = null) { var notification = new MNotification(); notification.TitleText.Text = title; notification.MessageText.Text = message;
if (confirmText != null) { notification.ConfirmButton.Content = confirmText; notification.ConfirmButton.Visibility = Visibility.Visible; notification.ButtonPanel.Visibility = Visibility.Visible; } else { notification.ConfirmButton.Visibility = Visibility.Collapsed; }
if (cancelText != null) { notification.CancelButton.Content = cancelText; notification.CancelButton.Visibility = Visibility.Visible; notification.ButtonPanel.Visibility = Visibility.Visible; } else { notification.CancelButton.Visibility = Visibility.Collapsed; }
if (autoCloseSeconds > 0) { notification._countdownSeconds = autoCloseSeconds; notification.StartCountdown(); }
notification.Show(); }
#endregion
#region 窗口定位与动画
private void OnLoaded(object sender, RoutedEventArgs e) { var workingArea = SystemParameters.WorkArea; Left = workingArea.Right - ActualWidth - 20; Top = workingArea.Bottom - ActualHeight - 20;
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)); BeginAnimation(OpacityProperty, fadeIn); }
#endregion
#region 倒计时
private void StartCountdown() { _closeTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_closeTimer.Tick += (s, e) => { _countdownSeconds--;
if (_countdownSeconds <= 0) { _closeTimer!.Stop(); CloseWithAnimation(); } };
_closeTimer.Start(); }
private void CloseWithAnimation() { var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(200)); fadeOut.Completed += (s, e) => Close(); BeginAnimation(OpacityProperty, fadeOut); }
#endregion
#region 事件处理
private void OnCloseClick(object sender, RoutedEventArgs e) { _closeTimer?.Stop(); CloseWithAnimation(); }
private void OnCancelClick(object sender, RoutedEventArgs e) { _closeTimer?.Stop(); CloseWithAnimation(); }
private void OnConfirmClick(object sender, RoutedEventArgs e) { _closeTimer?.Stop(); CloseWithAnimation(); }
#endregion } }
|
使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| MNotification.Show("提示", "操作成功");
MNotification.Show("", "已复制到剪贴板", autoCloseSeconds: 3, confirmText: null);
MNotification.Show("余额不足", "请及时充值", confirmText: "去充值");
MNotification.Show("新版本可用", "检测到新版本,是否立即更新?", confirmText: "更新", cancelText: "稍后");
MNotification.Show("重要通知", "请仔细阅读此内容", autoCloseSeconds: 0);
|
设计要点
1. 右下角精准定位
在 Loaded 事件里通过 SystemParameters.WorkArea 获取屏幕工作区(排除任务栏),计算窗口位置到右下角偏移 20px:
1 2
| Left = workingArea.Right - ActualWidth - 20; Top = workingArea.Bottom - ActualHeight - 20;
|
必须在 Loaded 后定位,因为 SizeToContent="WidthAndHeight" 模式下窗口实际宽高在加载完成后才确定。
2. DispatcherTimer 倒计时
用 DispatcherTimer 每秒减一,到 0 时触发 CloseWithAnimation()。手动点关闭/确认/取消按钮时先 _closeTimer?.Stop() 停掉计时器,避免重复关闭。
3. 灵活的按钮配置
confirmText 和 cancelText 参数都接受 null:
| confirmText |
cancelText |
效果 |
"确定" |
null |
只显示确定按钮 |
"更新" |
"稍后" |
显示两个按钮 |
null |
null |
不显示按钮(纯通知) |
4. Topmost 置顶
Topmost="True" 确保通知始终在最前面,即使用户正在操作其他窗口也能看到。
5. 不显示在任务栏
ShowInTaskbar="False" 避免通知窗口在任务栏占位置,它只是一个临时提示。