WPF NotifyIcon

--------------
ADD   Added ParentTaskbarIcon attached dependency property which is set for tooltips, popups, and custom balloons.
CHG   Made CloseBalloon public.
CHG   Changed sample, cleaned up commands pattern.


git-svn-id: https://svn.evolvesoftware.ch/repos/evolve.net/WPF/NotifyIcon@93 9f600761-6f11-4665-b6dc-0185e9171623
This commit is contained in:
Philipp Sumi
2009-05-03 07:10:30 +00:00
parent d792f1c5a4
commit 2b05ff7bd7
12 changed files with 330 additions and 76 deletions

View File

@@ -121,7 +121,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
}
/// <summary>
/// Provides a secure method for setting the CustomBalloon property.
/// Provides a secure method for setting the <see cref="CustomBalloon"/> property.
/// </summary>
/// <param name="value">The new value for the property.</param>
protected void SetCustomBalloon(Popup value)
@@ -333,7 +333,21 @@ namespace Hardcodet.Wpf.TaskbarNotification
//recreate tooltip control
CreateCustomToolTip();
//udpate tooltip settings - needed to make sure a string is set, even
if (e.OldValue != null)
{
//remove the taskbar icon reference from the previously used element
SetParentTaskbarIcon((DependencyObject) e.OldValue, this);
}
if (e.NewValue != null)
{
//set this taskbar icon as a reference to the new tooltip element
SetParentTaskbarIcon((DependencyObject) e.NewValue, this);
}
//update tooltip settings - needed to make sure a string is set, even
//if the ToolTipText property is not set. Otherwise, the event that
//triggers tooltip display is never fired.
WriteToolTipSettings();
@@ -390,6 +404,19 @@ namespace Hardcodet.Wpf.TaskbarNotification
/// <param name="e">Provides information about the updated property.</param>
private void OnTrayPopupPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
//remove the taskbar icon reference from the previously used element
SetParentTaskbarIcon((DependencyObject)e.OldValue, this);
}
if (e.NewValue != null)
{
//set this taskbar icon as a reference to the new tooltip element
SetParentTaskbarIcon((DependencyObject)e.NewValue, this);
}
//create a pop
CreatePopup();
}
@@ -1670,7 +1697,38 @@ namespace Hardcodet.Wpf.TaskbarNotification
#endregion
//ATTACHED PROPERTIES
//TODO put into use
#region ParentTaskbarIcon
/// <summary>
/// An attached property that is assigned to
/// </summary>
public static readonly DependencyProperty ParentTaskbarIconProperty =
DependencyProperty.RegisterAttached("ParentTaskbarIcon", typeof (TaskbarIcon), typeof (TaskbarIcon));
/// <summary>
/// Gets the ParentTaskbarIcon property. This dependency property
/// indicates ....
/// </summary>
public static TaskbarIcon GetParentTaskbarIcon(DependencyObject d)
{
return (TaskbarIcon)d.GetValue(ParentTaskbarIconProperty);
}
/// <summary>
/// Sets the ParentTaskbarIcon property. This dependency property
/// indicates ....
/// </summary>
public static void SetParentTaskbarIcon(DependencyObject d, TaskbarIcon value)
{
d.SetValue(ParentTaskbarIconProperty, value);
}
#endregion

View File

@@ -163,7 +163,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
Popup.CreateRootPopup(popup, balloon);
//TODO we don't really need this and it causes the popup to become hidden if the
//don't set the PlacementTarget as it causes the popup to become hidden if the
//TaskbarIcon's parent is hidden, too...
//popup.PlacementTarget = this;
@@ -180,6 +180,9 @@ namespace Hardcodet.Wpf.TaskbarNotification
SetCustomBalloon(popup);
}
//assign this instance as an attached property
SetParentTaskbarIcon(balloon, this);
//fire attached event
RaiseBalloonShowingEvent(balloon);
@@ -199,7 +202,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
/// <summary>
/// Closes the current <see cref="CustomBalloon"/>, if it's set.
/// </summary>
private void CloseBalloon()
public void CloseBalloon()
{
if (IsDisposed) return;
@@ -214,6 +217,11 @@ namespace Hardcodet.Wpf.TaskbarNotification
{
//if a balloon message is already displayed, close it immediately
popup.IsOpen = false;
//reset attached property
UIElement element = popup.Child;
if (element != null) SetParentTaskbarIcon(element, null);
SetCustomBalloon(null);
}
}
@@ -396,7 +404,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
tt = new ToolTip();
tt.Placement = PlacementMode.Mouse;
//TODO we don't really need this and it causes the popup to become hidden if the
//do *not* set the placement target, as it causes the popup to become hidden if the
//TaskbarIcon's parent is hidden, too.
//tt.PlacementTarget = this;

