Skinnable and Animated popup window in WPF






4.82/5 (26 votes)
How to create a skinnable and animatable MSN Live Messenger style popup window in WPF with MVVM design patterns

Introduction
The purpose of this article is to demonstrate how to create an animated MSN Live Messenger style popup window in WPF with MVVM design patterns. The popup window employs the fly-in and fade-in animation to move from the Taskbar to the screen area and then fade-out from the screen in 3 seconds.
Background
Traditionally, Windows desktop applications uses Notification Icon in system tray to notify user about some message or status notification. This is a good user experience which doesn't interrupt the user at work while keeping them aware of what's about. However, it renders a poor user experience in a real-time and highly interactive application where it failed to provide multiple or concurrent notification to the user. Personally, I prefer to use the MSN Live Messenger style popup window to solve the above mentioned problem.
Understanding the Code
This demo application is composed of several files, I will focus on the core functionality written in the following files:
- NotifyMessageWindow.xaml
- NotifyMessageViewModel.cs
- NotifyMessageManager.cs
NotifyMessageWindow.xaml
This is a skinnable and animated WPF popup window to show the message notification. To create a skinnable window, we need to set 3 properties of the Window to the following values:
AllowsTransparency = "True"
Background = "Transparent"
WindowsStyle = "None"
Then, we need to create 2 storyboards in the Window.Resources
section.
The 'flyinStoryboard'
storyboard is responsible for showing the fly-in window by animate the Window.Left
and Window.Top
properties.
The 'fadeInfadeOutStoryboard
' storyboard is responsible for fade-in and fade-out the window by animating the attached property Window.Opacity
in 3 seconds.
The data binding {Binding
} properties in the resources are coming from the NotifyMessageViewModel
view model, which will be revealed later in this article.
<Window.Resources>
<Storyboard x:Key="flyinStoryboard">
<DoubleAnimation From="{Binding Location.FromLeft}"
To="{Binding Location.ToLeft}"
Storyboard.TargetProperty="(Window.Left)"
Duration="0:0:1"></DoubleAnimation>
<DoubleAnimation From="{Binding Location.FromTop}"
To="{Binding Location.ToTop}"
Storyboard.TargetProperty="(Window.Top)"
Duration="0:0:1"></DoubleAnimation>
</Storyboard>
<Storyboard x:Key="fadeInfadeOutStoryboard">
<DoubleAnimation Storyboard.TargetProperty="(Window.Opacity)"
From="0.0" To="1.0" AutoReverse="True"
AccelerationRatio="0.4" DecelerationRatio="0.6"
Duration="0:0:3"></DoubleAnimation>
</Storyboard>
</Window.Resources>
Upon Window.Loaded
event triggered, we start the 'flyinStoryboard
' and 'fadeInfadeOutStoryboard
' storyboard together to create a smooth fly-in, then fade-in effect while showing the popup window.
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource flyinStoryboard}" />
<BeginStoryboard Storyboard="{StaticResource fadeInfadeOutStoryboard}" />
</EventTrigger>
</Window.Triggers>
The content layout of the popup window is shown below and it simply binds the SkinName
, HeaderText
and BodyText
properties from the view model.
<Grid>
<Grid Margin="5" Background="LightYellow">
<Grid.Resources>
<Style x:Key="headerTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="Margin" Value="30 0 5 0" />
</Style>
<Style x:Key="bodyTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
<Setter Property="Margin" Value="10 0 10 0" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource headerTextStyle}"
Text="{Binding Message.HeaderText}"></TextBlock>
<TextBlock Grid.Row="1" Style="{StaticResource bodyTextStyle}"
Text="{Binding Message.BodyText}"></TextBlock>
</Grid>
<Image Source="{Binding Message.SkinName}" Stretch="Fill" />
</Grid>
The content layout of the Markup code shown above.
In this demo project, I've created 2 skin images named as 'BlueSkin.png' and 'GreenSkin.png', respectively. You will get to know how to use them in the section titled 'Using the Code' later in this article.


NotifyMessageViewModel.cs
This class serves as the view model to the popup window. It contains the following properties:
Message
- It contains theSkinName
,HeaderTe
andxt
BodyText
for content display.Location
- It instructs thestoryboard
where to show the popup window in the screen location.ClickCommand
- Action to be taken when popup window is being clicked.CloseCommand
- Action to be taken while popup window is closed.
The properties shown above are pretty self-explanatory. User can customize the ClickCommand
as of what to do when a popup window is being clicked. For example, showing the detailed message, navigate to another view, open the Internet Explorer and so on. The CloseCommand
is used to notify NotifyMessageManager
when the popup window is closed.
NotifyMessageManager.cs
This class is responsible for storing and displaying the queued messages. In order to support highly concurrent scenario, it employs a ConcurrentQueue data structure internally to store and retrieve the queued messages in First-In-First-Out (FIFO) manner. Its primary job is to start a background Task to display the popup window as soon as a message is enqueued in the system and stop while there is no message in the queue.
var dispatcher = Application.Current.MainWindow.Dispatcher;
return Task.Factory.StartNew(() => {
do
{
// Gets the next available display location in the screen
int nextLocation = FindNextLocation();
if (nextLocation > -1)
{
NotifyMessage msg = null;
// Retrieve the message from the queue
if (QueuedMessages.TryDequeue(out msg))
{
// construct a View Model and bind it to the Popup Window
var viewModel = new NotifyMessageViewModel(msg,
DisplayLocations[nextLocation],
() => DisplayMessages[nextLocation] =
null); // Free the display location once the popup window is closed
DisplayMessages[nextLocation] = viewModel;
// Use Application.Current.MainWindow.Dispatcher to
// switch back to the UI Thread to create popup window
dispatcher.BeginInvoke(
new MethodInvoker(() =>
{
var window = new NotifyMessageWindow()
{
Owner = Application.Current.MainWindow,
DataContext = viewModel,
ShowInTaskbar = false
};
window.Show();
}), DispatcherPriority.Background);
}
}
Thread.Sleep(1000);
} while (QueuedMessages.Count > 0 && !cancellationToken.IsCancellationRequested);
Stop();
});
Using the Code
First of all, create a singleton NotifyMessageManager
in your application. You need to provide 4 required parameters to the constructor as shown in the code below. The popup window's width and height must be the same as the Skin's width and height. In our case, we are using 200x150 pixels skin here.
var notifyMessageMgr = new NotifyMessageManager(
Screen.Width, // Screen width
Screen.Height, // Screen height excludes the Taskbar height
200, // Popup Window width
150 // Popup Window height
);
Next, create one instance of NotifyMessage
and enqueue it to the NotifyMessageManager
as shown in the code below:
var msg = new NotifyMessage(
"Images/BlueSkin.png", // Skin file name - Choose your favorite skin
"Blue Skin Title", // The message title
"Blue Skin has been chosen.", // The message content
() => MessageBox.Show("Blue Skin has been chosen.") // Show a MessageBox
// when user clicks the popup window
);
notifyMessageMgr.EnqueueMessage(msg);
You will see a popup window show up in the bottom left of the screen after the message is enqueued. Last, but not least, I've included a sample, available from the link at the top of this article, which demonstrates what I've described in this article.
Conclusion
To run this demo on your computer, you need to download the Microsoft Expression Blend Software Development Kit (SDK) for .NET 4 or manage to get the following library files:
- Microsoft.Expression.Interactions.dll
- System.Windows.Interactivity.dll
History
- 29th October, 2011: Initial version