原链接:https://www.codeproject.com/Articles/36468/WPF-NotifyIcon-2 全文由笔者机翻+小学英语润色,如果有建议欢迎提出! 特别感谢:Lyt99、旅人 更新时间:2020年11月17日 以下正文。

WPF NotifyIcon

一个为WPF准备的,利用了该平台若干特性的NotifyIcon。

可以在以下平台下载到最新、最好用的版本:
GitHub: https://github.com/hardcodet/wpf-notifyicon
NuGet: https://www.nuget.org/packages/Hardcodet.NotifyIcon.Wpf

介绍

这是WPF平台的NotifyIcon(系统托盘图标)的另一种实现方式,它不依赖于相应的WinForms组件,而是一个完全独立的控件。
它利用WPF框架的一些特性来显示丰富的Tooltips、弹出窗口、上下文菜单(右键菜单)和气球消息。
ToolTip.png

开发背景

在我开发我的NetDrives工具(顺便说一句,它也是开源的)期间,我发现在WPF的名称空间中并没有内置可用的NotifyIcon——我不得不再利用WinForms名称空间中的组件。
到目前为止,一切都很好。但很快我就发现,这样做会无法应用一些新的特性,包括:

  • 丰富的Tooltips,而不是使用文本
  • WPF的上下文菜单和弹出窗口
  • 命令的支持和路由事件
  • 灵活的数据Binding
  • 丰富的气球消息,而不是操作系统提供的默认消息

由于上述的问题,我开始着手创建这个纯粹依靠WPF的解决方案,来填补这部分的空白。所以就有了这个项目:)

内容目录

(虽然语雀支持直接看,但是原文既然写了我也就搬过来吧)

  • 文章和例子
  • 项目页面/备选方案
  • 控件实现的概述
  • Win32 API——名称空间 Interop
  • WPF API——TaskbarIcon Class
  • 教程第1部分
    • Hello NotifyIcon
    • 修改图标的可见性
    • 正确地关闭NotifyIcon
  • 教程第2部分
    • ToolTips
    • 弹出窗口
    • 上下文菜单
    • 气球提示
  • 教程第3部分
    • 内置命令支持
    • 数据Binding
    • 事件
  • 后记
  • 历史

文章和例子

本文将简要地讨论该控件的实现和结构,并对其各种特性进行了描述。教程会从一些常见的内容(声明、设置图标、ToolTips等)开始,然后深入一些更高级的情况(数据Binding、命令、事件)。
由于控件特性的数量较多,阅读完可能会需要一定的时间。不过,您现在可以立即开始,一步步地探索不同的特性,示例程序也将在本文后给出。
大体上,您可以立即尝试本文教程部分列出的所有内容,并且您也可以在示例程序的Tutorials文件找到所有的源码:
Solution.png

项目页面/备选方案

项目页面

这篇文章已经有好几年的历史了,虽然我会保证这里的文档是最新的,但是控件开发的参考页面在这里:https://github.com/hardcodet/wpf-notifyicon

备选方案

我还想提到的是,其实还有许多可替代的解决方案。但到目前为止,除了一个项目之外,所有的基本上都是WinForms NotifyIcon的包装器,它们提供了你可能从WinForms项目中了解过的基本功能。
一些比较显著的项目链接(不完整的列表):

控件实现的概述

基本上,实现可分为两个方面:

  • Interop名称空间的类中包含着通过Win32 API的Shell_NotifyIcon函数配置的NotifyIcon的代码。这些类由控件的内部使用。
  • 项目文件夹中的类提供了WPF APi。这些是您将使用的公共API。这里最主要的是TaskbarIcon类,它展示了您的NotifyIcon。

Classes.png

Win32 API——名称空间 Interop