View File

@@ -0,0 +1,64 @@
using System;
using System.Windows.Input;
using System.Windows.Markup;
namespace Sample_Project.Commands
{
/// <summary>
/// Basic implementation of the <see cref="ICommand"/>
/// interface, which is also accessible as a markup
/// extension.
/// </summary>
public abstract class CommandBase<T> : MarkupExtension, ICommand
where T : class, ICommand, new()
{
/// <summary>
/// A singleton instance.
/// </summary>
private static T command;
/// <summary>
/// Gets a shared command instance.
/// </summary>
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (command == null) command = new T();
return command;
}
/// <summary>
/// Fires when changes occur that affect whether
/// or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command.
/// If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public abstract void Execute(object parameter);
/// <summary>
/// Defines the method that determines whether the command
/// can execute in its current state.
/// </summary>
/// <returns>
/// This default implementation always returns true.
/// </returns>
/// <param name="parameter">Data used by the command.
/// If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public virtual bool CanExecute(object parameter)
{
return true;
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows;
using System.Windows.Input;
namespace Sample_Project.Commands
@@ -10,30 +6,21 @@ namespace Sample_Project.Commands
/// <summary>
/// Hides the main window.
/// </summary>
public class HideMainWindowCommand : ICommand
public class HideMainWindowCommand : CommandBase<HideMainWindowCommand>
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
public override void Execute(object parameter)
{
Application.Current.MainWindow.Hide();
TaskbarIconCommands.RefreshCommands();
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(object parameter)
public override bool CanExecute(object parameter)
{
return Application.Current.MainWindow.IsVisible;
}
/// <summary>
/// Raises the <see cref="CanExecuteChanged"/> event.
/// </summary>
internal void RaiseCanExcecuteChanged()
{
if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
@@ -10,29 +9,19 @@ namespace Sample_Project.Commands
/// <summary>
/// Shows the main window.
/// </summary>
public class ShowMainWindowCommand : ICommand
public class ShowMainWindowCommand : CommandBase<ShowMainWindowCommand>
{
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
public override void Execute(object parameter)
{
Application.Current.MainWindow.Show();
TaskbarIconCommands.RefreshCommands();
CommandManager.InvalidateRequerySuggested();
}
public bool CanExecute(object parameter)
public override bool CanExecute(object parameter)
{
return Application.Current.MainWindow.IsVisible == false;
}
/// <summary>
/// Raises the <see cref="CanExecuteChanged"/> event.
/// </summary>
internal void RaiseCanExcecuteChanged()
{
if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty);
}
}
}

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace Sample_Project.Commands
{
public static class TaskbarIconCommands
{
public static HideMainWindowCommand HideMain { get; set; }
public static ShowMainWindowCommand ShowMain { get; set; }
static TaskbarIconCommands()
{
HideMain = new HideMainWindowCommand();
ShowMain =new ShowMainWindowCommand();
}
public static void RefreshCommands()
{
HideMain.RaiseCanExcecuteChanged();
ShowMain.RaiseCanExcecuteChanged();
}
}
}

View File

@@ -0,0 +1,99 @@
<UserControl
x:Class="Sample_Project.FancyBalloon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tb="http://www.hardcodet.net/taskbar"
x:Name="me"
Height="120"
Width="240">
<UserControl.Resources>
<Storyboard x:Key="FadeIn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.95"/>
<SplineDoubleKeyFrame KeyTime="00:00:03" Value="0.95"/>
<SplineDoubleKeyFrame KeyTime="00:00:05" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HighlightCloseButton">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="imgClose" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.4"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="FadeCloseButton">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="imgClose" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.4"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="tb:TaskbarIcon.BalloonShowing">
<BeginStoryboard Storyboard="{StaticResource FadeIn}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="imgClose">
<BeginStoryboard Storyboard="{StaticResource HighlightCloseButton}" x:Name="HighlightCloseButton_BeginStoryboard"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave" SourceName="imgClose">
<BeginStoryboard Storyboard="{StaticResource FadeCloseButton}" x:Name="FadeCloseButton_BeginStoryboard"/>
</EventTrigger>
</UserControl.Triggers>
<Grid x:Name="grid">
<Border
HorizontalAlignment="Stretch"
Margin="5,5,5,5"
BorderThickness="1,1,1,1"
BorderBrush="#FF997137">
<Border.Effect>
<DropShadowEffect Color="#FF747474"/>
</Border.Effect>
<Border.Background>
<LinearGradientBrush
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop
Color="#FF4B4B4B"
Offset="0" />
<GradientStop
Color="#FF8F8F8F"
Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Image
HorizontalAlignment="Left"
Margin="0,10,0,0"
Width="72"
Source="Images\Info.png"
Stretch="Fill" Height="72" VerticalAlignment="Top" />
<TextBlock
Margin="72,49.2,10,0"
VerticalAlignment="Top"
Foreground="#FFECAD25"
TextWrapping="Wrap"><Run Text="This is a user control. The animation uses the attached " Language="de-ch"/><Run FontStyle="Italic" FontWeight="Bold" Text="BalloonShowing " Language="de-ch"/><Run Text="event." Language="de-ch"/></TextBlock>
<Path
Fill="#FFFFFFFF"
Stretch="Fill"
Margin="72,38.2,34,0"
VerticalAlignment="Top"
Height="1"
Data="M26,107 L220.04123,107" SnapsToDevicePixels="True">
<Path.Stroke>
<LinearGradientBrush
EndPoint="0.973,0.5"
StartPoint="0.005,0.5">
<GradientStop
Color="#00ECAD25"
Offset="1" />
<GradientStop
Color="#87ECAD25"
Offset="0" />
</LinearGradientBrush>
</Path.Stroke>
</Path>
<TextBlock Margin="72,10,10,0" VerticalAlignment="Top" Height="23.2" Text="{Binding Path=BalloonText, ElementName=me, Mode=Default}" TextWrapping="Wrap" Foreground="#FFECAD25" FontWeight="Bold"/>
<Image HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top" Width="16" Height="16" Source="Images\Close.png" Stretch="Fill" Opacity="0.4" ToolTip="Close Immediately" x:Name="imgClose" MouseDown="imgClose_MouseDown"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Hardcodet.Wpf.TaskbarNotification;
namespace Sample_Project
{
/// <summary>
/// Interaction logic for FancyBalloon.xaml
/// </summary>
public partial class FancyBalloon : UserControl
{
#region BalloonText dependency property
/// <summary>
/// Description
/// </summary>
public static readonly DependencyProperty BalloonTextProperty =
DependencyProperty.Register("BalloonText",
typeof (string),
typeof (FancyBalloon),
new FrameworkPropertyMetadata(""));
/// <summary>
/// A property wrapper for the <see cref="BalloonTextProperty"/>
/// dependency property:<br/>
/// Description
/// </summary>
public string BalloonText
{
get { return (string) GetValue(BalloonTextProperty); }
set { SetValue(BalloonTextProperty, value); }
}
#endregion
public FancyBalloon()
{
InitializeComponent();
}
/// <summary>
/// Resolves the <see cref="TaskbarIcon"/> that displayed
/// the balloon and requests a close action.
/// </summary>
private void imgClose_MouseDown(object sender, MouseButtonEventArgs e)
{
//the tray icon assigned this attached property to simplify access
TaskbarIcon taskbarIcon = TaskbarIcon.GetParentTaskbarIcon(this);
taskbarIcon.CloseBalloon();
}
}
}

View File

@@ -102,7 +102,7 @@
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="Commands\TaskbarIconCommands.cs" />
<Compile Include="Commands\CommandBase.cs" />
<Compile Include="Commands\HideMainWindowCommand.cs" />
<Compile Include="Commands\ShowMainWindowCommand.cs" />
<Compile Include="FancyBalloon.xaml.cs">
@@ -153,6 +153,9 @@
<Resource Include="Images\Add.png" />
<Resource Include="Images\Remove.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Images\Close.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="Samples\" />
</ItemGroup>

View File

@@ -31,7 +31,7 @@
x:Key="tbMenu">
<MenuItem
Header="Show Main Window"
Command="{Binding Source={x:Static Commands:TaskbarIconCommands.ShowMain}}">
Command="{Commands:ShowMainWindowCommand}">
<MenuItem.Icon>
<Image
Width="16"
@@ -44,7 +44,7 @@
<MenuItem
Header="Hide Main Window"
Command="{Binding Source={x:Static Commands:TaskbarIconCommands.HideMain}}">
Command="{Commands:HideMainWindowCommand}">
<MenuItem.Icon>
<Image
Width="16"

View File

@@ -43,7 +43,7 @@
<Window.Triggers>
<EventTrigger
RoutedEvent="tb:TaskbarIcon.TrayToolTipOpen"
SourceName="tb" />
SourceName="tb" />
</Window.Triggers>
<Grid>
@@ -52,8 +52,9 @@
<!--
THE TASKBARICON ELEMENT WAS DECLARED INLINE IN ORDER TO USE DATABINDING
FOR ITS PROPERTIES. IN A REAL-LIFE APP, YOU'D PROBABLY RATHER CREATE IT THROUGH
CODE OR DECLARE IT IN A RESOURCE DICTIONARY.
FOR ITS PROPERTIES. IN A REAL-LIFE APP, YOU'D PROBABLY RATHER DECLARE
IT IN A RESOURCE DICTIONARY SO YOU CAN EVEN USE IT IF THERE IS NO WINDOW
OPEN.
-->
<tb:TaskbarIcon
@@ -65,7 +66,7 @@
Visibility="{Binding Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}, ElementName=iconVisibility, Mode=Default}"
MenuActivation="{Binding Path=SelectedItem, ElementName=lstMenuTrigger, Mode=Default}"
PopupActivation="{Binding Path=SelectedItem, ElementName=lstPopupTrigger, Mode=Default}"
DoubleClickCommand="{Binding Source={x:Static Commands:TaskbarIconCommands.ShowMain}}"
DoubleClickCommand="{Commands:ShowMainWindowCommand}"
>
<tb:TaskbarIcon.TrayPopup>
@@ -148,7 +149,7 @@
<TextBox
Margin="125,0,17,76"
x:Name="txtBalloonText" AcceptsReturn="True" Height="47" VerticalAlignment="Bottom" d:LayoutOverrides="VerticalAlignment"
TextWrapping="Wrap">You should see a grey LED icon in your system tray. This is your NotifyIcon</TextBox>
TextWrapping="Wrap" Text="You should see a LED icon in your system tray. This is your NotifyIcon."/>
<RadioButton
HorizontalAlignment="Left"
Margin="14,0,0,54"
@@ -379,10 +380,12 @@
<TextBlock Margin="12,0,10,10" VerticalAlignment="Bottom" Height="22.42" TextWrapping="Wrap" FontWeight="Bold" Foreground="#FFFFA051"><Run Text="Copyright (c) 2009 Philipp Sumi. This is prerelease software, realeased under the CodeProject Open License (CPOL)" Language="de-ch"/></TextBlock>
<Grid HorizontalAlignment="Right" Margin="0,0,10,262.42" VerticalAlignment="Bottom" Width="358" Height="87.2" x:Name="CustomBalloons">
<Border HorizontalAlignment="Stretch" Width="Auto" BorderThickness="2,2,2,2" BorderBrush="#FF000000"/>
<Button Content="Display Message" x:Name="showCustomBalloon"
Click="showCustomBalloon_Click" HorizontalAlignment="Right" Margin="0,0,24.377,10.52" Width="107.623" Height="23" VerticalAlignment="Bottom" />
<Button Content="Show" x:Name="showCustomBalloon"
Click="showCustomBalloon_Click" HorizontalAlignment="Right" Margin="0,0,91.377,10" Width="71.623" Height="23" VerticalAlignment="Bottom" />
<TextBox VerticalAlignment="Bottom" Height="23" Text="WPF Balloon" TextWrapping="Wrap" Margin="10,0,173,10" x:Name="customBalloonTitle"/>
<TextBlock Margin="10,10,24.377,0" VerticalAlignment="Top" TextWrapping="Wrap"><Run Text="Custom Balloon Tips are more flexible then standard tips when it comes to styling..." Language="de-ch"/></TextBlock>
<Button Content="Hide" x:Name="hideCustomBalloon"
Click="hideCustomBalloon_Click" HorizontalAlignment="Right" Margin="0,0,9.754,10.52" Width="71.623" Height="23" VerticalAlignment="Bottom" />
</Grid>
</Grid>
</Window>

View File

@@ -66,5 +66,10 @@ namespace Sample_Project
//show and close after 2.5 seconds
tb.ShowCustomBalloon(balloon, PopupAnimation.Slide, 5000);
}
private void hideCustomBalloon_Click(object sender, RoutedEventArgs e)
{
tb.CloseBalloon();
}
}
}