Click here to Skip to main content
Click here to Skip to main content

Growl Alike WPF Notifications

By , 8 Mar 2013
 

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)

About the Author

Ivan Leonenko
Software Developer (Senior) Enkata
Russian Federation Russian Federation
Member
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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionError: The resource "CloseButton" could not be resolved.memberaccessoryfreak11 Apr '13 - 8:18 
Hi!
 
I'm using this code in a C# UserControl, which is part of a larger project. I do not have an App.xaml. When I add this code
 
<UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="ButtonStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
 
to my UserControl.xaml file, I get the error, "The resource "Close Button" could not be resolved." This error prevents the notification from working properly (as in the code crashes on line 49 of GrowlNotifications.xaml.cs).
 
When I remove the above <UserControl.Resources> reference and comment out the "CloseButton" code from GrowlNotifications.xaml, the code works exactly as expected EXCEPT it only allows 4 notifications, no queuing.
 
Do you have any suggestions for how I can get at least the queuing to work for more than 4 notifications?
 
Thanks for any help! This is a *FAB* bit of code!
AnswerRe: Error: The resource "CloseButton" could not be resolved.memberIvan Leonenko17 Apr '13 - 7:05 
Hi!
 
you can delete ButtonStyle.xaml resource and references to it completely. Also delete a usage:
Style="{StaticResource CloseButton}"
 
In this case you will have all the same functionality, but the button will have default style.
You cannot affect functionality by deleting styles. I just checked it, it works well.
 
You mentioned the quantity of notifications, well in this example active notifications quantity is hardcoded in MAX_NOTIFICATIONS constant in GrowlNotifications.xaml.cs
GeneralMy vote of 5memberMartin H. Andersen2 Mar '13 - 3:18 
Just what I needed, You should make this a nuget packet
GeneralMy vote of 5memberVitorHugoGarcia27 Feb '13 - 6:26 
goody goody
GeneralMy vote of 5membersam.hill26 Feb '13 - 5:23 
Quite interesting.
I Obviously disagree with Mr. Thornik!
QuestionMy 5c about articlememberThornik25 Feb '13 - 23:27 
1. It has confusing title, having nothing related Growl.
2. Inventing new "tray notificator" is a bad idea, having billion of already written.
3. Your reasons to rewrite Growl is close to nothing - EVERY library is a dependency, your is not a cent better in this terms.
4. Growl is not only "show me notification" library, but a system wide service(bus) with standard protocol. This protocol can be used by any program, not only .NET
 
So this is a good university home work, but hardly replaces Growl.
AnswerRe: My 5c about articlememberIvan Leonenko25 Feb '13 - 23:37 
1. Title is already fixed.
2. This is not tray notifier. Nothing is invented, you just using WPF facilities to do the job, not referencing anything.
3. Here you are just not right. I'm not rewriting growl! I don't care about Growl. Regarding dependencies - for example this project doesn't load Forms library.
4. Yeah that's why it is called growl alike notifications, see people use it everywhere to describe the behavior:
Take a look on this http://zacstewart.com/2011/06/18/meow-a-growl-work-alike-for-jquery.html[^]
 
It was not supposed to replace Growl! Not so much people care about having this common bus for notifications. And for windows it will die, you can see Windows 8 notifications and its common bus. Also Growl project is not so popular to be a standard. I hope you'll understand what was the point of the article - the graphics and behavior not the component and common bus for messages.
 
Cool | :cool:
GeneralMy vote of 1memberThornik25 Feb '13 - 22:22 
Bicycle reinvention is mostly a bad idea.
GeneralRe: My vote of 1memberIvan Leonenko25 Feb '13 - 22:36 
There's nothing reinvented. It is just small amount of code. Am I supposed to used heavy components for such a simple functionality? Definitely no!
And it would be interesting to know what you mean by bicycle reinvention? If you're a programmer you should code not copy pasting everything you see.Cool | :cool:
QuestionInvalidOperationException after notification is closedmemberPCoffey18 Feb '13 - 5:03 
If you use the x button to close a window on the notification window you will get an InvalidOperationException next time that you try to show a notification.
 
The exception is in GrowlNotifiactions line number 31.
 

the ex message is 'Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed.'
 

Otherwise very cool idea.
AnswerRe: InvalidOperationException after notification is closedmemberIvan Leonenko18 Feb '13 - 7:27 
Yeah it was a subtle bug, after latest changes, of cousre I should Hide a window not close. I'll update the article.
 