就像WinForms的NotifyIcon一样,这个控件基本上也是一个Windows API的Shell_NotifyIcon函数的包装器。不过,它支持最新的改进(支持到Vista),包括更丰富的ToolTips(带有Windows XP的回退机制)和气球消息的自定义图标。
如果你想开始自己来实现(无论是否用WPF),则基本上可以使用Interop名称空间的代码以及Util类中的helper方法,并且应该会做的很好:那里的类和枚举为您提供了Shell_NotifyIcon和相关类的干净整洁的外观。如果要调用Shell_NotifyIcon,可以通过Util类中的WriteIconData方法来执行此操作。

  1. /// 使用提供的数据更新任务栏图标
  2. /// <see cref="NotifyIconData"/> 实例.
  3. /// </summary>
  4. /// <param name="data">NotifyIcon配置的设置</param>
  5. /// <param name="command">对图标的操作(例如:删除图标)</param>
  6. /// <returns>如果数据已成功写入,则为True</returns>
  7. /// <remarks>有关详细信息,请参见MSDN上的Shell_NotifyIcon文档。</remarks>
  8. public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command)
  9. {
  10. return WriteIconData(ref data, command, data.ValidMembers);
  11. }
  12. /// <summary>
  13. /// 使用提供的数据更新任务栏图标
  14. /// <see cref="NotifyIconData"/> 实例.
  15. /// </summary>
  16. /// <param name="data">NotifyIcon配置的设置</param>
  17. /// <param name="command">对图标的操作(例如:删除图标)</param>
  18. /// <param name="flags">定义<paramref name="data"/>
  19. /// 结构中的哪些成员的已经被设定</param>
  20. /// <returns>如果数据已成功写入,则为True</returns>
  21. /// <remarks>有关详细信息,请参见MSDN上的Shell_NotifyIcon文档。</remarks>
  22. public static bool WriteIconData(ref NotifyIconData data, NotifyCommand command, IconDataMembers flags)
  23. {
  24. //如果在设计模式下则不执行任何操作
  25. if (IsDesignMode) return true;
  26. data.ValidMembers = flags;
  27. lock (SyncRoot)
  28. {
  29. return WinApi.Shell_NotifyIcon(command, ref data);
  30. }
  31. }

WPF API——TaskbarIcon Class

作为控件的用户,您压根儿不需要处理Win32 interna。TaskbarIcon类代表了NotifyIcon,并且它公开了每个WPF开发人员都应该熟悉的干净的API。
以下是该类的属性和事件的概述:
TaskbarIcon.png
这里有大量可用的属性和事件,但是不用担心——如果您不愿意,则不必处理其中的大部分内容。基本上,该控件为您提供了以下区域的功能,这些区域都是可选的(甚至显示的图标本身):

  • 在托盘区域中显示一个图标
  • 如果用户将鼠标悬停在任务栏图标上,则显示ToolTips
  • 如果用户点击NotifyIcon,则打开上下文菜单
  • 如果用户点击NotifyIcon,则打开交互式Popup控件
  • 如果触发事件,则在托盘区域中显示气球消息(标准气球提示或自定义的气球)

教程第1部分:基础知识

Hello NotifyIcon

首先,让我们来看看如何创建您的第一个NotifyIcon。您可以通过编程方式或声明方式来完成。

用代码创建NotifyIcon

//注意: 除了最简单的方案外,建议在所有的方案中使用XAML来完成
TaskbarIcon tbi = new TaskbarIcon();
tbi.Icon = Resources.Error;
tbi.ToolTipText = "hello world";

用XAML创建NotifyIcon

为了在XAML中声明TaskbarIcon,您必须先在XAML文件的开头处添加以下名称空间的声明:

xmlns:tb="http://www.hardcodet.net/taskbar"

然后只需要声明带有tb前缀的TaskbarIcon即可(此处笔者忽略了部分代码):

<Window xmlns:tb="http://www.hardcodet.net/taskbar">
    <Grid>
        <!--为了创建一个NotifyIcon,您所需要做的就是声明名称空间和一个简单的声明-->
        <tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world" />
    </Grid>
</Window>

仅需上面的代码片段,您就可以在托盘区域显示NotifyIcon了:
BasicDeclaration.png

