Create popup "toaster" notifications in Windows with .NET Create popup "toaster" notifications in Windows with .NET windows windows

Create popup "toaster" notifications in Windows with .NET


WPF makes this absolutely trivial: It would proably take ten minutes or less. Here are the steps:

  1. Create a Window, set AllowsTransparency="true" and add a Grid to it
  2. Set the Grid's RenderTransform to a ScaleTransform with origin of 0,1
  3. Create an animation on the grid that animates the ScaleX 0 to 1 then later animates the Opacity from 1 to 0
  4. In the constructor calculate Window.Top and Window.Left to place the window in the lower right-hand corner of the screen.

That's all there is to it.

Using Expression Blend it took about 8 minutes me to generate the following working code:

<Window    x:Class="NotificationWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  Title="Notification Popup" Width="300" SizeToContent="Height"  WindowStyle="None" AllowsTransparency="True" Background="Transparent">  <Grid RenderTransformOrigin="0,1" >    <!-- Notification area -->    <Border BorderThickness="1" Background="Beige" BorderBrush="Black" CornerRadius="10">      <StackPanel Margin="20">        <TextBlock TextWrapping="Wrap" Margin="5">          <Bold>Notification data</Bold><LineBreak /><LineBreak />          Something just happened and you are being notified of it.        </TextBlock>        <CheckBox Content="Checkable" Margin="5 5 0 5" />        <Button Content="Clickable" HorizontalAlignment="Center" />      </StackPanel>    </Border>    <!-- Animation -->    <Grid.Triggers>      <EventTrigger RoutedEvent="FrameworkElement.Loaded">        <BeginStoryboard>          <Storyboard>            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">              <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>              <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>            </DoubleAnimationUsingKeyFrames>            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">              <SplineDoubleKeyFrame KeyTime="0:0:2" Value="1"/>              <SplineDoubleKeyFrame KeyTime="0:0:4" Value="0"/>            </DoubleAnimationUsingKeyFrames>          </Storyboard>        </BeginStoryboard>      </EventTrigger>    </Grid.Triggers>    <Grid.RenderTransform>      <ScaleTransform ScaleY="1" />    </Grid.RenderTransform>  </Grid></Window>

With code behind:

using System;using System.Windows;using System.Windows.Threading;public partial class NotificationWindow{  public NotificationWindow()  {    InitializeComponent();    Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>    {      var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;      var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;      var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));      this.Left = corner.X - this.ActualWidth - 100;      this.Top = corner.Y - this.ActualHeight;    }));  }}

Since WPF is one of the regular .NET libraries, the answer is yes, it is possible to accomplish this with the "regular .NET libraries".

If you're asking if there is a way to do this without using WPF the answer is still yes, but it is extremely complex and will take more like 5 days than 5 minutes.


I went ahead and created a CodePlex site for this that includes "Toast Popups" and control "Help Balloons". These versions have more features than what's described below. https://toastspopuphelpballoon.codeplex.com.

This was a great jumping off point for the solution that I was looking for. I've made a couple of modifications to meet my requirements:

  • I wanted to stop the animation on mouse over.
  • "Reset" animation when mouse leave.
  • Close the Window when opacity reached 0.
  • Stack the Toast (I have not solved the problem if the number of windows exceeds the screen height)
  • Call Load from my ViewModel

Here's my XAML

<Window x:Class="Foundation.FundRaising.DataRequest.Windows.NotificationWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    Title="NotificationWindow" Height="70" Width="300" ShowInTaskbar="False"    WindowStyle="None" AllowsTransparency="True"     Background="Transparent"><Grid RenderTransformOrigin="0,1" >    <Border BorderThickness="2" Background="{StaticResource GradientBackground}" BorderBrush="DarkGray" CornerRadius="7">        <Grid>            <Grid.ColumnDefinitions>                <ColumnDefinition Width="60"/>                <ColumnDefinition Width="*"/>                <ColumnDefinition Width="24"/>            </Grid.ColumnDefinitions>            <Grid.RowDefinitions>                <RowDefinition Height="30"/>                <RowDefinition Height="*"/>            </Grid.RowDefinitions>            <Image Grid.Column="0"                    Grid.RowSpan="2"                    Source="Resources/data_information.png"                    Width="40" Height="40"                    VerticalAlignment="Center"                    HorizontalAlignment="Center"/>            <Image Grid.Column="2"                    Source="Resources/error20.png"                   Width="20"                    Height="20"                    VerticalAlignment="Center"                    ToolTip="Close"                   HorizontalAlignment="Center"                    Cursor="Hand" MouseUp="ImageMouseUp"/>            <TextBlock Grid.Column="1"                        Grid.Row="0"                       VerticalAlignment="Center"                       HorizontalAlignment="Center"                       FontWeight="Bold" FontSize="15"                       Text="A Request has been Added"/>            <Button Grid.Column="1"                    Grid.Row="1"                    FontSize="15"                    Margin="0,-3,0,0"                    HorizontalAlignment="Center"                    VerticalAlignment="Center"                    Content="Click Here to View"                     Style="{StaticResource LinkButton}"/>        </Grid>                </Border>    <!-- Animation -->    <Grid.Triggers>        <EventTrigger RoutedEvent="FrameworkElement.Loaded">            <BeginStoryboard x:Name="StoryboardLoad">                <Storyboard>                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="0.0" To="1.0" Duration="0:0:2" />                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:5" Completed="DoubleAnimationCompleted"/>                </Storyboard>            </BeginStoryboard>        </EventTrigger>        <EventTrigger RoutedEvent="Mouse.MouseEnter">            <EventTrigger.Actions>                <RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>                <RemoveStoryboard BeginStoryboardName="StoryboardFade"/>            </EventTrigger.Actions>        </EventTrigger>        <EventTrigger RoutedEvent="Mouse.MouseLeave">            <BeginStoryboard x:Name="StoryboardFade">                <Storyboard>                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:2" Completed="DoubleAnimationCompleted"/>                </Storyboard>            </BeginStoryboard>        </EventTrigger>    </Grid.Triggers>    <Grid.RenderTransform>        <ScaleTransform ScaleY="1" />    </Grid.RenderTransform></Grid>

