65.9K
CodeProject 正在变化。 阅读更多。
Home

类似 Growl 的 WPF 通知

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (42投票s)

2012 年 11 月 26 日

CPOL

3分钟阅读

viewsIcon

147222

downloadIcon

10270

用于 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 - 初始版本。
© . All rights reserved.