Thank you! Wink | ;)
BugRemoveNotificationmemberChrDressler16 Feb '13 - 23:41 
Great job!
The collection-window does not close, if the last notify is closed. Sniff | :^)
I changed this:
if (buffer.Count <= 0)
    return;
Notifications.Add(buffer[0]);
buffer.RemoveAt(0);
to this:
if (buffer.Count > 0)
{
    Notifications.Add(buffer[0]);
    buffer.RemoveAt(0);
}
and it works fine. Wink | ;)
 
-christoph
GeneralRe: RemoveNotificationmemberIvan Leonenko17 Feb '13 - 2:51 
I can't check it right now, but are you sure this was causing such behavior?
 
These code blocks are equivalent.
GeneralRe: RemoveNotificationmemberChrDressler17 Feb '13 - 6:17 
Your code prevents closing the window, if _Buffer ist empty.
Here the complete method:
            if (Notifications.Contains(notification))
                Notifications.Remove(notification);
 
            if (_Buffer.Count > 0)
            {
                Notifications.Add(_Buffer[0]);
                _Buffer.RemoveAt(0);
            }          
 
            if (Notifications.Count < 1 && IsActive)
                Close();
Your sample app handles this issue right. May be, you call Close() at the end of your app.
 
Christoph
GeneralRe: RemoveNotificationmemberIvan Leonenko17 Feb '13 - 6:21 
I see, it was not last line and definitely there's a bug, I'll update article.
 
Thank you! Smile | :)
GeneralMy vote of 5memberCalvinWang25 Jan '13 - 20:22 
Great Job
QuestionClose All Notifications ImediatelymemberPeteUKinUSA7 Jan '13 - 19:19 
Is there a way to close all notifications immediately when one is clicked on, or any of the close buttons are clicked ?
 
Thanks
 
Pete
AnswerRe: Close All Notifications ImediatelymemberPeteUKinUSA7 Jan '13 - 19:23 
Nevermind. Notifications.Clear() did the trick.
 
Sorry for all the dumb questions.
QuestionExcellent this example .. ButmemberEnyelber Altube27 Dec '12 - 11:27 
Excellent this example .. But you will not in Visual Basic?
 

Thanks, would be very useful
GeneralMonitor the tail of a log filememberandre1234519 Dec '12 - 7:50 
I have added it to monitor the tail of a log file.
I get a "Growl Notification" for each new message.
It adds a nice touch! Thanks Smile | :)
QuestionActivation questionmemberJohn Schroedl18 Dec '12 - 10:36 
First off, this is excellent. Thanks for sharing this; you got my 5.
 
Question:
 
Do you know a way to show the Notification without having the transparent GrowlNotifications window becoming active? In Win32, I would have used ShowWindow(hwnd, SW_NOACTIVATE) but I don't see anything similar in WPF yet.
 
You can see the issue if you run the sample, tab to a button and press Spacebar. Focus and activation go to the Growl window. It would be good to avoid this if possible.
 
Nitpick (with a fix):
 
If you show the first Growl and then press Tab you see the dotted focus visual for the ItemsControl. This can be fixed with
FocusVisualStyle="{x:Null}"
in the ItemsControl attributes.
 
John
AnswerRe: Activation questionmemberJohn Schroedl18 Dec '12 - 10:41 
Sorry to reply to myself but I found my own answer which you may want to incorporate.
 
Add
ShowActivated="False"
to the Window in GrowlNotification.xaml. This will allow the Growl to show up without stealing focus from the main window.
 
John
GeneralRe: Activation questionmemberIvan Leonenko18 Dec '12 - 10:46 
Thanks for information and sharing! Thumbs Up | :thumbsup:
I'll update the code.
QuestionAdd Notification from Bottom Up / Closing NotificationmemberPeter A Burgess11 Dec '12 - 5:52 
Great work, this is a really handy little tool. 2 questions...
 
1 : Is it possible to set the notifications to run from bottom up ? I.e. notification #1 gets added like a toast popup, notification #2 gets added above that rather than below it ?
2 : How do I manually close a notification ? Right now I've got an event which does "stuff" when someone clicks on the notification window. Once they've clicked on it I'd like to be able to close it immediately.
 
Big fat disclaimer : I'm not a programmer so I may well be asking some very stupid/easy questions.
 
Thanks
 
Pete
AnswerRe: Add Notification from Bottom Up / Closing NotificationmemberIvan Leonenko11 Dec '12 - 11:14 
Thank you,
 
1. Yes it is possible, same concept can be used as in this article, but different containers and animations should be used. May be I publish toast like notifications later.
 
2. In code I have EventTrigger for Button.Click event of "CloseButton", which sets opacity to 0 and this triggers collapsing animation. To close on notification click you can set this trigger to Border.MouseLeftButtonUp or Border.MouseLeftButtonDown of "border" element.
GeneralRe: Add Notification from Bottom Up / Closing NotificationmemberPeteUKinUSA13 Dec '12 - 4:02 
Perfect, thanks. Time to educate myself a little about XAML I think !
GeneralRe: Add Notification from Bottom Up / Closing NotificationmemberPeteUKinUSA4 Jan '13 - 11:29 
Another quick question...
 
I've added the border click as above. If there's more than one notification on the screen is it possible to tell which one was clicked (by ID or whatever) ?
GeneralRe: Add Notification from Bottom Up / Closing NotificationmemberPeteUKinUSA7 Jan '13 - 19:18 
Never mind. Figured it out.
QuestionWhere is the 'Growl'memberFatCatProgrammer10 Dec '12 - 3:18 
I thought you integrated Growl into this application. Right now I see that I have to add the nofitications. Can you integrate Growl?
Relativity

AnswerRe: Where is the 'Growl'memberIvan Leonenko10 Dec '12 - 3:58 
This article is about Growl alike notifications from UI perspective. It is not supposed to be integrated with Growl for Windows. May be I should rename an article if it looks confusing.
 
If you want Growl for Windows you can use libraries from them http://www.growlforwindows.com/gfw/[^], for WPF too. I searched the web and found libraries for WPF (.NET Framework 4), so anyone could integrate Growl without much pain.
 
Also if Growl was a part of the OS I wouldn't mind an integration.
GeneralRe: Where is the 'Growl'memberFatCatProgrammer10 Dec '12 - 13:51 
Where are the .Net libraries? Thanks
Relativity

GeneralRe: Where is the 'Growl'memberIvan Leonenko10 Dec '12 - 21:19 
Here's Growl for Windows libs and documentation: http://www.growlforwindows.com/gfw/developers.aspx[^]
Also there's a project with source code available that is a display for Growl for Windows based upon WPF. http://softwarebakery.com/frozencow/translucentdark.html[^]
GeneralRe: Where is the 'Growl'memberThornik25 Feb '13 - 22:26 
Article renaming is a good idea: people are coming to see at real Growl library usage, but see custom made "tray notifications".
GeneralRe: Where is the 'Growl'memberIvan Leonenko25 Feb '13 - 22:38 
There's nothing special about growl lib usage I said that in the article, I will rename article I see people confused. Sorry for this.
GeneralMy vote of 5memberVladimir Kocheryzhkin4 Dec '12 - 0:44 
"Nice" Smile | :) Take a look at http://www.hardcodet.net/projects/wpf-notifyicon usually tray and notification come together.
GeneralRe: My vote of 5memberIvan Leonenko4 Dec '12 - 1:13 
Nice to see you here Smile | :)
 
Thank you, actually I know about that great project, and I've used it recently.
 
But what was needed to achieve is more like this functionality: http://zacstewart.com/2011/06/18/meow-a-growl-work-alike-for-jquery.html[^]
GeneralMy vote of 5memberMichał Zalewski4 Dec '12 - 0:39 
Great work!
GeneralRe: My vote of 5memberIvan Leonenko4 Dec '12 - 1:14 
Thank you.
GeneralMy vote of 5memberRabinDl26 Nov '12 - 7:18 
Neat and simple.
GeneralMy vote of 5member GeekBond 26 Nov '12 - 6:54 
Nice work, you could make it easy and create a dll for it though. Thanks.
GeneralRe: My vote of 5memberIvan Leonenko26 Nov '12 - 8:04 
I'm glad you like it.
I'll try to update it when I have some time.
GeneralRe: My vote of 5member GeekBond 26 Nov '12 - 22:47 
Smile | :) Alright.
The first step in the acquisition of wisdom is SILENCE, the second is LISTENING, the third MEMORY, the forth, PRACTICE and the fifth is TEACHING others!

GeneralMy vote of 5memberdyma26 Nov '12 - 5:20 
My vote of 5

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

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