注意:
如果仔细观察,您会发现同样是设置图标的图像,在C#代码中,是通过设置属性Icon完成的;在XAML中,则是通过设置依赖属性IconSource完成的。
这两个属性之间的区别是Icon是带有System.Drawing.Icon实例的标准属性,而IconSource是类型为ImageSource的WPF 依赖属性,它更适合在XAML中使用。 结果虽然是一样的,但是:设置IconSource时会自动设置Icon属性。

从资源字典创建NotifyIcon

这基本上是代码和标记的结合,也是我推荐在实际情况应用的方式:

  • 在XAML的资源字典中声明TaskbarIcon
  • 在程序初始化期间在代码中查找它

这种模式允许您将NotifyIcon与应用程序的窗口分开,并且允许您在应用程序在后台运行时关闭所有窗口。

<ResourceDictionary 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">
    <!-- 全局声明的托盘图标 -->
    <tb:TaskbarIcon x:Key="MyNotifyIcon" IconSource="/Icons/Error.ico" ToolTipText="hello world" />
</ResourceDictionary>

笔者注: 添加在App.xaml文件的内,上述代码的第一个和第二个命名空间默认就引用了,应该可以不用再写。

声明为全局资源之后,您就可以使用FindResource方法查找NotifyIcon:

public class App
{
    private TaskbarIcon tb;

    private void InitApplication()
    {
        //初始化 NotifyIcon
        tb = (TaskbarIcon)FindResource("MyNotifyIcon");
    }
}

一种优雅地提供交互的解决方案是,通过在XAML中或者以编程方式设置其DataContext,将view model分配给NotifyIcon控件。可以在下载的程序实例中的windowless application sample(Github链接:https://github.com/hardcodet/wpf-notifyicon/tree/master/src/Windowless%20Sample)看到。

修改图标的可见性

在程序运行时,您始终可以通过设置TaskbarIcon的Visibility属性(默认值为Visibility.Visible)来修改NotifyIcon在托盘中的显示状态。
这是在XAML中进行操作的方法:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" Visibility="Collapsed" />

这是对应的C#代码:

TaskbarIcon tbi = new TaskbarIcon();
tbi.Icon = Resources.Error;
tbi.Visibility = Visibility.Collapsed;

正确地关闭NotifyIcon

在创建之后,NotifyIcon会一直存在(无论是否可见),除非调用TaskbarIcon类的Dispose方法。
如果您关闭应用程序,则此操作会自动进行,因此您根本不必担心(如果您只有一个NotifyIcon,则可以在不需要的时候隐藏它)。
但是,如果要在运行时将其完全删除,则需要手动调用TaskbarIcon类的Dispose方法。

教程第2部分:ToolTips、Popups、上下文菜单、气球提示

教程的第二部分重点介绍了所支持的不同类型的可视化效果。
从这里开始,我将仅展示在XAML中的示例。因为我认为,一旦UI变得更加复杂,用声明的方式来操作要比在代码中操作更有意义。
现在我们来整点好活 :)

ToolTips

ToolTip.png
(NetDrives中动态的ToolTip,提供了快捷的状态摘要)

TaskbarIcon类提供了两个与ToolTip相关的属性:

  • TrayToolTip属性可以使用任何的UIElement,如果用户将鼠标悬停在该区域上,则会显示它。它可以是User Control、按钮、图像控件或者任何其他控件。这是显示更丰富的ToolTip的一种非常方便的方法。但是由于早期版本(XP、2003)中Win32 Shell_NotifyIcon函数的限制,仅从Windows Vista、2008才开始支持它。当然,这在Windows 7上也可以正常工作。
  • ToolTipText属性使用string类型,它在以下两种情况下显示
    • 未使用TrayToolTip属性
    • 在不支持丰富的ToolTip的老版操作系统(XP、2003)上
  • 因此,建议您一定要设置ToolTipText属性,以保证您的应用程序在老版操作系统上也有保障机制。

    笔者注: 原文为:

    • Accordingly, I’d recommend you always set the ToolTipText property in order to have a fallback mechanism if your application runs on an older OS.

    fallback mechanism直译为“后备机制”,但是直接用有点生硬,于是改动了一下。

