类似 Growl 的 WPF 通知






4.96/5 (42投票s)
用于 WPF 项目的轻量级类似 Growl 的通知。
引言
最近我需要在 WPF 项目中添加类似 Growl 的通知。只是一个仅供此程序使用的通知系统。 我在 Google 上搜索了一下,找到了 Growl for Windows,它很好,但对于这个功能来说似乎太多了(它有一个独立的组件和公共消息总线,我不需要)。 此外,您正在向项目中添加新的依赖项(Growl 组件、Forms 等)和新的库,但您可以只使用几个类来拥有相同的通知行为来处理此问题。
功能
此实现提供以下功能
- 可以添加通知并将其放置在屏幕上
- 可以删除特定的通知
- 添加时通知淡入(2 秒)
- 通知在淡入后停留 6 秒,然后淡出(2 秒)并折叠
- 如果用户将鼠标指针放在通知上方,则该通知将完全可见并且不会淡出
- 通知数量有上限,如果超过最大数量,则将它们放入队列中,并在有空位时显示
- 通知的外观由 DataTemplate 定义
- Notification 类用于存储数据,该数据绑定到 DataTemplate
使用代码
GrowlNotifications 类
GrowlNotifiactions
类包含添加或删除通知的逻辑。 它非常简单。 首先,在创建时将 DataContext 设置为 Notifications,这是一个 ObservableCollection<Notification>
。 此集合是 XAML 中 ItemControl
的来源。
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WPFGrowlNotification
{
public partial class GrowlNotifiactions
{
private const byte MAX_NOTIFICATIONS = 4;
private int count;
public Notifications Notifications = new Notifications();
private readonly Notifications buffer = new Notifications();
public GrowlNotifiactions()
{
InitializeComponent();
NotificationsControl.DataContext = Notifications;
}
public void AddNotification(Notification notification)
{
notification.Id = count++;
if (Notifications.Count + 1 > MAX_NOTIFICATIONS)
buffer.Add(notification);
else
Notifications.Add(notification);
//Show window if there're notifications
if (Notifications.Count > 0 && !IsActive)
Show();
}
public void RemoveNotification(Notification notification)
{
if (Notifications.Contains(notification))
Notifications.Remove(notification);
if (buffer.Count > 0)
{
Notifications.Add(buffer[0]);
buffer.RemoveAt(0);
}
//Close window if there's nothing to show
if (Notifications.Count < 1)
Hide();
}
private void NotificationWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Height != 0.0)
return;
var element = sender as Grid;
RemoveNotification(Notifications.First(
n => n.Id == Int32.Parse(element.Tag.ToString())));
}
}
}
添加新通知时,会分配一个唯一的 ID。 当我们要从集合中删除一个元素时(在这种情况下它会折叠),这是必需的,请参阅 NotificationWindowSizeChanged
。 然后,如果有空间,则 ItemsControl
元素通知直接添加到 Notifications
集合中,否则将存储在缓冲区变量中。 最后一步是显示窗口。
删除通知时,会检查缓冲区,如果其中有内容,则会将其推送到 Notifications
集合中。 如果没有要显示的内容,则关闭窗口。
Notification 类
Notification 类实现了 INotifyPropertyChanged
,因此您可以在 DataTemplate 中绑定到它的值。 您可以自定义它来显示您喜欢的任何内容。
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WPFGrowlNotification
{
public class Notification : INotifyPropertyChanged
{
private string message;
public string Message
{
get { return message; }
set
{
if (message == value) return;
message = value;
OnPropertyChanged("Message");
}
}
private int id;
public int Id
{
get { return id; }
set
{
if (id == value) return;
id = value;
OnPropertyChanged("Id");
}
}
private string imageUrl;
public string ImageUrl
{
get { return imageUrl; }
set
{
if (imageUrl == value) return;
imageUrl = value;
OnPropertyChanged("ImageUrl");
}
}
private string title;
public string Title
{
get { return title; }
set
{
if (title == value) return;
title = value;
OnPropertyChanged("Title");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Notifications : ObservableCollection<Notification> { }
}
XAML
所有的动画都在 XAML 中,这非常方便。 很容易理解它们,只有四个触发器。 此外,您还可以看到 DataTemplate
,您可以根据自己的喜好自定义它以显示 Notification
类中的内容。
<Window x:Class="WPFGrowlNotification.GrowlNotifiactions"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Model="clr-namespace:WPFGrowlNotification"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
Title="GrowlNotifiactions" Height="530" Width="300" ShowActivated="False"
AllowsTransparency="True" WindowStyle="None" ShowInTaskbar="False"
Background="Transparent" Topmost="True" UseLayoutRounding="True">
<Window.Resources>
<Storyboard x:Key="CollapseStoryboard">
<DoubleAnimation From="100" To="0" Storyboard.TargetProperty="Height" Duration="0:0:1"/>
</Storyboard>
<DataTemplate x:Key="MessageTemplate" DataType="Model:Notification">
<Grid x:Name="NotificationWindow" Tag="{Binding Path=Id}"
Background="Transparent" SizeChanged="NotificationWindowSizeChanged">
<Border Name="border" Background="#2a3345"
BorderThickness="0" CornerRadius="10" Margin="10">
<Border.Effect>
<DropShadowEffect ShadowDepth="0" Opacity="0.8" BlurRadius="10"/>
</Border.Effect>
<Grid Height="100" Width="280" Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Grid.RowSpan="2" Source="{Binding Path=ImageUrl}" Margin="4" Width="80"></Image>
<TextBlock Grid.Column="1" Text="{Binding Path=Title}"
TextOptions.TextRenderingMode="ClearType"
TextOptions.TextFormattingMode="Display" Foreground="White"
FontFamily="Arial" FontSize="14" FontWeight="Bold"
VerticalAlignment="Center" Margin="2,4,4,2"
TextWrapping="Wrap" TextTrimming="CharacterEllipsis" />
<Button x:Name="CloseButton" Grid.Column="1" Width="16" Height="16"
HorizontalAlignment="Right" Margin="0,0,12,0" Style="{StaticResource CloseButton}" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Message}"
TextOptions.TextRenderingMode="ClearType"
TextOptions.TextFormattingMode="Display" Foreground="White"
FontFamily="Arial" VerticalAlignment="Center"
Margin="2,2,4,4" TextWrapping="Wrap" TextTrimming="CharacterEllipsis"/>
</Grid>
</Border>
</Grid>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="Window.Loaded" SourceName="NotificationWindow">
<BeginStoryboard x:Name="FadeInStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NotificationWindow"
From="0.01" To="1" Storyboard.TargetProperty="Opacity" Duration="0:0:2"/>
<DoubleAnimation Storyboard.TargetName="NotificationWindow"
From="1" To="0" Storyboard.TargetProperty="Opacity"
Duration="0:0:2" BeginTime="0:0:6"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<SeekStoryboard Offset="0:0:3" BeginStoryboardName="FadeInStoryBoard" />
<PauseStoryboard BeginStoryboardName="FadeInStoryBoard" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<SeekStoryboard Offset="0:0:3" BeginStoryboardName="FadeInStoryBoard" />
<ResumeStoryboard BeginStoryboardName="FadeInStoryBoard"></ResumeStoryboard>
</Trigger.ExitActions>
</Trigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="CloseButton">
<BeginStoryboard>
<Storyboard >
<DoubleAnimation Storyboard.TargetName="NotificationWindow"
From="1" To="0" Storyboard.TargetProperty="(Grid.Opacity)" Duration="0:0:0"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger SourceName="NotificationWindow" Property="Opacity" Value="0">
<Setter TargetName="NotificationWindow" Property="Visibility" Value="Hidden"></Setter>
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource CollapseStoryboard}"/>
</Trigger.EnterActions>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<ItemsControl x:Name="NotificationsControl" FocusVisualStyle="{x:Null}"
d:DataContext="{d:DesignData Source=DesignTimeNotificationData.xaml}"
ItemsSource="{Binding .}" ItemTemplate="{StaticResource MessageTemplate}" />
</Window>
示例应用程序
示例应用程序有一个窗口,该窗口将不同的通知添加到通知窗口。
历史
- 02/18/2013 - Bug 修复:通知窗口应该被隐藏,而不是关闭。 关闭应该在外部或某些事件上完成。
- 02/17/2013 - Bug 修复:如果最后一个通知关闭,则集合窗口保持打开状态。 感谢 ChrDressler (见评论)。
- 12/19/2012 -
ItemsControl
的 focusable 样式设置为 null。 通知窗口未激活。 感谢 John Schroedl(见评论)。 - 12/10/2012 - 可执行文件已添加到下载中。
- 11/26/2012 - 初始版本。