Windows 应用的任务栏托盘常驻与 Toast 通知 —— NotifyIcon 的坑与 AppNotification 的选型
· Go Komura · 任务栏托盘, NotifyIcon, Toast 通知, AppNotification, Windows App SDK, WinForms, WPF, C#, .NET, 常驻应用, Windows 开发, 技术咨询
“处理完成后想不打扰地告知用户”,”即使关闭窗口也希望监控继续运行”——在业务应用中,常驻任务栏托盘(通知区域)和 Toast 通知是一对经常一起出现的经典需求。然而一旦真正着手实现,就会不断冒出各种细节问题:本该关闭的应用无法退出、重启资源管理器后图标消失、开发阶段能正常弹出的 Toast 到了客户环境却不显示。
而且 Toast 通知从 UWP 时代起就一直在经历 API 的更新换代。搜索到的示例代码,如今很可能已经是仅维护级别的旧 API,这种情况并不少见。本文将结合实务中的常见陷阱,整理基于 NotifyIcon 的托盘常驻设计,以及截至 2026 年 Toast 通知 API 的选型思路。
1. 先说结论
- 托盘常驻的基础至今仍是
System.Windows.Forms.NotifyIcon。必须同时设置Icon并将Visible设为true才会显示(默认值为false)。工具提示Text在 .NET 6 及以后最长为 127 个字符(此前为 63 个字符)。12 - WPF 中不存在与 NotifyIcon 对应的控件。官方对照表中明确写着”没有对应控件”。在 csproj 中同时启用
UseWPF和UseWindowsForms,使用 WinForms 的 NotifyIcon,是最小化的配置方案。34 - “即使点击关闭按钮也保留在托盘中”的做法是在
FormClosing中将e.Cancel设为true并调用Hide()。务必单独提供一个明确的退出方式(如托盘菜单中的”退出”),并检查CloseReason,以免阻碍操作系统的关机流程。5 - 截至 2026 年,Toast 通知 API 的官方推荐已经很明确。无论是 WinForms、WPF 还是非打包应用,都推荐使用 Windows App SDK 的
AppNotificationManager(NuGet 包:Microsoft.WindowsAppSDK)。Windows.UI.Notifications处于”仅维护”状态,Community Toolkit 的通知组件已被归档。67 - Windows App SDK 的 AppNotification 即使是没有打包标识的普通 exe 也可以使用。
AppNotificationManager.Default.Register()会自动完成 COM 服务器注册及相当于 AUMID 的处理,因此不再需要旧一代方案中所需的开始菜单快捷方式设置。不过,从提升为管理员权限的进程发送通知不受支持。8910 - Toast 并非”一定会被用户看到”的手段。在开启专注助手(勿扰模式)期间,不会以横幅方式显示,而是直接进入通知中心,用户也可以在设置中直接关闭通知功能。设计上不应把”Toast 一定会显示”作为业务流程的前提。1112
- 无法从 Windows 服务发送 Toast(会话 0 中没有 UI)。标准做法是在用户会话一侧放置常驻托盘代理,通过 IPC 进行通信。13
2. 基于 NotifyIcon 的托盘常驻基础
在任务栏右端的通知区域显示图标的组件是 System.Windows.Forms.NotifyIcon。持续在后台运行的应用会将其用作状态展示与操作菜单的入口。1
private NotifyIcon _trayIcon = null!;
private void InitializeTrayIcon()
{
var menu = new ContextMenuStrip();
menu.Items.Add("打开", null, (_, _) => ShowMainWindow());
menu.Items.Add(new ToolStripSeparator());
menu.Items.Add("退出", null, (_, _) => ExitApplication());
_trayIcon = new NotifyIcon
{
Icon = Properties.Resources.AppIcon, // Icon 为必填项,未设置则不会显示
Text = "订单监控工具 ── 运行中", // 工具提示(.NET 6+ 最多 127 个字符)
ContextMenuStrip = menu,
Visible = true // 默认为 false,设为 true 后才会显示
};
_trayIcon.DoubleClick += (_, _) => ShowMainWindow();
}
需要注意的要点有三个。
- 只有
Icon和Visible = true两者都具备时才会显示。Visible的默认值是false。1 Text有长度限制。 .NET 6 及以后最长为 127 个字符,.NET 5 及更早版本以及 .NET Framework 为 63 个字符,超出会抛出ArgumentException。把过多状态信息塞进工具提示里,可能会在某一刻突然导致异常崩溃。214- 右键菜单通过
ContextMenuStrip关联。 托盘图标往往是用户唯一能看到的入口,因此至少要提供”打开”和”退出”这两个选项。15
还有一点:托盘图标不仅是通知的入口,同时也是一个始终可见的状态展示。由于 Icon 和 Text 可以在运行期间随时替换,如果做到”正常时使用普通图标、异常时切换为警告图标”“在工具提示中显示最近一次处理时间”之类的设计,反映当前状态,那么即便用户错过了 Toast,只要看一眼图标也能知道当前的状态。可以说 Toast 负责的是”告知那一瞬间发生的事”,而托盘图标负责的是”持续展示当前状态”,两者分工不同。
3. “关闭后仍驻留在托盘”的设计与正确的退出路径
常驻应用的经典行为是”点击 × 按钮关闭后,进程并不退出,而是留在托盘中”。这个实现本身只需在 FormClosing 中取消关闭即可轻松写出,但如果写得粗糙,就会导致两类事故:没有任何手段可以退出应用,以及阻碍 Windows 的关机流程。
private bool _exitRequested;
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 如果用户只是点击了 × 按钮,则隐藏而不是关闭
if (!_exitRequested && e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
return;
}
// 通过「退出」菜单或操作系统关机等情况则正常关闭
base.OnFormClosing(e);
}
private void ExitApplication()
{
_exitRequested = true;
_trayIcon.Visible = false;
_trayIcon.Dispose();
Close();
}
FormClosing 在窗体关闭之前触发,可以通过将 e.Cancel 设为 true 来取消关闭。此时务必检查 CloseReason。正确的做法是只在 UserClosing(用户操作)时取消关闭,而对于 WindowsShutDown 等由操作系统引发的关闭则不应取消。516
WPF 中的思路也是一样的,官方文档中直接给出了在 Window.Closing 中将 e.Cancel 设为 true 并调用 Hide() 的隐藏模式。17
另外,常驻应用就其性质而言,总是伴随着”重复启动会导致同一事件被处理两次”的风险。防止重复启动的方法我们在「防止 Windows 应用重复启动 —— 命名互斥体与重复启动时的激活处理」中做过整理。而常驻应用内部(监控循环或定期处理)的构建方式,则可以参考「为什么要在桌面应用中使用 .NET Generic Host 与 BackgroundService」这篇文章。
4. WPF 应用中的托盘常驻
WPF 中没有与 NotifyIcon 对应的控件。这是官方”与 WinForms 控件对应的 WPF 控件”对照表中明确写明”没有对应控件”的规格空白。3
现实中的可行方案有两种。
| 方案 | 概要 | 判断依据 |
|---|---|---|
| 并用 WinForms 的 NotifyIcon | 在 csproj 中同时启用 UseWPF 与 UseWindowsForms |
无额外依赖。只需要图标与菜单时已经足够 |
| 第三方库(如 H.NotifyIcon 等) | 面向 WPF/WinUI 的托盘图标实现 | 想用 XAML 编写、或需要更丰富的弹出效果时选用。需考虑其并非 Microsoft 官方组件 |
前者只需在 SDK 风格的项目文件中并列写出两个属性即可。4
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
之后就可以直接在 WPF 的 Application 中像平常一样 new 出 System.Windows.Forms.NotifyIcon 来使用。虽然菜单部分会变成 WinForms 的 ContextMenuStrip,但对于托盘菜单这种程度的需求来说,实际使用中几乎不会造成问题。
5. 托盘图标的三个陷阱
5.1. 图标默认会进入”隐藏的图标”中 —— 无法通过代码强制常显
“希望自己的图标始终显示在任务栏上”这样的需求必然会出现,但这一点无法通过程序实现。官方文档明确写明:图标默认会被添加到溢出(隐藏图标)区域,能够决定是否将其提升到通知区域(始终显示)的只有用户本人。18
在 Windows 11 中,用户可以在”设置 → 个性化 → 任务栏 → 其他系统任务栏图标”中按应用逐个开关。如果始终显示在运维上很重要,正确的做法是在安装说明或帮助文档中写明”请开启此设置”。
5.2. 资源管理器重启后图标会消失 —— TaskbarCreated
当资源管理器因崩溃或重启而重新创建任务栏时,已经注册的托盘图标会消失。外壳(Shell)在创建任务栏时会向所有顶层窗口广播 TaskbarCreated 消息,因此在 Win32 层面的规则是:收到该消息后应重新注册图标。19
WinForms 的 NotifyIcon 会在内部处理这条消息并自动重新注册,因此通常无需特别在意。如果是自行通过 P/Invoke 调用 Shell_NotifyIcon(或者维护带有托盘图标的原生应用),就需要确认是否已经加入了这段重新注册的逻辑。”重启资源管理器后只有图标消失了,进程依然存活”这种症状,基本上就是这个原因。
5.3. 忘记 Dispose 会导致图标残留
从 Win32 的角度看,不再需要托盘图标时必须显式删除(NIM_DELETE),在 WinForms 中这项工作由 NotifyIcon.Dispose() 承担。18 请确保退出路径一定会经过 Visible = false → Dispose()。如果进程因异常终止等原因没有执行删除操作,图标就会一直以”幽灵图标”的形式残留,直到鼠标悬停上去才消失——这是任何常驻应用开发者迟早都会遇到的场景。
按照第 3 章的代码那样,在”退出”菜单的处理中先清理托盘图标,再调用 Close(),是比较安全的做法。
6. Toast 通知 —— 该使用哪种 API
Toast 通知是显示在屏幕右下角的横幅,并会保留在通知中心中的通知形式。从桌面应用发送 Toast 的方式在过去十年间经历了多次更新换代,搜索结果中至今仍残留着大量过时的示例。以下是截至 2026 年的整理。6
| API | NuGet / 命名空间 | 定位(截至 2026 年) | 判断 |
|---|---|---|---|
| Windows App SDK AppNotification | Microsoft.WindowsAppSDK / Microsoft.Windows.AppNotifications |
对 WinForms、WPF、非打包 Win32 均为官方推荐 | 新项目选它 |
| Community Toolkit Notifications | Microsoft.Toolkit.Uwp.Notifications | 已归档。 不再包含在当前的 Community Toolkit 中,最后一次更新在 2022 年 | 仅用于维护现有代码。不要用于新项目 |
| 直接使用 WinRT | Windows.UI.Notifications(通过 Windows TFM) |
仅维护状态。 主要推荐用于 UWP | 不要用于新项目 |
| NotifyIcon.ShowBalloonTip | System.Windows.Forms |
气泡通知 API。在 Windows 10 中以 Toast 形式显示,在 Windows 11 中则为临时显示,不会保留在通知中心 | 简单用途可以接受,但要注意其行为会随操作系统版本变化 |
有两点补充说明。
ShowBalloonTip属于”虽然是老 API,但其实并未过时”的一类。 在 Windows 10 中,气泡会以 Toast 的形式渲染,在被关闭之前会一直保留在通知中心。而在 Windows 11 中,它更接近传统气泡那种临时显示的效果,消失之后不会保留在通知中心。显示时长(timeout)的设定现在也被忽略,而是遵循操作系统一侧的辅助功能设置。如果只是托盘常驻应用想轻量级地告知自身状态变化,这已经足够;但如果希望”在通知中心留下历史记录”,就要记住在 Windows 11 上它派不上用场。20- 从桌面应用直接使用原生
Windows.UI.Notifications的经典方法,存在一个著名的限制:必须在开始菜单中注册带有 AUMID(AppUserModelID)的快捷方式,否则 Toast 无法显示。21 而下一章介绍的 AppNotification 正是为了消除这一麻烦而出现的。
7. Windows App SDK AppNotification 的最小实现
Windows App SDK 的应用通知 API 即使是未打包的普通 exe,也可以在没有打包标识的情况下运行。8 项目侧的前提条件包括:使用 Windows 专用的 TFM(例如 net8.0-windows10.0.19041.0)、<WindowsPackageType>None</WindowsPackageType>(适用于非打包情况),以及在部署目标上安装了 Windows App SDK 运行时。9
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
// 启动时执行一次即可。处理程序注册 → Register() 的顺序很重要
AppNotificationManager.Default.NotificationInvoked += (_, args) =>
{
// 点击 Toast 或按下按钮都会传到这里(通过 args.Arguments 判断参数)
if (args.Arguments.TryGetValue("action", out var action) && action == "openLog")
{
ShowLogWindow();
}
};
AppNotificationManager.Default.Register();
// 显示通知
var notification = new AppNotificationBuilder()
.AddText("订单导入已完成")
.AddText("新增 12 条,错误 1 条")
.AddButton(new AppNotificationButton("打开日志")
.AddArgument("action", "openLog"))
.BuildNotification();
AppNotificationManager.Default.Show(notification);
实际使用时需要注意以下几点。
NotificationInvoked的订阅要在Register()之前完成。 如果顺序颠倒,当通过点击 Toast 启动应用时,可能会漏掉这个事件。9- 应用退出前应调用
AppNotificationManager.Default.Unregister()。 官方文档明确指出,在退出前调用 Unregister 清理资源,可以确保之后点击通知时应用能够正确启动。请将其与NotifyIcon.Dispose()一并加入第 3 章”退出”菜单的处理逻辑中(如果是要卸载一个今后完全不再使用通知功能的应用,还另外提供了会一并清除注册信息的UnregisterAll())。22 - 对于非打包应用,
Register()会自动完成 COM 服务器注册以及相当于 AUMID 的处理。旧一代(原生 WinRT)所需要的快捷方式设置和注册表登记都不再需要。如果改用 MSIX 打包,反而需要在 Package.appxmanifest 中声明 COM 激活器。9 - 如果选择随附 Windows App SDK 运行时的 self-contained 部署方式,需要格外注意。
AppNotificationManager依赖于 Windows App SDK 的 Singleton 包,官方针对从 self-contained 应用调用时的注意事项有专门说明。在确定部署方式之前请先确认这一点。23 - 不支持从提升为管理员权限的进程发送通知。
Show不会抛出异常,而是静默失败,因此这是一个很难察觉原因的问题。对于需要提升权限的应用,可以考虑仅将通知功能交给未提升权限的进程来处理(这种分离思路我们在「Windows 应用中如何将「需要管理员权限的处理」单独分离出来的具体做法」中有过介绍)。10 - 当 Toast 被点击时,如果应用尚未运行,会新启动一个进程。 在托盘常驻应用中,需要将其与防止重复启动的机制(参见第 3 章)结合起来,设计为把处理转交给已存在实例的方式。
8. 进阶用法 —— 带进度条的 Toast 与通知的清理
8.1. 在 Toast 内部更新进度 —— UpdateAsync
对于”正在导入 120 条订单数据”这类耗时较长的处理,正确的做法不是反复重新弹出通知,而是在一张 Toast 内部改写其中的进度条。AppNotification 为此提供了数据绑定与更新机制。24
整个流程分为三个阶段。首先通过 Bind 系列方法将进度条的各个值设为占位符,并通过 Tag(如有需要也可加上 Group)使通知可被识别,然后将其显示出来。此后只需将 AppNotificationProgressData 传给 UpdateAsync,就只会改写已经显示出来的那个 Toast 的内容。
const string tag = "import-progress";
const string group = "batch";
// 首次显示:将值绑定,并在 Progress 中设置初始值
var notification = new AppNotificationBuilder()
.AddText("正在导入订单数据")
.AddProgressBar(new AppNotificationProgressBar()
.BindStatus().BindValue().BindValueStringOverride())
.SetTag(tag)
.SetGroup(group)
.BuildNotification();
notification.Progress = new AppNotificationProgressData(1) // 序号
{
Status = "导入中...", Value = 0, ValueStringOverride = "0/120 条"
};
AppNotificationManager.Default.Show(notification);
// 每次进度更新时:递增序号并更新
var data = new AppNotificationProgressData(2)
{
Status = "导入中...", Value = 45 / 120.0, ValueStringOverride = "45/120 条"
};
await AppNotificationManager.Default.UpdateAsync(data, tag, group);
序列号是表示更新顺序的非零整数,如果多个更新前后交错到达,会显示编号最大的那一个。这一机制是为了防止异步处理并发发送更新时数值倒退回旧状态,因此请务必让它单调递增。25
8.2. “更新”与”替换”的区别
用相同的 Tag 重新 Show 一条新通知来实现的”替换”,看起来同样能更新外观,但其行为与”更新”并不相同。24
| 替换(用相同 Tag 重新 Show) | 更新(UpdateAsync) | |
|---|---|---|
| 在通知中心内的位置 | 会移动到最前面 | 停留在原位 |
| 可修改的内容 | 包括布局在内的全部内容 | 仅限已绑定的进度条与最前面的文本 |
| 弹出提示 | 可以再次弹出 | 不会再次弹出(静默改写) |
| 用户已经关闭通知的情况 | 总是会发送 | 会失败 |
官方文档给出的建议也很明确:中间过程用更新,完成时用替换。 进度从 50% 变为 65% 时没有必要每次都弹出提示,但”导入完成(3 条错误)”这样的信息,即使用户已经关闭了进度通知,也希望能够送达。因此,在完成时去掉进度条、换成带按钮(如”查看错误详情”)的另一种布局并进行替换,是实务中常见的做法。
8.3. 清理通知中心里残留的旧通知
Toast 会一直堆积在通知中心,直到用户将其清除。如果常驻应用每天发送多条通知,通知中心就会被自家应用的旧通知塞满,反而会把重要通知埋没掉。
只要附加了 Tag / Group,就可以通过代码删除自家应用的通知(可按标签、按分组,或全部删除)。同时也可以设置有效期,因此建议在设计通知发送方式的同时,一并制定诸如”进度类通知在完成时清除”“信息类通知缩短有效期”这样的清理规则。26
9. 将通知”未能送达”的情况纳入设计考虑
Toast 通知并非可靠的传达手段。存在一些完全属于正常情况、但通知不会显示或不会被注意到的路径。
| 情况 | 行为 |
|---|---|
| 开启了专注助手(勿扰模式) | 不会显示横幅,通知会直接进入通知中心 |
| 按应用或按系统整体关闭了通知 | 不会显示 |
| 来自提升为管理员权限进程的 AppNotification | 不受支持,不会显示 |
| 正在运行全屏应用等情况 | 操作系统可能会自行判断并加以抑制 |
勿扰模式在 Windows 10 中称为”专注助手(Focus assist)”,在 Windows 11 中则通过”设置 → 系统 → 通知”中的”勿扰模式”开关来控制,可由用户或组策略控制。甚至还有规则规定,在系统升级后的一小时内会自动开启该模式。11 专注模式的行为并不是”不发送通知”,而是”抑制横幅显示,直接发送到通知中心”,因此只要用户之后打开通知中心,依然能够看到。12
对设计的启示很简单:
- 应把 Toast 当作”用户一旦注意到就能更快响应”的辅助渠道,业务上必需的信息务必确保可以在应用内的界面或列表中查阅。 不要把”Toast 应该已经弹出了”作为业务流程的前提。
- 对于确实需要强制打断用户的通知(例如紧急停止警告),存在通过
AppNotificationScenario.Urgent(重要通知)在获得用户许可的前提下突破勿扰模式的机制,27 但如果滥用,用户就会直接关闭该应用的全部通知。应仅将其用于真正紧急的情形。
关于如何将外部设备的异常等状态在界面上呈现这一更广泛的话题,也可以参考「外部设备状态确认与显示的最佳实践」。
10. 需要从 Windows 服务发送通知时
“常驻服务检测到异常后,希望通过 Toast 告知已登录的用户”是一个很自然的需求,但无法直接从服务发送 Toast。 自 Windows Vista 起,服务运行在名为会话 0 的专用会话中,不具备与用户交互的 UI。13
标准做法是将 UI 分离到用户会话一侧的进程中。
[会话 0] [用户会话]
Windows 服务 <-- IPC --> 常驻托盘代理
(监控・业务处理) 命名管道等 (NotifyIcon + AppNotification)
- 服务专注于监控与业务处理,将需要通知的事件通过 IPC 发送给代理。
- 代理是在用户登录时启动的托盘常驻应用(也就是本文第 2~8 章所讲的内容本身),负责将接收到的事件以 Toast 或图标变化的形式呈现出来。
- IPC 方式的选型可以参考「如何选择 Windows 进程间通信方式」,服务端的实现方法可以参考「Windows 服务的制作方法与运维」,会话机制本身的原理可以参考「如何理解 Windows 的会话隔离 —— 会话 0、RDP 与多用户同时运行」。
在多个用户同时登录的环境中(RDP、共享终端),代理会按会话各自运行一份。事先在需求阶段就明确”应该通知谁”(是所有人,还是特定用户),可以让设计更加稳定,不至于摇摆不定。
11. 总结
- 托盘常驻的基础是
NotifyIcon。通过Icon+Visible = true显示,Text有 127 字符的长度限制(.NET 6 及以上),退出路径中要确保执行Dispose()。图标与工具提示也可以作为常显的”当前状态”展示来使用。 - “点击 × 关闭后仍保留在托盘”通过取消
FormClosing来实现,但只应在CloseReason.UserClosing时取消,并务必提供一个明确的”退出”菜单。 - 图标能否始终显示只能由用户决定。资源管理器重启导致图标消失的问题,通过在
TaskbarCreated时重新注册来解决(WinForms 会自动处理)。 - 新开发的 Toast 通知功能,唯一的选择就是 Windows App SDK 的
AppNotificationManager。即使是没有打包标识的 exe 也可以运行,Register()会自动完成繁琐的注册工作。Community Toolkit 通知已被归档,原生 WinRT 则仅处于维护状态。 - 对于耗时较长的处理,中间进度应通过
UpdateAsync静默改写同一张 Toast 中的进度条,完成时则通过替换来弹出提示——这是标准做法。同时应附加Tag/Group,并将清理旧通知也纳入设计范围。 - Toast 是一种辅助渠道,可能因为勿扰模式或通知被关闭而无法送达。设计上应确保必需信息始终可以在应用内确认,同时也要注意来自提升权限进程的通知不受支持。
- 来自服务的通知,通过”用户会话一侧的常驻托盘代理 + IPC”这种分离架构来实现。
相关文章
- 防止 Windows 应用重复启动 —— 命名互斥体与重复启动时的激活处理
- Windows 服务的制作方法与运维 —— 与任务计划程序的取舍,以及将 BackgroundService 服务化
- 如何选择 Windows 进程间通信方式 —— 命名管道 / TCP / gRPC / 共享内存 / COM 判断表
- 如何理解 Windows 的会话隔离 —— 会话 0、RDP 与多用户同时运行
- 为什么要在桌面应用中使用 .NET Generic Host 与 BackgroundService
- Windows 应用中如何将「需要管理员权限的处理」单独分离出来的具体做法
- 外部设备状态确认与显示的最佳实践 - 不止步于”已连接”的设计
相关咨询领域
合同会社小村软件(KomuraSoft LLC)承接托盘常驻型监控工具与通知代理的设计与实现、与 Windows 服务组合的常驻系统架构评估,以及现有常驻应用的故障排查。
参考资料
-
Microsoft Learn, NotifyIcon Class / NotifyIcon.Visible Property。关于 NotifyIcon 是在通知区域显示图标的组件、需要设置 Icon 属性,以及 Visible 默认值为 false 的说明。 ↩ ↩2 ↩3
-
Microsoft Learn, NotifyIcon.Text Property。关于工具提示文本的长度上限,以及超出时会抛出 ArgumentException 的说明。 ↩ ↩2
-
Microsoft Learn, Windows Forms Controls and Equivalent WPF Controls。关于 WPF 中不存在与 NotifyIcon 对应的控件的说明。 ↩ ↩2
-
Microsoft Learn, MSBuild properties for .NET desktop projects。关于可以同时启用 UseWPF 与 UseWindowsForms 的说明。 ↩ ↩2
-
Microsoft Learn, Form.FormClosing Event。关于该事件在窗体关闭之前触发,并可通过 Cancel 属性取消关闭的说明。 ↩ ↩2
-
Microsoft Learn, Windows notifications overview。按应用类型划分的推荐通知 API 对照表,涵盖对 WinForms/WPF/非打包 Win32 的 Windows App SDK 推荐,以及 Windows.UI.Notifications 处于”仅维护”状态的说明。 ↩ ↩2
-
Microsoft Learn, Windows Community Toolkit — Notifications (archive)。关于 Notifications 组件已被归档、由 Windows App SDK 的应用通知取代的说明。 ↩
-
Microsoft Learn, Use the Windows App SDK in an existing project。关于 Windows App SDK 的应用通知 API 同时支持打包与非打包两种形态,且不要求打包标识的说明。 ↩ ↩2
-
Microsoft Learn, Quickstart: App notifications in the Windows App SDK。关于 AppNotificationManager/AppNotificationBuilder 的用法、非打包应用中 Register() 带来的自动注册、应在 Register() 之前订阅 NotificationInvoked,以及打包应用中清单文件记载方式的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, App notifications overview。关于从提升为管理员权限的应用发送通知不受支持的说明。 ↩ ↩2
-
Microsoft Learn, Notifications don’t appear or aren’t displayed。关于勿扰模式(Quiet Hours / Focus Assist)对通知的抑制、系统升级后一小时内的自动开启,以及通知未显示时的排查步骤的说明。 ↩ ↩2
-
Microsoft Learn, Focus assist。关于专注模式会抑制通知的横幅显示、并将其直接发送到通知中心这一机制的说明。 ↩ ↩2
-
Microsoft Learn, Service Changes for Windows Vista / Interactive Services。关于会话 0 专用于服务、不支持与用户交互,以及若要向用户显示内容,推荐使用 CreateProcessAsUser 在用户会话中创建进程并通过 IPC 通信这一做法的说明。 ↩ ↩2
-
Microsoft Learn, NotifyIcon.Text maximum text length increased。关于 .NET 6 中 Text 的最大长度从 63 个字符扩展到 127 个字符这一重大变更的说明。 ↩
-
Microsoft Learn, FormClosingEventArgs Class。关于可以通过 CloseReason 判断窗体关闭原因的说明。 ↩
-
Microsoft Learn, How to close a window or dialog box (WPF)。关于在 WPF 的 Closing 事件中将 e.Cancel 设为 true 并调用 Hide() 实现隐藏模式的说明。 ↩
-
Microsoft Learn, Notification Area (Windows Shell)。关于图标默认会被添加到溢出区域、只有用户才能决定是否提升到通知区域,以及不再需要的图标必须通过 NIM_DELETE 删除的说明。 ↩ ↩2
-
Microsoft Learn, The Taskbar。关于任务栏创建时会广播 TaskbarCreated 消息、收到该消息后应重新注册托盘图标的说明。 ↩
-
Microsoft Learn, Shell_NotifyIconW function。关于气泡通知在 Windows 10 中以 Toast(横幅)形式显示并保留在通知中心、在 Windows 11 中则为临时显示且不保留在通知中心,以及显示时长遵循辅助功能设置的说明。 ↩
-
Microsoft Learn, Send a local toast notification from a C# app (Win32/经典 API)。关于使用原生 ToastNotificationManager 时,必须在开始菜单中注册带 AUMID 的快捷方式的说明。 ↩
-
Microsoft Learn, AppNotificationManager.Register Method。关于应在应用退出前调用 Unregister 以清理资源、确保之后的通知启动能够正确进行,以及在今后完全不再使用通知功能时应使用 UnregisterAll 清除注册信息的说明。 ↩
-
Microsoft Learn, AppNotificationManager.UpdateAsync Method / Windows App SDK deployment architecture。关于 AppNotificationManager 类依赖于 Windows App SDK 的 Singleton 包,以及从 self-contained 部署的应用调用时的注意事项的说明。 ↩
-
Microsoft Learn, App notification progress bar and data binding。关于通过 Bind 方法实现数据绑定、通过 AppNotificationProgressData 与 UpdateAsync 实现更新,以及更新与替换的区别(在通知中心内的位置、可修改的范围、弹出提示的再次显示、用户已关闭通知后的行为),以及完成时应考虑使用替换的说明。 ↩ ↩2
-
Microsoft Learn, AppNotificationProgressData(UInt32) Constructor。关于序列号是指定更新顺序的非零整数、在收到多个更新数据时会显示编号最大的数据的说明。 ↩
-
Microsoft Learn, Remove app notifications。关于附加标签・分组、删除已显示的通知,以及设置有效期的说明。 ↩
-
Microsoft Learn, App notification content。关于 scenario=”urgent” 的重要通知,在获得用户许可的前提下会优先于勿扰模式设置的说明。 ↩
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
WinForms/WPF应用的多语言化 ── resx、附属程序集与区域文化切换的实务
本文从实务角度系统梳理Windows桌面应用多语言化的要点:CurrentCulture与CurrentUICulture的区别、resx与附属程序集构成的资源机制、WinForms的Localizable属性、WPF的现实方案选择、运行时语言切换,以及排版、格式、RTL等内容。
Windows 业务应用程序的打印与 PDF 输出 ── System.Drawing.Printing / WPF / 报表库的取舍选型
本文按需求整理成判断表,梳理 PrintDocument 的 WinForms 打印、WPF 的 FlowDocument/FixedDocument 打印,以及 PDF 输出的可选方案。并从实务角度讲解分页控制、DPI 偏差等实现陷阱,以及从 Windows 服务打印、依...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
VB6 应用能用到什么时候 ── 运行时支持现状与务实的 .NET 迁移做法
VB6 应用程序到底能用到什么时候?本文整理 VB6 运行时的支持政策(Windows 11 也在支持范围内)与 IDE 支持早已终止这一不对称现状,并以实务指南的形式说明全面重写、自动转换、分阶段迁移的判断表、迁移前的资产盘点、VB6 与 .NET 的不兼容之处,以及 C...
常见问题
汇总了咨询这一主题时常见的问题。
- 在 WPF 中要怎样制作任务栏托盘常驻应用?
- 由于 WPF 中不存在与 NotifyIcon 对应的控件,最小配置是在 csproj 中同时启用 UseWPF 和 UseWindowsForms,使用 WinForms 的 NotifyIcon。如果想要 XAML 风格的写法,也可以选择 H.NotifyIcon 等第三方库,但请注意它并非 Microsoft 官方组件,需据此权衡是否采用。
- Toast 通知应该使用哪种 API?
- 以 2026 年的新开发而言,无论是 WinForms、WPF 还是非打包应用,官方都推荐使用 Windows App SDK 的 AppNotificationManager。Microsoft.Toolkit.Uwp.Notifications 已被归档,不再包含在当前的 Community Toolkit 中;而 WinRT 的 ToastNotificationManager 则处于仅维护状态。如果现有代码依然运行在后两者之上,不必急于重写,但没有理由在新项目中选用它们。
- Toast 通知没有显示时应该检查什么?
- 首先要检查操作系统一侧的设置。如果开启了「专注助手」(勿扰模式),通知不会以横幅形式显示,而是直接进入通知中心;此外,如果在设置应用中按应用或按系统整体关闭了通知,同样不会显示。在实现层面,常见原因包括:以管理员权限提升的进程发送的 AppNotification 不受支持而显示失败,以及在没有 AUMID 的桌面应用中直接调用原生的 Windows.UI.Notifications 也不会显示。
- 可以从 Windows 服务发送 Toast 通知吗?
- 不可以。自 Windows Vista 起,服务运行在会话 0(Session 0)中,无法拥有与用户交互的 UI。如果需要通知用户,标准做法是在用户会话一侧放置一个常驻托盘的代理进程,服务与该代理之间通过命名管道等 IPC 方式通信,由代理负责显示通知。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。