Windows 应用的任务栏托盘常驻与 Toast 通知 —— NotifyIcon 的坑与 AppNotification 的选型

· · 任务栏托盘, 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 中同时启用 UseWPFUseWindowsForms,使用 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();
}

需要注意的要点有三个。

  • 只有 IconVisible = true 两者都具备时才会显示。 Visible 的默认值是 false1
  • Text 有长度限制。 .NET 6 及以后最长为 127 个字符,.NET 5 及更早版本以及 .NET Framework 为 63 个字符,超出会抛出 ArgumentException。把过多状态信息塞进工具提示里,可能会在某一刻突然导致异常崩溃。214
  • 右键菜单通过 ContextMenuStrip 关联。 托盘图标往往是用户唯一能看到的入口,因此至少要提供”打开”和”退出”这两个选项。15

还有一点:托盘图标不仅是通知的入口,同时也是一个始终可见的状态展示。由于 IconText 可以在运行期间随时替换,如果做到”正常时使用普通图标、异常时切换为警告图标”“在工具提示中显示最近一次处理时间”之类的设计,反映当前状态,那么即便用户错过了 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 中同时启用 UseWPFUseWindowsForms 无额外依赖。只需要图标与菜单时已经足够
第三方库(如 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 = falseDispose()。如果进程因异常终止等原因没有执行删除操作,图标就会一直以”幽灵图标”的形式残留,直到鼠标悬停上去才消失——这是任何常驻应用开发者迟早都会遇到的场景。

按照第 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)

在多个用户同时登录的环境中(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”这种分离架构来实现。

相关文章

相关咨询领域

合同会社小村软件(KomuraSoft LLC)承接托盘常驻型监控工具与通知代理的设计与实现、与 Windows 服务组合的常驻系统架构评估,以及现有常驻应用的故障排查。

参考资料

  1. Microsoft Learn, NotifyIcon Class / NotifyIcon.Visible Property。关于 NotifyIcon 是在通知区域显示图标的组件、需要设置 Icon 属性,以及 Visible 默认值为 false 的说明。  2 3

  2. Microsoft Learn, NotifyIcon.Text Property。关于工具提示文本的长度上限,以及超出时会抛出 ArgumentException 的说明。  2

  3. Microsoft Learn, Windows Forms Controls and Equivalent WPF Controls。关于 WPF 中不存在与 NotifyIcon 对应的控件的说明。  2

  4. Microsoft Learn, MSBuild properties for .NET desktop projects。关于可以同时启用 UseWPF 与 UseWindowsForms 的说明。  2

  5. Microsoft Learn, Form.FormClosing Event。关于该事件在窗体关闭之前触发,并可通过 Cancel 属性取消关闭的说明。  2

  6. Microsoft Learn, Windows notifications overview。按应用类型划分的推荐通知 API 对照表,涵盖对 WinForms/WPF/非打包 Win32 的 Windows App SDK 推荐,以及 Windows.UI.Notifications 处于”仅维护”状态的说明。  2

  7. Microsoft Learn, Windows Community Toolkit — Notifications (archive)。关于 Notifications 组件已被归档、由 Windows App SDK 的应用通知取代的说明。 

  8. Microsoft Learn, Use the Windows App SDK in an existing project。关于 Windows App SDK 的应用通知 API 同时支持打包与非打包两种形态,且不要求打包标识的说明。  2

  9. Microsoft Learn, Quickstart: App notifications in the Windows App SDK。关于 AppNotificationManager/AppNotificationBuilder 的用法、非打包应用中 Register() 带来的自动注册、应在 Register() 之前订阅 NotificationInvoked,以及打包应用中清单文件记载方式的说明。  2 3 4

  10. Microsoft Learn, App notifications overview。关于从提升为管理员权限的应用发送通知不受支持的说明。  2

  11. Microsoft Learn, Notifications don’t appear or aren’t displayed。关于勿扰模式(Quiet Hours / Focus Assist)对通知的抑制、系统升级后一小时内的自动开启,以及通知未显示时的排查步骤的说明。  2

  12. Microsoft Learn, Focus assist。关于专注模式会抑制通知的横幅显示、并将其直接发送到通知中心这一机制的说明。  2

  13. Microsoft Learn, Service Changes for Windows Vista / Interactive Services。关于会话 0 专用于服务、不支持与用户交互,以及若要向用户显示内容,推荐使用 CreateProcessAsUser 在用户会话中创建进程并通过 IPC 通信这一做法的说明。  2

  14. Microsoft Learn, NotifyIcon.Text maximum text length increased。关于 .NET 6 中 Text 的最大长度从 63 个字符扩展到 127 个字符这一重大变更的说明。 

  15. Microsoft Learn, How to associate a shortcut menu with a Windows Forms NotifyIcon component。关于通过 ContextMenuStrip 关联右键菜单的说明。 

  16. Microsoft Learn, FormClosingEventArgs Class。关于可以通过 CloseReason 判断窗体关闭原因的说明。 

  17. Microsoft Learn, How to close a window or dialog box (WPF)。关于在 WPF 的 Closing 事件中将 e.Cancel 设为 true 并调用 Hide() 实现隐藏模式的说明。 

  18. Microsoft Learn, Notification Area (Windows Shell)。关于图标默认会被添加到溢出区域、只有用户才能决定是否提升到通知区域,以及不再需要的图标必须通过 NIM_DELETE 删除的说明。  2

  19. Microsoft Learn, The Taskbar。关于任务栏创建时会广播 TaskbarCreated 消息、收到该消息后应重新注册托盘图标的说明。 

  20. Microsoft Learn, Shell_NotifyIconW function。关于气泡通知在 Windows 10 中以 Toast(横幅)形式显示并保留在通知中心、在 Windows 11 中则为临时显示且不保留在通知中心,以及显示时长遵循辅助功能设置的说明。 

  21. Microsoft Learn, Send a local toast notification from a C# app (Win32/经典 API)。关于使用原生 ToastNotificationManager 时,必须在开始菜单中注册带 AUMID 的快捷方式的说明。 

  22. Microsoft Learn, AppNotificationManager.Register Method。关于应在应用退出前调用 Unregister 以清理资源、确保之后的通知启动能够正确进行,以及在今后完全不再使用通知功能时应使用 UnregisterAll 清除注册信息的说明。 

  23. Microsoft Learn, AppNotificationManager.UpdateAsync Method / Windows App SDK deployment architecture。关于 AppNotificationManager 类依赖于 Windows App SDK 的 Singleton 包,以及从 self-contained 部署的应用调用时的注意事项的说明。 

  24. Microsoft Learn, App notification progress bar and data binding。关于通过 Bind 方法实现数据绑定、通过 AppNotificationProgressData 与 UpdateAsync 实现更新,以及更新与替换的区别(在通知中心内的位置、可修改的范围、弹出提示的再次显示、用户已关闭通知后的行为),以及完成时应考虑使用替换的说明。  2

  25. Microsoft Learn, AppNotificationProgressData(UInt32) Constructor。关于序列号是指定更新顺序的非零整数、在收到多个更新数据时会显示编号最大的数据的说明。 

  26. Microsoft Learn, Remove app notifications。关于附加标签・分组、删除已显示的通知,以及设置有效期的说明。 

  27. Microsoft Learn, App notification content。关于 scenario=”urgent” 的重要通知,在获得用户许可的前提下会优先于勿扰模式设置的说明。 

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

常见问题

汇总了咨询这一主题时常见的问题。

在 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表