注意:
由于TaskbarIcon派生于FrameworkElement,因此它也提供了ToolTip属性,该属性可以赋值为任意Object。但请忽略该属性——您仅应该使用TrayToolTipToolTipText属性!

创建ToolTip方式1:内联声明ToolTips

您可以直接在TaskbarIcon的内部声明自定义的ToolTip。
这是一个显示了半透明Border和一个TextBlock的简单示例:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world">
    <!-- 我们可以使用任何的UI元素作为ToolTip,这里我们使用半透明的Border -->
    <tb:TaskbarIcon.TrayToolTip>
        <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Opacity="0.8" Width="160" Height="40">
            <TextBlock Text="hello world" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
    </tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>

效果如图:
SimpleToolTip.png

创建ToolTip方式2:使用UserControl

虽然内联声明ToolTips是可以的,但是您可能希望将ToolTip的UI与NotifyIcon的声明分开。
例如,我们仍然按照上述的内容创建ToolTip,但是这次我们将UI放入名为“SimpleUserControl”的专用UserControl中:

<UserControl x:Class="Samples.Tutorials.ToolTips.SimpleUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- 一个简单的UserControl,在Border中显示固定的文本 -->
    <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Opacity="0.8" Width="160" Height="40">
        <TextBlock Text="hello world" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
</UserControl>

最后,将其指定给NotifyIcon:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world">
    <!-- 将UserControl指定为ToolTip -->
    <tb:TaskbarIcon.TrayToolTip>
        <local:SimpleUserControl/>
    </tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>

创建ToolTip方式3:链接到资源

如果您已将ToolTip控件声明为资源,则声明将变得更加简单:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world" TrayToolTip="{StaticResource TheKeyOfMyToolTipControl}"/>

弹出窗口(Popup)

与ToolTip不同,只有用户点击NotifyIcon时,才会显示弹出窗口。即便用户将鼠标从托盘区域移开,它也会保持打开的状态。而一旦用户点击了其他位置,它就会关闭。因此,您可以在弹出窗口中提供一些交互功能,通常用于快速访问您的应用程序。
Popup.png
(NetDrives的弹出窗口,提供了对所有已配置分享的快速访问功能。用户可以在Grid中选择一个分享、双击一个分享、或者打开Grid的上下文菜单。)

声明弹出窗口与设置ToolTip非常相似,可以通过设置TrayPopup属性将任意UIElement控件指定为弹出窗口。
让我们再用一次上面的内联ToolTip示例,但是这次显示的是Button而不是TextBlock

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world">
    <!-- 我们可以使用任意UI元素作为弹出窗口 弹出窗口将在用户离开托盘区域后保持打开状态 -->
    <tb:TaskbarIcon.TrayPopup>
        <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Width="160" Height="40">
            <Button Content="Click Me!" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </tb:TaskbarIcon.TrayPopup>
</tb:TaskbarIcon>

用户点击NotifyIcon时,上述内容会产生如下的结果(非常难看)。但至少,这确实是个弹出窗口:
SimplePopup.png

激活(显示)弹出窗口

默认情况下,弹出窗口会在鼠标左键点击图标时出现。但是您可以使用PopupActivation属性来控制哪个鼠标按键将会激活您的弹出窗口。
您可以在鼠标左键、中键、右键或几种常见组合之间进行选择。

<!-- 左键双击时打开弹出窗口 -->
<tb:TaskbarIcon TrayPopup="{StaticResource MyPopup}" PopupActivation="LeftOrDoubleClick"/>

PopupActivation.png

上下文菜单

如果用户点击NotifyIcon,则可以显示WPF标准的上下文菜单:
ContextMenu.png
这样没啥特别的——ContextMenu属性是直接派生于FrameworkElement基类的:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world">
    <!-- 设置一个简单的上下文菜单  -->
    <tb:TaskbarIcon.ContextMenu>
        <ContextMenu Background="LightCoral">
            <MenuItem Header="First Menu Item" />
            <MenuItem Header="Second Menu Item" />
        </ContextMenu>
    </tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>

