Click here to Skip to main content
12,622,527 members (30,976 online)
Click here to Skip to main content
Add your own
alternative version

Stats

66.5K views
8.6K downloads
117 bookmarked
Posted

Growl Alike WPF Notifications

, 8 Mar 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Lightweight growl alike notifications for WPF project.

Introduction

Recently I needed to add Growl like notifications to a WPF project. Just a notification system that only this program will be using. I Googled a bit and found Growl for Windows, it's fine but seemed too much for just this functionality (it has an independent component and common message bus, which I don't need). Also you're adding new dependencies (Growl components, Forms, etc.) and new libraries to the project, but you can just have a few classes to have the same behavior of notifications to handle this.

Functionality

This implementation provides the following functionality:

  • Notifications could be added and will be placed on the screen
  • Specific notifications can be deleted
  • Notification fades in when added (2 sec)
  • Notification stays for 6 seconds after fade in and then it will fade out (2 sec) and collapse
  • If user places mouse pointer above a notification it becomes fully visible and doesn't fade out
  • There's a maximum number of notifications, if there's more than the max number, they are placed in a queue and will be shown when the place is available
  • The looks of the notification is defined by a DataTemplate
  • Notification class is used to store data, which is bound to the DataTemplate

Using the code

GrowlNotifications class

The GrowlNotifiactions class contains logic to add or remove notifications. It is very simple. First of all a DataContext is set on creation to Notifications, which is an ObservableCollection<Notification>. This collection is the source for the ItemControl in XAML.

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

When a new notification is added a unique ID is assigned. This is needed when we want to remove an element from a collection (when it is collapsed in this case), see NotificationWindowSizeChanged. Then if there's space, the ItemsControl element notification is added directly to the Notifications collection, otherwise it will be stored in a buffer variable. The final step is to show the window.

When a notification is removed, the buffer is checked and if there's something in it, it's pushed to the Notifications collection. If there's nothing to show, the window is closed.

Notification class

The notification class implements INotifyPropertyChanged, so you can bind to its values in DataTemplate. You can customize it to show whatever you like.

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

All the animations are in XAML and it is very handy. It is easy to go through them, there're only four triggers. Also you can see the DataTemplate which can be customized however you like to show what's in the Notification class.

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

Sample application

The sample application has a window that adds different notifications to the notification window.

History

  • 02/18/2013 - Bug fix: notification window should be hidden, not closed. Closing should be external or on some event.
  • 02/17/2013 - Bug fix: Collection window stays open if the last notification is closed. Thanks ChrDressler (see comments).
  • 12/19/2012 - ItemsControl focusable style is set to null. Notifications window is not activated. Thanks John Schroedl (see comments).
  • 12/10/2012 - Executable added to downloads.
  • 11/26/2012 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ivan Leonenko
Software Developer (Senior) Enkata
Russian Federation Russian Federation
More than 7 years of software design and development.

Specialties: C#, .NET 2 3.5 4, WPF (MVVM), Silverlight, ASP.NET, WCF, Javascript, Flash, ActionScript, SQL, WinAPI, Powershell

My blog http://ileonenko.wordpress.com

You may also be interested in...

Pro

