65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF/XAML 通知图标和任务栏(系统托盘)弹出窗口

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (77投票s)

2009 年 5 月 28 日

CPOL

14分钟阅读

viewsIcon

302700

downloadIcon

18680

将 WPF 和 XAML 与 Windows Forms 的 NotifyIcon 控件集成,在鼠标悬停在 NotifyIcon 上时显示一个时尚、风格化的弹出窗口。

WPF_xaml_taskbar_window/WPF_xaml_popup.jpg

目录

  1. 引言
  2. 背景
  3. 必备组件
  4. 运行演示
  5. Using the Code
  6. 关注点
  7. 已知问题
  8. 历史

引言

我想创建一个 WPF 应用程序,该应用程序“驻留”在 Windows 系统托盘中,并在鼠标移动到任务栏图标上方时显示一个淡入淡出的弹出窗口。此外,我希望用户体验流畅而顺滑。我曾看到过 Live Mesh 任务栏客户端,非常喜欢它的工作方式,并认为类似的东西非常适合我设想的应用程序。

我已分享此示例,以便为有类似需求的项目提供一个良好的开端。

背景

创建这个弹出窗口比预期的要困难,因为涉及大量琐碎的工作。外观和感觉并没有完全达到我想要的效果,窗口动画最初也非常笨拙。回想起来,我应该先在纸上或 Visio 中勾勒出设计和故事板,然后再开始动手。在使用 WPF 的设计器时,XAML 会迅速膨胀,而且真的需要小心如何组合 UI 元素和动画,以保持可维护性和灵活性。

还出现了一些意想不到的挑战,起初,实现流畅顺滑界面的总体目标似乎遥不可及。经过初步调查和一些探索性代码,我发现

  • WPF 不支持原生的任务栏/系统托盘图标,需要利用 Windows Forms 的实现。
  • 标准的 NotifyIcon 控件不支持鼠标离开事件。
  • 动画 WPF 窗口实现无缝淡入淡出并处理中断非常困难。

必备组件

  1. .NET Framework 3.0 或更高版本
  2. Windows XP、2003、Vista、2008

运行演示

从存档中解压演示,然后运行 WpfXamlPopup.exe。以下图表和相应的要点将引导您了解功能和用户界面。

Funky Popup window

Yellow1 将鼠标悬停在任务栏图标上将导致弹出窗口显示。
Yellow2 将鼠标移开任务栏图标后,会有一瞬间的延迟,然后弹出窗口会淡出。短暂的延迟允许鼠标无缝地移动到桌面和弹出窗口上。这为淡出被取消争取了时间,用户可以进一步与弹出窗口进行交互。
Yellow3 “固定”按钮会将弹出窗口固定打开,而不管鼠标指针在桌面的哪个位置。
Yellow4 “关闭”按钮会卸载弹出窗口和任务栏图标,并关闭应用程序。
Yellow5 可用于应用程序的示例按钮 - 我想创建一个按钮似乎“弹出”到焦点中的效果。
Yellow6 此单选按钮组演示了如何轻松更改任务栏图标。

Using the Code

首次打开解决方案文件(源代码)时,请生成解决方案,以便正确解析项目引用,并在设计器中查看弹出窗口。代码已进行了详细注释,因此我不会过多介绍,但我会解释总体架构和项目结构,以帮助您快速了解解决方案。

提供的解决方案 WPF XAML Notify Icon and Popup.sln 包含两个项目:

  • ExtendedWindowsControls:一个类库,它公开 Windows Forms 的 NofityIcon 控件,并用附加方法对其进行装饰,以满足本项目所需的交互。理想情况下,可以继承 Windows Forms 的 NotifyIcon 类,但不幸的是,它是一个密封类,不允许继承。
  • WpfXamlPopup:主项目,如果您希望直接从 Visual Studio 运行应用程序,应将其设置为启动项目。该项目包含弹出窗口、淡入淡出动画和视觉样式资源。

我将逐一讨论代码文件,按下面的项目编号。

Solution View