上述代码的效果如下:
SimpleContextMenu.png

激活上下文菜单

默认情况下,上下文菜单由鼠标右键单击显示,但是可以使用MenuActivation属性来控制哪些鼠标按键可以打开右键菜单:

<!-- 鼠标左右键单击都可以打开上下文菜单 -->
<tb:TaskbarIcon ContextMenu="{StaticResource MyMenu}"  MenuActivation="LeftOrRightClick"/>

注意:
如果为PopupActivationMenuActivation定义了冲突的值,则上下文菜单始终优先于弹出窗口显示:

<!-- 弹出窗口仅在双击时显示,左键单击会打开上下文菜单 -->
<tb:TaskbarIcon ContextMenu="{StaticResource MyMenu}" MenuActivation="LeftOrRightClick"
                TrayPopup="{StaticResource MyPopup}"  PopupActivation="LeftOrDoubleClick"/>

气球提示

NotifyIcon支持两种气球提示,可用于在托盘区域显示信息:

  • 标准气球提示,由操作系统定义。
  • 自定义气球提示——就像ToolTips和弹出窗口一样,您可以将任意UIElement转换为气球消息。这不仅意味着您可以按照自己的喜好修改气球的样式,而且得益于NotifyIcon丰富的事件模型,您还可以创建精美的动画。

标准气球提示

TaskbarIcon类提供了两个ShowBalloonTip方法来显示标准气球。一个是显示带有标准图标(信息、警告、错误、无)的气球;另一个重载则带是有自定义System.Drawing.Icon的气球:

private void ShowStandardBalloon()
{
    string title = "WPF NotifyIcon";
    string text = "This is a standard balloon";

    //显示带有内置图标的气球
    MyNotifyIcon.ShowBalloonTip(title, text, BalloonIcon.Error);

    //显示带有自定义图标的气球
    MyNotifyIcon.ShowBalloonTip(title, text, MyNotifyIcon.Icon);


    //隐藏气球
    MyNotifyIcon.HideBalloonTip();
}

这是使用了第二种方法唤出的使用了自定义图标的气球:
StandardBalloon.png

自定义气球提示

要显示自定义气球提示,需要调用TaskbarIcon类的ShowCustomBalloon方法。
ShowCustomBalloon不仅可以显示任意的Element实例,而且还提供了一些现成的基本动画以及一个可以选的时间跨度,该时间跨度定义了气球的显示时间。
CustomBalloon.png

private void ShowCustomBalloon()
{
    FancyBalloon balloon = new FancyBalloon();

    //显示一个气球消息并在4秒后关闭
    MyNotifyIcon.ShowCustomBalloon(balloon, PopupAnimation.Slide, 4000);
}

您还可以通过编程的方式关闭气球或终止关闭的计时器(例如,用户将鼠标悬停在气球上时)。在实例程序中可以看到在几种情况下的展示。

教程第3部分:命令、事件和数据Binding

本教程的最后部分并不完全要求您是WPF忍者,而是假定您熟悉命令、路由事件或数据Binding背后的概念。万一您不知道该如何去做了,只需要在论坛上发布问题,我(希望其他人也可以)将尝试为您指明正确的方向。

内置的命令支持

命令提供了一种对于NotifyIcon上的事件无需连接事件侦听器即可作出反应的简洁方法。TaskbarIcon目前公开了两个可用于分配命令的属性:

  • LeftClickCommand
  • DoubleClickCommand

让我们实现一个利用它们的小栗子:

实施自定义命令

首先,这是一个名为ShowMessageCommand的简单命令,该命令仅显示一个消息对话框。对话框的文本取自命令的参数:

