Introduction
This WPF Taskbar Notifier is a WPF window that opens and closes using a storyboard animation. It can be used to notify a user when some event of interest has occurred.
When Notify()
is called, the window moves up from the bottom right corner of the desktop (just above the taskbar when the taskbar is there). Once open, the window stays open for a configurable amount of time before it begins hiding again. The speed for opening and hiding are also configurable. The window will remain open as long as the mouse is over it.
Additionally, a WPF wrapper for the Windows Forms NotifyIcon
is included to make it easy to add an icon to the system tray. This icon can come in handy for adding a user interface for re-opening, configuring, and exiting the WPF Taskbar Notifier. The NotifyIcon
wrapper was written by Mariano Omar Rodriguez and posted on his blog here.
To see the WPF Taskbar Notifier in action, download the demo executable and give it a try!
Background
There are at least several other C# implementations of a window that pops up from the taskbar. None of the ones I've seen offer a WPF window.
Using the Code
TaskbarNotifier.cs contains the TaskbarNotifier
class, which is the popup WPF window. TaskbarNotifier
defines no window content because anyone using it will want to define their own application specific content. In the demo source code, the ExampleTaskbarNotifier
class uses TaskbarNotifier
as a base class and defines its content in ExampleTaskbarNotifier.xaml. What content you decide to put into your own subclass of TaskbarNotifier
is up to you.
There are several public properties and methods that allow you to control the window's behavior.
int OpeningMilliseconds
- The number of milliseconds it takes for the window to open from its fully hidden position.
int HidingMilliseconds
- The number of milliseconds it takes for the window to hide from its fully open position.
int StayOpenMilliseconds
- The number of milliseconds the window will stay open once it reaches its fully open position. Once this time has elapsed, it will begin hiding.
int LeftOffset
- The number of pixels the window should be offset from the left side of the desktop. This allows the window to be shifted horizontally, if the default (extreme bottom right corner) is not desired.
void Notify()
- Tells the window to open.
void ForceHidden()
- Immediately puts the window into its hiding position.
As mentioned above, TaskbarNotifier
should be subclassed in order to add your own content. In the example, the main application window itself is a standard window, not a TaskbarNotifier
window. The main window creates and displays the ExampleTaskbarNotifier
window in its constructor. At this point it is not visible to the user, because it is too low on the desktop to see.
public Window1()
{
this.taskbarNotifier = new ExampleTaskbarNotifier();
InitializeComponent();
this.taskbarNotifier.Show();
}
The application specific content of the subclasses TaskbarNotifier
window can be defined in XAML like any other WPF window:
<tn:TaskbarNotifier x:Class="WPFTaskbarNotifierExample.ExampleTaskbarNotifier"
xmlns:tn="clr-namespace:WPFTaskbarNotifier;assembly=WPFTaskbarNotifier"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Taskbar Notifier Example" Height="160" Width="300"
>
-->
</tn:TaskbarNotifier>
Because the TaskbarNotifier
window hides itself completely, it seemed necessary to include some way of opening, configuring, and exiting it. I've included Mariano Omar Rodriguez's NotifyIcon wrapper to allow a system tray icon to be instantiated in XAML. While the demo application does not use the NotifyIcon's
balloon tips for anything, it does demonstrate how a system tray icon can be used in conjunction with the Taskbar Notifier.
Here is how the NotifyIcon
is declared in the example's XAML:
<tn:NotifyIcon x:Name="NotifyIcon" Text="Example Notifier"
Icon="Resources/UFO.ico" MouseDoubleClick="NotifyIcon_DoubleClick">
<tn:NotifyIcon.ContextMenu>
<ContextMenu>
<MenuItem Header="Open" Click="NotifyIconOpen_Click" />
<MenuItem Header="Configure..." Click="NotifyIconConfigure_Click" />
<Separator/>
<MenuItem Header="Exit" Click="NotifyIconExit_Click" />
</ContextMenu>
</tn:NotifyIcon.ContextMenu>
</tn:NotifyIcon>
In your application, whenever it makes sense for the TaskbarNotifier
to pop up, its Notify()
method can be called.
How it Works
The TaskbarNotifier
uses a single Storyboard
based on the window's Top
property, and a DoubleAnimation
.
this.animation = new DoubleAnimation();
Storyboard.SetTargetProperty(this.animation, new PropertyPath(Window.TopProperty));
this.storyboard = new Storyboard();
this.storyboard.Children.Add(this.animation);
this.storyboard.FillBehavior = FillBehavior.Stop;
By animating Top
, the window's location moves. The size of the window is never changed, as this could potentially change the way the window's contents are organized. The window is simply moved out of view.
In the code, when a change in movement is required the Storyboard
is stopped, the To
and Duration
properties of the DoubleAnimation
are altered, and the Storyboard
is told to Begin
again. For example, if the window is Hiding
and the user moves the cursor over the window, the window stops hiding and changes its state to Opening
. The Duration
is calculated based on the current window location to ensure the desired opening/hiding rate.
The private DisplayState
property drives the movement of the window. The possible values of DisplayState
are defined in the following enumeration:
private enum DisplayStates
{
Opening,
Opened,
Hiding,
Hidden
}
Whenever DisplayState
is altered, the OnDisplayStateChanged
method is called:
private DisplayStates DisplayState
{
get { return this.displayState; }
set
{
if (value != this.displayState)
{
this.displayState = value;
this.OnDisplayStateChanged();
}
}
}
OnDisplayStateChanged
simply stops the current animation and handles DisplayState
based on each of the four possible DisplayStates
.
For example, here is how the Storyboard
is configured and re-started when the window is told to open:
int milliseconds = this.CalculateMillseconds(this.openingMilliseconds, this.openedTop);
this.animation.To = this.openedTop;
this.animation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, milliseconds));
this.storyboard.Completed += arrivedOpened;
this.storyboard.Begin(this, true);
A DispatcherTimer
is used to trigger the hiding of the window once it has been in the open state for the duration indicated by StayOpenMilliseconds
.
Here is how the DispatcherTimer
is created:
this.stayOpenTimer = new DispatcherTimer();
this.stayOpenTimer.Interval = TimeSpan.FromMilliseconds(this.stayOpenMilliseconds);
this.stayOpenTimer.Tick += new EventHandler(this.stayOpenTimer_Elapsed);
When the stayOpenTimer
elapses, the timer is stopped and as long as the mouse is not over it, the window is told to hide. This is accomplished by setting the DisplayState
to DisplayStates.Hiding
:
private void stayOpenTimer_Elapsed(Object sender, EventArgs args)
{
this.stayOpenTimer.Stop();
if (!this.IsMouseOver)
{
this.DisplayState = DisplayStates.Hiding;
}
}
For more details, download the source code and check it out.
History
- December 2007: Initial creation
- 23 Jan 2008: Quick update to fix some visual problems