项目:ExtendedWindowsControls - 此项目提供了 Windows Forms 控件扩展的容器。
Green 1 ExtendedNotifyIcon.cs:
  • MouseLeave 事件是此类中的关键,因此我将从这里开始。
    为了实现我想要的弹出窗口行为,我必须在 Windows Forms 的 NotifyIcon 上构建功能,以添加 MouseLeave 事件。默认情况下,NotifyIcon 只有以下鼠标事件:MouseClick、MouseDoubleClick、MouseDown、MouseMove 和 MouseUp。更复杂的是,我不想让新的 MouseLeave 事件立即触发,而是允许延迟后再触发。此延迟对于 WpfXamlPopup 项目中的弹出窗口至关重要,因为它为鼠标离开任务栏中的 NotifyIcon 提供了时间缓冲,从而实现了三个重要功能:
    • 用户可以在短时间内离开并重新回到图标上,而不会导致弹出窗口关闭(例如,如果用户不小心将鼠标移开然后又移回图标;或者短暂离开图标,然后改变主意又回到图标上)。
    • 消除鼠标悬停在图标边缘时有时出现的重复命中/未命中场景引起的闪烁/闪烁。
    • 用户可以从图标移到弹出窗口,而不会导致弹出窗口关闭,即,如果用户在延迟时间内从图标移到弹出窗口,则弹出窗口会保持打开状态,以实现无缝过渡。
  • ExtendedNotifyIcon(int millisecondsToDelayMouseLeaveEvent) 是构造函数,它设置 Windows Forms NotifyIcon 控件的实例,并关联用于跟踪鼠标位置的 MouseMove 事件。构造函数还设置 delayMouseLeaveEventTimer 定时器,该定时器用于计算鼠标离开/退出图标上方的时间延迟以及 MouseLeave 事件的触发。链式构造函数将默认延迟设置为 100 毫秒。
  • targetNotifyIcon_MouseMove 方法会拦截 NotifyIcon 上的 MouseMove 事件,并跟踪屏幕上的鼠标位置。它还会启动定时器(delayMouseLeaveEventTimer.Start)。当定时器触发时,它会调用 delayMouseLeaveEventTimer_Tick 方法,该方法会检查鼠标是否仍然位于 NotifyIcon 上方。如果是,则忽略任何操作。如果不是,它将触发 MouseLeave 事件。认识到这一点之所以有效,主要是因为两个原因:
    • 如果定时器过期且鼠标仍然悬停在 NotifyIcon 上方,则不执行任何操作。
    • 当鼠标移到图标上方时,它会不断重置定时器并跟踪鼠标 - 这意味着当鼠标从图标上方移开时,离开事件的定时器将从头开始计时。
  • StartMouseLeaveTimerStopMouseLeaveEventFromFiring 方法为调用代码提供自定义 MouseLeave 事件的手动覆盖。
  • IDisposable 接口:如果 Windows NotifyIcon 没有被释放,它将在应用程序终止后保持可见,并且只有在用户将鼠标悬停在图标上后才会消失。
项目:WpfXamlPopup - 这是主项目,包含 WPF 弹出窗口。
Yellow 1 Images 文件夹包含项目中使用的所有图像。其中包括用于更改任务栏图标外观的各种彩色球体,以及用于固定和取消固定操作的按钮图像。
Yellow 2 App.xaml:包含对资源字典中存储的各种可重用 XAML 组件的引用。这些组件包括:SlickButtonRD.xamlMainGridStyleRD.xamlStealthButtonRD.xaml - 详见下文。
Yellow 3 HorizontalSeparatorRD.xaml:一个简单的用户控件,使用矩形的组合来生成带有“浮雕”效果的分隔线。
Yellow 4 MainGridStyleRD.xaml:一个资源字典,保存主网格的样式。主网格构成了弹出窗口的基础,并定义了
  • LinearGradientBrush:用于生成弹出窗口的渐变背景。
  • OuterGlowBitmapEffect:用于生成窗口周围的阴影,使其从背景中脱颖而出。
  • MainGridBorder:一种样式,用于设置圆角、边框和默认背景。