/// <summary>
/// 一个将命令参数作为对话框文本的简单命令
/// </summary>
public class ShowMessageCommand : ICommand
{
    public void Execute(object parameter)
    {
        MessageBox.Show(parameter.ToString());
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
}

声明命令

设置好ShowMessageCommand之后,剩下要做的就是将它与NotifyIcon挂钩。在下面的代码中,我将命令声明为本地资源:

<Window x:Class="Samples.Tutorials.Commands.CommandWindow"
        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"
        xmlns:local="clr-namespace:Samples.Tutorials.Commands"
        Height="300" Width="300">
    <Grid>
        <!-- 将命令声明为本地资源 -->
        <Grid.Resources>
            <local:ShowMessageCommand x:Key="MessageCommand" />
        </Grid.Resources>

        <!-- 声明NotifyIcon并为命令配置参数 -->
        <tb:TaskbarIcon IconSource="/Icons/Error.ico"
                        LeftClickCommand="{StaticResource MessageCommand}"
                        LeftClickCommandParameter="Single left mouse button click."

                        DoubleClickCommand="{StaticResource MessageCommand}"
                        DoubleClickCommandParameter="Double click on NotifyIcon." />
    </Grid>
</Window>

您可以在示例程序中找到该例子(以及本文中的所有其他代码片段)。

LeftClickCommand的延迟

请注意,LeftClickCommand会在短暂延迟后触发(与立即触发的DoubleClickCommand相反)。这是因为在第一次单击和第二次单击之间存在一定的时间间隔,以便操作系统将鼠标操作视为双击。NotifyIcon非常聪明,可以等待此时间,以确保仅当用户在该时间段内未第二次单击时才触发LeftClickCommand

ToolTips、弹出窗口、上下文菜单和自定义气球中的数据Binding

笔者注:此段内容翻译准确性存疑

Binding到TaskbarIcon类来管理您的ToolTips、弹出窗口、上下文菜单和气球提示非常容易。这里有两种变体(在实例应用程序中均有展示):

  • 您可以通过自定义控件中的DataContext进行隐式Binding(如果尚未使用DataContext)。
  • 您可以通过附加的ParentTaskbarIcon属性进行显示Binding。即使DataContext已在使用中,此属性也允许您访问TaskbarIcon。

通过DataContext隐式Binding

为了简化您的数据Binding方案,TaskbarIcon类将按照以下规则设置ToolTips、弹出窗口、上下文菜单和自定义气球消息的DataContext

  1. 如果ToolTips、弹出窗口、上下文菜单和自定义气球消息有着自己的DataContext,则不会改变它。
  2. 如果未使用DataContext:
    1. 如果TaskbarIcon具有DataContext(其自身的DataContext属性不为null),则将其分配给ToolTips、弹出窗口、上下文菜单和自定义气球消息的DataContext
    2. 如果TaskbarIcon没有DataContext(其自身的DataContext属性为null),则将其自身分配为ToolTips、弹出窗口、上下文菜单和自定义气球消息的DataContext

此机制为您在Binding表达式中隐式访问TaskbarIcon或其DataContext提供了一个非常简单的解决方案。
作为一个简单的示例,让我们重新使用第一个教程中的内联ToolTip示例,但是这次,将显示的TextBlock的输出绑定到NotifyIcon的ToolTipText属性:

<tb:TaskbarIcon IconSource="/Icons/Error.ico" ToolTipText="hello world">
    <tb:TaskbarIcon.TrayToolTip>
        <Border>
            <!-- 使用Binding表达式而不是固定文本 -->
            <TextBlock Text="{Binding Path=ToolTipText}" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>
    </tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>

如果您将上面的代码与第一个ToolTip中的教程进行比较,可以看到只有一个Attribute被改变了:

Text="{Binding Path=ToolTipText}"

该Binding语句可以用的原因是TaskbarIcon类成为了TextBlock控件的DataContext
这是在运行时发生的情况:

  • Border控件指定给TaskbarIconTrayToolTip属性。
  • NotifyIcon检查该Border控件的DataContext是否已被设置。并不是这种情况
  • 结果,TaskbarIcon检查是否设置了自己的DataContext
  • 因为TaskbarIcon本身也没有DataContext,所以将自身分配为Border控件的DataContext
  • TextBlockBorder控件的子级。这样,它派生了DataContext。因此,它可以隐式绑定到TaskbarIconToolTipText属性。

显式Binding:附加属性ParentTaskbarIcon

另一个更明确的解决方案是使用附加属性ParentTaskbarIcon。如果ToolTips、弹出窗口、上下文菜单和自定义气球消息由TaskbarIcon管理,则TaskbarIcon将通过该附加属性进行分配。
您可以在代码或数据Binding表达式中访问此属性。
作为示例,这次使用相同的ToolTip例子,但通过ParentTaskbarIcon属性显式访问NotifyIcon:

<!-- 该NotifyIcon设置了DataContext——不能进行隐式Binding -->
<tb:TaskbarIcon x:Name="MyNotifyIcon2" DataContext="WPF IS GREAT: " IconSource="/Icons/Inactive.ico" 
                ToolTipText="{Binding ElementName=txtToolTip, Path=Text}">

    <tb:TaskbarIcon.TrayToolTip>

        <!-- 特别重要:附加属性已经指定给Border!NotifyIcon不会接触基础控件 -->
        <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Opacity="0.8" Width="160" Height="40">

            <!-- 隐式访问DataContext(这次是一个string) -->
            <TextBlock Text="{Binding}">

                <!-- 显式访问NotifyIcon -->
                <TextBlock 
                    Text="{Binding RelativeSource={
                    RelativeSource FindAncestor,
                    AncestorType={x:Type Border}},
                    Path=(tb:TaskbarIcon.ParentTaskbarIcon).ToolTipText}"

                    HorizontalAlignment="Center" VerticalAlignment="Center"/>

            </TextBlock>

        </Border>

    </tb:TaskbarIcon.TrayToolTip>

</tb:TaskbarIcon>

注意Binding表达式,为了得到附加属性,它在这里要复杂一些。TextBlock需要解析其父Border(已经为其分配了附加属性),以便获得附加属性:

<TextBlock 
    Text="{Binding RelativeSource={
    RelativeSource FindAncestor,
    AncestorType={x:Type Border}},
    Path=(tb:TaskbarIcon.ParentTaskbarIcon).ToolTipText}"

/>

尽管这种语法有着额外的复杂性,但其仍有优势,因为即使使用了DataContext(例如为了访问ViewModel),也可以确保附加属性始终由您使用。

事件

TaskbarIcon的路由事件

TaskbarIcon类提供了一系列路由事件——NotifyIcon或相关控件发生的所有事情都有很多事件。有关如何声明性地使用这些事件来触发动画的示例,请查看示例应用程序中的教程。
EventTutorial.png

ToolTips、弹出窗口和气球的附加事件

这是该控件精美的WPF特性之一。基本上,附加事件是在不需要编写任何代码的情况下自定义ToolTips、弹出窗口和气球控件上触发的事件。这是一种非常简洁的机制,可以纯粹基于声明的方式触发这些控件中的动画。
我不会在这里详细介绍,而是为您提供以下资源:

  • 使用PopupOpened附加事件,以便在每次显示弹出窗口时触发动画(旋转图标)的示例程序。
  • 自定义气球示例,使用了几个附加事件实现淡入淡出。

我最近发布了另一个示例,是有关使用附加事件在Blend中触发动画的通用教程。您可以在这里找到它。

后记

我希望本文以及随附的示例可以帮助您顺利入门。我真的很喜欢编写控件,希望它能为您的工具箱增加宝贵的内容。Happy coding! :)

历史

(详细的变更日志是解决方案的一部分)

1.0.5 (2013.11.20) Fixes issues with x64, Win 8, Focus

1.0.4 (2009.09.21)

Proper focus for popups, improved compatibility for WinForms scenarios.

1.0.3 (2009.07.02)

Proper support for routed commands including command targets.

1.0.2 (2009.05.18)

Fixed possibly wrong DataContext assignment when intitializing ContextMenus.

1.0.1 (2009.05.15)

Initial CodeProject released