The Code Behind

public partial class NotificationWindow : Window{    public NotificationWindow()        : base()    {        this.InitializeComponent();        this.Closed += this.NotificationWindowClosed;    }    public new void Show()    {        this.Topmost = true;        base.Show();        this.Owner = System.Windows.Application.Current.MainWindow;        this.Closed += this.NotificationWindowClosed;        var workingArea = Screen.PrimaryScreen.WorkingArea;        this.Left = workingArea.Right - this.ActualWidth;        double top = workingArea.Bottom - this.ActualHeight;        foreach (Window window in System.Windows.Application.Current.Windows)        {                            string windowName = window.GetType().Name;            if (windowName.Equals("NotificationWindow") && window != this)            {                window.Topmost = true;                top = window.Top - window.ActualHeight;            }        }        this.Top = top;    }    private void ImageMouseUp(object sender,         System.Windows.Input.MouseButtonEventArgs e)    {        this.Close();    }    private void DoubleAnimationCompleted(object sender, EventArgs e)    {        if (!this.IsMouseOver)        {            this.Close();        }    }}

The call from the ViewModel:

    private void ShowNotificationExecute()    {        App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(            () =>            {                var notify = new NotificationWindow();                notify.Show();            }));    }

The Styles referenced in the XAML:

     <Style x:Key="LinkButton" TargetType="Button">        <Setter Property="Template">            <Setter.Value>                <ControlTemplate TargetType="Button">                    <TextBlock>                        <ContentPresenter />                    </TextBlock>                </ControlTemplate>            </Setter.Value>        </Setter>        <Setter Property="Foreground" Value="Blue"/>        <Setter Property="Cursor" Value="Hand"/>        <Style.Triggers>            <Trigger Property="IsMouseOver" Value="True">                <Setter Property="ContentTemplate">                    <Setter.Value>                        <DataTemplate>                            <TextBlock TextDecorations="Underline" Text="{TemplateBinding Content}"/>                        </DataTemplate>                    </Setter.Value>                </Setter>            </Trigger>        </Style.Triggers>    </Style>    <LinearGradientBrush x:Key="GradientBackground" EndPoint="0.504,1.5" StartPoint="0.504,0.03">        <GradientStop Color="#FFFDD5A7" Offset="0"/>        <GradientStop Color="#FFFCE79F" Offset="0.567"/>    </LinearGradientBrush>

UPDATE: I added this event handler when the form is closed to "drop" the other windows.

    private void NotificationWindowClosed(object sender, EventArgs e)    {        foreach (Window window in System.Windows.Application.Current.Windows)        {            string windowName = window.GetType().Name;            if (windowName.Equals("NotificationWindow") && window != this)            {                // Adjust any windows that were above this one to drop down                if (window.Top < this.Top)                {                    window.Top = window.Top + this.ActualHeight;                }            }        }    }


public partial class NotificationWindow : Window{    DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();    public NotificationWindow()        : base()    {        this.InitializeComponent();        Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>        {            var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;            var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;            var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));            this.Left = corner.X - this.ActualWidth;            this.Top = corner.Y - this.ActualHeight;        }));        timer.Interval = TimeSpan.FromSeconds(4d);        timer.Tick += new EventHandler(timer_Tick);    }    public new void Show()    {        base.Show();        timer.Start();    }    void timer_Tick(object sender, EventArgs e)    {        //set default result if necessary        timer.Stop();        this.Close();    }}

The above code is refined version @Ray Burns approach. Added with time interval code. So that Notification window would close after 4 seconds..

Call the Window as,

NotificationWindow nfw = new NotificationWindow();nfw.Show();