Yellow 5 MainNotifyWindow.xaml:这是主窗体的 XAML。由于有了资源字典文件和用户控件,此主窗口 XAML 简洁、优雅且易于理解。在 WPF 的这次简短探索中,我发现 XAML 文件很快就会变得笨重且难以管理。如果您使用 Blend,这会更加麻烦,因为它很容易生成大量无关的、未使用的/孤立的工件,清理起来会很费时。
  • 两个故事板 <Storyboard x:Key="gridFadeInStoryBoard"><Storyboard x:Key="gridFadeOutStoryBoard"> 简单地将主网格淡入淡出,以提供淡入/淡出效果。这些在代码隐藏中进行处理(有关详细信息,请参阅下文)。
  • <Grid x:Name="uiGridMain" Margin="10"> 是弹出窗口的“框架”,定义了分隔线、按钮和窗口内容的区域。它使用 MainGridStyleRD.xaml 中的 MainGridBorder 资源定义进行样式化。
  • SlickToggleButtonsSlickButtonRD.xaml 中定义,并用于弹出窗口的固定和关闭控件框选项。
  • 简单的 Label 提供了弹出窗口的标题。
  • 两个 StackPanels 定义了窗口的主要内容区域。
    • 第一个包含用于在任务栏中切换图标图像的单选按钮。
    • 第二个包含一系列可用于弹出窗口各种操作的按钮。
Yellow 6

MainNotifyWindow.cs

  • MainNotifyWindow() 是构造函数,它通过以下方式设置弹出应用程序:
    • NotifyIcon 添加到任务栏并注册相关的事件以调用弹出窗口。
    • 将弹出窗口的位置设置在屏幕的右下角。
    • 将弹出窗口的默认状态设置为隐藏,方法是将窗口和主网格的不透明度设置为 0。
    • 缓存弹出窗口所需的动画,并将事件与动画完成时的操作关联起来。
  • SetNotifyIcon(string iconPrefix) 通过接受一个前缀并附加 Orb.ico 来更改实际图标,以生成一个已内置到资源文件中的图标名称。例如,“BlueOrb.Ico”,然后使用 pack:// URI 表示法检索并分配给 NotifyIcon
  • SetWindowToBottomRightOfScreen() 将弹出窗口定位在屏幕的右下角。
  • extendedNotifyIcon_OnShowWindow() 处理鼠标移动到任务栏中的 NotifyIcon 时触发的事件。如果没有动画正在执行(uiGridMain.Opacity > 0 && uiGridMain.Opacity < 1),则处理程序开始动画故事板以淡入弹出窗口。在这种情况下,将立即显示弹出窗口和主网格,以提供即时访问弹出窗口。这种情况通常发生在用户无意中将鼠标移出 NotifyIcon,看到弹出窗口开始淡出,然后迅速将焦点移回图标以“恢复”窗口时。
  • extendedNotifyIcon_OnHideWindow() 处理鼠标离开任务栏中的 NotifyIcon 或鼠标离开弹出窗口时触发的事件。如果窗口未固定打开,则处理程序开始动画故事板以淡出弹出窗口。与上面的 extendedNotifyIcon_OnShowWindow() 方法一样,此处理程序仅在弹出窗口完全可见时才进行动画处理,以避免将窗口的不透明度重置为完全不透明,从而有效重启动画,导致可怕的闪烁。
  • uiWindowMainNotification_MouseEnter(...) 处理鼠标进入弹出窗口时触发的事件。在这种情况下,用户显然希望与窗口进行交互,所有“关闭/淡出”事件都会被取消,并且窗口的主网格设置为完全不透明。
  • uiWindowMainNotification_MouseLeave(...) 处理鼠标离开弹出窗口时触发的事件。在这种情况下,假设用户已完成与弹出窗口的交互,并发出指令以淡出弹出窗口。
  • gridFadeOutStoryBoard_Completed(...) 此事件通过将窗口的 Opacity = 0; 来确保窗口已隐藏。
  • gridFadeInStoryBoard_Completed(...) 此事件通过将窗口的 Opacity = 1; 来确保窗口在淡入后完全可见。
  • PinButton_Click(...) 确保在固定按钮上显示正确的图像以表示按钮的状态(已选中/未选中)。
  • colourRadioButton_Click(...) 切换任务栏中 NotifyIcon 使用的图像。
  • CloseButton_Click(...) 执行任务栏图标的必要清理并关闭应用程序。
Yellow 7 SlickButtonControl.csSlickToggleButton 控件通过添加附加属性来扩展标准的切换按钮。这些属性由 SlickButtonRD.xaml 中定义的样式使用。通过扩展 ToggleButton,我消除了在需要对按钮进行细微更改(例如,具有不同的背景颜色和不同的圆角配置文件)时使用多个样式的需求。
Yellow 8 SlickButtonRD.xaml:此资源字典包含控件框中切换按钮的样式,并用于关闭和固定按钮。我不会详细介绍,但这些按钮很复杂,是通过一个包含 3 个边框元素的控件模板构建的,用于创建背景、边框样式,然后是用于显示按钮内容的 content target。暴露的 Triggers 处理 MouseOverChecked 属性,以创建视觉上吸引人的按钮控件。
Yellow 9 StealthButtonRD.xaml:此资源字典包含弹出窗口底部按钮的样式。这些按钮看起来并不显眼,但当鼠标悬停在它们上方时,它们会“活起来”。这是通过使用“休眠/正常”样式和“鼠标悬停”样式来实现的。带有 ThicknessAnimation 和按钮边距操作的故事板提供了“弹出”效果。

关注点

  • 总的来说,我对这个小应用程序的工作方式很满意。动画流畅,任务栏图标响应迅速,用户体验具有优质感。
  • 将来,在开始 WPF 或 Silverlight 项目之前,我会在白板上勾勒概念。我发现尝试在画布上布局项目,然后对其进行移动和更改样式非常耗时、混乱且繁琐。我发现先在其他画布(或纸上)勾勒出概念,然后从中生成 WPF 设计要容易得多。
  • WPF 在最大化窗口时占用内存可能非常高。即便如此,内存管理器在窗口隐藏或最小化时也能很好地释放资源。
  • 一个具有平滑边缘和可靠动画的流畅 WPF 设计看起来很棒——但需要付出代价。我花在这个项目上的时间比预期的要长得多,而且我不禁想到,很多 WPF 项目会因为低估用户界面需求而延期。
  • 将 Windows Forms NotifyIcon 与 WPF 集成非常简单且有效。
  • 我发现 Blend 设计器在开始时确实帮助了我很多,但后来在处理更技术性的项目时却让我陷入困境,而且它还生成了多余的代码。我实际上是在 Blend 中开始的,然后在 Visual Studio 中重构并完成,因为 Blend 实在太混乱了,而且我觉得我无法很好地控制组件的分离以及 Blend 中更棘手的样式工作。

已知问题

  • 窗口可见并使用时内存使用量可能很高——我还没有找到更好的管理方法。
  • 有时窗口(固定打开时)会失去其最顶层位置,并被另一个窗口遮挡。我不确定这是我的问题还是 .NET Framework 的问题。如果您遇到相同的问题或以前见过,请告诉我。

历史

  • V1.0 - 初始发布
  • V1.1 - 任务栏集成将在 .NET 4 中发布,应该允许此解决方案摆脱其 WinForms 依赖。有关 WPF 4.0 功能的更多信息,请参阅演示文稿 http://mschannel9.vo.msecnd.net/o9/mix/09/pptx/t39f.pptx 或查看 MIX 的视频 http://videos.visitmix.com/MIX09/T39F
  • V1.2 (2010 年 8 月 4 日) - 我已测试将项目升级到 Visual Studio 2010 并以 .NET 4.0 框架为目标(您必须在项目属性中设置)。升级过程快速而简单,并且应用程序在 .NET 4 上仍能完美运行。太棒了!
  • V1.3 (2015 年 11 月 4 日) - 我再次测试了将项目升级到 Visual Studio 2015 并以 .Net 4.6 框架为目标。升级过程快速而简单,并且应用程序在 .Net 4.6 上仍能完美运行。太棒了!

希望您喜欢这篇文章和示例。非常欢迎提出建议、问题和评论,也欢迎您对文章和代码评分。

© . All rights reserved.