I am using .NET and am creating a desktop app/service that will display notifications in the corner of my Desktop when certain events are triggered. I don't want to use a regular Message Box b/c that would be too intrusive. I want notifications to slide into view and then fade out after a few seconds. I am thinking of something that will act very much like the Outlook alerts that one gets when a new message arrives. The question is: Should I use WPF for this? I've never done anything with WPF but will happily try it if that's best means to the end. Is there a way to accomplish this with regular .NET libraries?
WPF makes this absolutely trivial: It would proably take ten minutes or less. Here are the steps:
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:
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; } } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With