Comments and Discussions

 
QuestionHelp Me! Pin
programernguyenvantoan1-Aug-16 0:19
memberprogramernguyenvantoan1-Aug-16 0:19 
QuestionHelp me !! about value from notification Pin
Marck Moreno5-Oct-15 6:28
memberMarck Moreno5-Oct-15 6:28 
QuestionMy vote is 5. I have one question, to change the duration dynamically. Pin
MohanKumar Bangalore3-Oct-15 2:46
memberMohanKumar Bangalore3-Oct-15 2:46 
AnswerRe: My vote is 5. I have one question, to change the duration dynamically. Pin
James JM18-Jul-16 3:41
memberJames JM18-Jul-16 3:41 
QuestionFew Questions... Pin
Andy Cartwright30-Aug-15 12:11
memberAndy Cartwright30-Aug-15 12:11 
QuestionTypo Pin
Member 1194261428-Aug-15 9:49
memberMember 1194261428-Aug-15 9:49 
Questiongrate job , can you help me to convert this in mvvm? Pin
Member 1189824810-Aug-15 1:24
memberMember 1189824810-Aug-15 1:24 
QuestionAny way to change how long the notifications appear for ? Pin
Barry ONeill14-May-15 10:25
memberBarry ONeill14-May-15 10:25 
AnswerRe: Any way to change how long the notifications appear for ? Pin
Barry ONeill19-May-15 7:28
memberBarry ONeill19-May-15 7:28 
QuestionAwesome Control !...my boss is asking if there is a way to pin the notifications though ? :-) Pin
Barry ONeill23-Apr-15 11:01
memberBarry ONeill23-Apr-15 11:01 
AnswerRe: Awesome Control !...my boss is asking if there is a way to pin the notifications though ? :-) Pin
Barry ONeill23-Apr-15 11:43
memberBarry ONeill23-Apr-15 11:43 
QuestionA very good Job Pin
sel200519-Aug-14 7:18
membersel200519-Aug-14 7:18 
AnswerRe: A very good Job Pin
Ivan Leonenko1-Sep-14 16:08
memberIvan Leonenko1-Sep-14 16:08 
QuestionHow to import it in a project and use it? Pin
Member 1083727822-Jun-14 14:39
memberMember 1083727822-Jun-14 14:39 
QuestionHow to change animation Pin
Garnik1989_198918-Jun-14 21:29
memberGarnik1989_198918-Jun-14 21:29 
QuestionVote of 5 and a question Pin
Xhelixousinity5-Aug-13 22:12
memberXhelixousinity5-Aug-13 22:12 
AnswerRe: Vote of 5 and a question Pin
Ivan Leonenko16-Aug-13 4:27
memberIvan Leonenko16-Aug-13 4:27 
GeneralRe: Vote of 5 and a question Pin
Xhelixousinity16-Aug-13 5:03
memberXhelixousinity16-Aug-13 5:03 
GeneralRe: Vote of 5 and a question Pin
Dirkster996-Nov-13 8:16
memberDirkster996-Nov-13 8:16 
AnswerRe: Vote of 5 and a question Pin
Ivan Leonenko6-Nov-13 13:29
memberIvan Leonenko6-Nov-13 13:29 
GeneralRe: Vote of 5 and a question Pin
Dirkster9911-Nov-13 14:55
memberDirkster9911-Nov-13 14:55 
QuestionError: The resource "CloseButton" could not be resolved. Pin
accessoryfreak11-Apr-13 9:18
memberaccessoryfreak11-Apr-13 9:18 
AnswerRe: Error: The resource "CloseButton" could not be resolved. Pin
Ivan Leonenko17-Apr-13 8:05
memberIvan Leonenko17-Apr-13 8:05 
GeneralMy vote of 5 Pin
Martin H. Andersen2-Mar-13 4:18
memberMartin H. Andersen2-Mar-13 4:18 
GeneralMy vote of 5 Pin
VitorHugoGarcia27-Feb-13 7:26
memberVitorHugoGarcia27-Feb-13 7:26 
GeneralMy vote of 5 Pin
sam.hill26-Feb-13 6:23
membersam.hill26-Feb-13 6:23 
QuestionMy 5c about article Pin
Thornik26-Feb-13 0:27
memberThornik26-Feb-13 0:27 
AnswerRe: My 5c about article Pin
Ivan Leonenko26-Feb-13 0:37
memberIvan Leonenko26-Feb-13 0:37 
GeneralMy vote of 1 Pin
Thornik25-Feb-13 23:22
memberThornik25-Feb-13 23:22 
GeneralRe: My vote of 1 Pin
Ivan Leonenko25-Feb-13 23:36
memberIvan Leonenko25-Feb-13 23:36 
QuestionInvalidOperationException after notification is closed Pin
PCoffey18-Feb-13 6:03
memberPCoffey18-Feb-13 6:03 
AnswerRe: InvalidOperationException after notification is closed Pin
Ivan Leonenko18-Feb-13 8:27
memberIvan Leonenko18-Feb-13 8:27 
BugRemoveNotification Pin
ChrDressler17-Feb-13 0:41
memberChrDressler17-Feb-13 0:41 
GeneralRe: RemoveNotification Pin
Ivan Leonenko17-Feb-13 3:51
memberIvan Leonenko17-Feb-13 3:51 
GeneralRe: RemoveNotification Pin
ChrDressler17-Feb-13 7:17
memberChrDressler17-Feb-13 7:17 
GeneralRe: RemoveNotification Pin
Ivan Leonenko17-Feb-13 7:21
memberIvan Leonenko17-Feb-13 7:21 
GeneralMy vote of 5 Pin
CalvinWang25-Jan-13 21:22
memberCalvinWang25-Jan-13 21:22 
QuestionClose All Notifications Imediately Pin
PeteUKinUSA7-Jan-13 20:19
memberPeteUKinUSA7-Jan-13 20:19 
AnswerRe: Close All Notifications Imediately Pin
PeteUKinUSA7-Jan-13 20:23
memberPeteUKinUSA7-Jan-13 20:23 
QuestionExcellent this example .. But Pin
Enyelber Altube27-Dec-12 12:27
memberEnyelber Altube27-Dec-12 12:27 
GeneralMonitor the tail of a log file Pin
andre1234519-Dec-12 8:50
memberandre1234519-Dec-12 8:50 
QuestionActivation question Pin
John Schroedl18-Dec-12 11:36
memberJohn Schroedl18-Dec-12 11:36 
AnswerRe: Activation question Pin
John Schroedl18-Dec-12 11:41
memberJohn Schroedl18-Dec-12 11:41 
GeneralRe: Activation question Pin
Ivan Leonenko18-Dec-12 11:46
memberIvan Leonenko18-Dec-12 11:46 
QuestionAdd Notification from Bottom Up / Closing Notification Pin
Peter A Burgess11-Dec-12 6:52
memberPeter A Burgess11-Dec-12 6:52 
AnswerRe: Add Notification from Bottom Up / Closing Notification Pin
Ivan Leonenko11-Dec-12 12:14
memberIvan Leonenko11-Dec-12 12:14 
GeneralRe: Add Notification from Bottom Up / Closing Notification Pin
PeteUKinUSA13-Dec-12 5:02
memberPeteUKinUSA13-Dec-12 5:02 
GeneralRe: Add Notification from Bottom Up / Closing Notification Pin
PeteUKinUSA4-Jan-13 12:29
memberPeteUKinUSA4-Jan-13 12:29 
GeneralRe: Add Notification from Bottom Up / Closing Notification Pin
PeteUKinUSA7-Jan-13 20:18
memberPeteUKinUSA7-Jan-13 20:18 
QuestionWhere is the 'Growl' Pin
FatCatProgrammer10-Dec-12 4:18
memberFatCatProgrammer10-Dec-12 4:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161128.1 | Last Updated 8 Mar 2013
Article Copyright 2012 by Ivan Leonenko
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid