Stacking of Pop-Up Notification in WPF

tarun06

Tarun Kumar

Posted on May 21, 2023

Stacking of Pop-Up Notification in WPF

Introduction

The pop-up notification (or toast, desktop notification, notification bubble, or simply notification) is a graphical control element that communicates certain events to the user without forcing them to react to this notification immediately, unlike conventional pop-up windows. Desktop notifications usually disappear automatically after a short amount of time with or without any user intervention.

As a dotnet developer, you may have found yourself in need of displaying one or many such toast notifications in your application to communicate to user or make user aware in many cases not limited to
1) Success/Fail response to an action.
2) Internet connected/disconnected.
3) Battery about to die etc.

In this blog post, I will show you how to create a stacking of simple pop up notification and these notification will disappear automatically after a short time in WPF.

Project setup

To get started, we'll create a separate WPF user control library for our Custom Notification Controls. This will allow us to reuse the control in different projects without having to write the same code over and over again.
Here are the steps to create a WPF user control library in Visual Studio:

  1. Open Visual Studio and create a new project.
  2. In the New Project dialog box, select WPF User Control Library and click Next.
  3. Give your project a name ex."NotificationControls" and click Next.
  4. Select the dotnet version and click Create.

Designing the Notification Controls

To start with, lets create a simple usercontrol and named it NotificationView and add below code.

In the XAML file, we have 'StackPanel' to hold an Image to show icons for kinds (Success, Information, Warning etc.) and a Textblock to hold message.

  • NotificationView.xaml
<UserControl x:Class="NotificationControls.NotificationView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:NotificationControls"
             xmlns:draw="clr-namespace:System.Drawing;assembly=System.Drawing.Common"
             mc:Ignorable="d" 
             x:Name="notificationView">
    <UserControl.Resources>
        <local:IconToImageSourceConverter x:Key="IconToImageSourceConverter"/>
    </UserControl.Resources>
    <StackPanel Orientation="Horizontal">
        <Image Source="{Binding ElementName=notificationView, Path=IconKind, Converter={StaticResource IconToImageSourceConverter}, Mode=OneWay}" Width="25" Height="25"/>
        <TextBlock Margin="2 0" Grid.Column="1" TextWrapping="WrapWithOverflow" Text="{Binding ElementName=notificationView, Path=Message}"
                   VerticalAlignment="Center" HorizontalAlignment="Left"/>
    </StackPanel>
</UserControl>

Enter fullscreen mode Exit fullscreen mode

In the NotificationControls.xaml.cs code behind file, Dependency Property is used to exposed property to external world and for binding.

  • NotificationView.xaml.cs
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace NotificationControls
{
    /// <summary>
    /// Interaction logic for Notification.xaml
    /// </summary>
    public partial class NotificationView : UserControl
    {
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IconKindProperty =
            DependencyProperty.Register(nameof(IconKind), typeof(Icon), typeof(NotificationView), new PropertyMetadata(SystemIcons.Error));

        // Using a DependencyProperty as the backing store for Message.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MessageProperty =
            DependencyProperty.Register(nameof(Message), typeof(string), typeof(NotificationView), new PropertyMetadata(string.Empty));

        public NotificationView()
        {
            InitializeComponent();
            Loaded += NotificationView_Loaded;
        }

        public event Action Closed;

        private void NotificationView_Loaded(object sender, RoutedEventArgs e)
        {
            var task = Task.Delay(TimeSpan.FromSeconds(5));
            _ = task.ContinueWith(t => Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, () => Closed?.Invoke()));
        }

        public Icon IconKind
        {
            get { return (Icon)GetValue(IconKindProperty); }
            set { SetValue(IconKindProperty, value); }
        }

        public string Message
        {
            get { return (string)GetValue(MessageProperty); }
            set { SetValue(MessageProperty, value); }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Since the Image has Binding associated with Dependency property IconKind which is of type System.Drawing.Icon, An "IconToImageSourceConverter" converter is used to convert icon to image source.

  • IconToImageSourceConverter.cs
public class IconToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var icon = value as Icon;
            if (icon == null)
                return null;

            ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
                icon.Handle,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            return imageSource;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue;
        }
    }
Enter fullscreen mode Exit fullscreen mode

With all the above, we have successfully create a notification control which can display a notification with an type and a message.

Example, Error notification with message "Error occured, Please try again!" and IconKind as SystemIcons.Error, will display as below

Error notification

To let notification disappear after a short time (For this example, lets keep this to 5 seconds), A loaded event is subscribe and Task.Delay(TimeSpan.FromSeconds(5)) is used to wait for 5 second and then invoke close event.

Now, It is time to stack one or many notification in control and allow them to disappear after 5 second. To achieve this, Create a class with name StackedNoifications and derive it from StackPanel Control class. Now, add the below code to the StackedNoifications class.

using NotificationControls;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace CompanionWindows.Views.Controls
{
    public class StackedNoifications : StackPanel
    {
        // Using a DependencyProperty as the backing store for Message.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NotificationMessageProperty =
            DependencyProperty.Register(nameof(NotificationMessage), typeof(Notification),
                typeof(StackedNoifications), new PropertyMetadata(null, OnMessageChanged));

        public Notification NotificationMessage
        {
            get => (Notification)GetValue(NotificationMessageProperty);
            set => SetValue(NotificationMessageProperty, value);
        }
        private static void OnMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            try
            {
                var stackednotification = (StackedNoifications)d;
                if (e.NewValue is Notification newMessage)
                {
                    stackednotification.Push(newMessage);
                }
            }
            catch (Exception)
            {
                // ignore
            }
        }

        private NotificationView CreateNotificationView(Notification newMessage)
        {
            var view = new NotificationView
            {
                Message = newMessage.Message,
                Margin = new Thickness(0, 0, 0, 8),
                IconKind = newMessage.IconKind
            };

            return view;
        }

        private void Push(Notification newMessage)
        {
            var alertPopupViews = Children.OfType<NotificationView>().ToList();

            if (!alertPopupViews.Any(v => v.Message == newMessage.Message))
            {
                var notificationView = CreateNotificationView(newMessage);
                notificationView.Closed += () => Pop(notificationView);

                Children.Add(notificationView);
            }
        }
        public void Pop(NotificationView view) => Children.Remove(view);
    }
}
Enter fullscreen mode Exit fullscreen mode

The StackedNoifications has dependency property NotificationMessage which has implemented PropertyChangedCallback

The PropertyChangedCallback that is invoked when the effective property value of a dependency property changes which is used to push new messages to stack panel.

Once all code are in placed, The final result would be

Notification Control

Conclusion

With these elements and classes in place, we can create a fully functional custom notification control which is capable of communicating certain events to the user without forcing them to react to this notification immediately in WPF.

💖 💪 🙅 🚩
tarun06
Tarun Kumar

Posted on May 21, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related