Значки в области уведомлений и всплывающие (toast) уведомления в Windows-приложениях — подводные камни NotifyIcon и выбор правильного AppNotification

· · Область уведомлений, NotifyIcon, Toast-уведомления, AppNotification, Windows App SDK, WinForms, WPF, C#, .NET, Резидентное приложение, Разработка под Windows, Техническая консультация

«Хочу незаметно сообщить пользователю о завершении обработки» — «Хочу, чтобы мониторинг продолжался даже после закрытия окна» — это две классические потребности, которые часто встречаются вместе в бизнес-приложениях: постоянное присутствие в области уведомлений (system tray) и отправка toast-уведомлений. Но как только доходит до реальной реализации, один за другим начинают всплывать мелкие проблемы: приложение, которое должно было закрыться, на самом деле не завершает работу; значок исчезает после перезапуска Проводника; toast-уведомление, прекрасно работавшее при разработке, никогда не появляется у клиента.

Более того, toast-уведомления — это область, где API постоянно менялось поколениями со времён UWP. Не редкость встретить в поисковой выдаче примерный код, который по сегодняшним меркам использует API, находящийся, в лучшем случае, в статусе «только поддержка». В этой статье систематизированы как проектирование резидентности в области уведомлений с помощью NotifyIcon, так и выбор API для toast-уведомлений по состоянию на 2026 год, вместе с практическими подводными камнями, с которыми вы, скорее всего, столкнётесь.

1. Сначала — самое главное

  • Основа резидентности в области уведомлений — по-прежнему System.Windows.Forms.NotifyIcon. Значок появится только если задать Icon и установить Visible = true (значение по умолчанию — false). Текст всплывающей подсказки Text может достигать 127 символов начиная с .NET 6 (до этого — 63 символа).12
  • В WPF нет элемента управления, эквивалентного NotifyIcon. В официальной таблице соответствия прямо указано «Нет эквивалентного элемента управления». Минимальная конфигурация — включить в csproj одновременно UseWPF и UseWindowsForms и использовать NotifyIcon из WinForms.34
  • «Оставаться в области уведомлений даже при нажатии кнопки закрытия» реализуется установкой e.Cancel = true в FormClosing и вызовом Hide(). Всегда предусматривайте отдельный явный способ выхода (пункт «Выход» в меню значка), а также проверяйте CloseReason, чтобы никогда не блокировать завершение работы ОС.5
  • Рекомендуемый API для toast-уведомлений по состоянию на 2026 год определён однозначно. Независимо от того, разрабатываете ли вы WinForms-, WPF- или неупакованное приложение, рекомендуется AppNotificationManager из Windows App SDK (NuGet: Microsoft.WindowsAppSDK). Windows.UI.Notifications находится в статусе «только поддержка», а компонент уведомлений из Community Toolkit архивирован.67
  • AppNotification из Windows App SDK работает даже из обычного 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 = "Инструмент мониторинга заказов ── работает",     // Всплывающая подсказка (макс. 127 символов в .NET 6+)
        ContextMenuStrip = menu,
        Visible = true                        // По умолчанию false; отображается только после установки true
    };
    _trayIcon.DoubleClick += (_, _) => ShowMainWindow();
}

Стоит запомнить три момента.

  • Значок появляется только тогда, когда заданы одновременно Icon и Visible = true. Значение Visible по умолчанию — false.1
  • У Text есть ограничение длины. Максимум 127 символов начиная с .NET 6, и 63 символа для .NET 5 и более ранних версий, а также для .NET Framework; превышение вызывает 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 действует та же логика: официальная документация напрямую описывает шаблон установки e.Cancel = true в Window.Closing с последующим вызовом Hide().17

Стоит отметить, что резидентное приложение по своей природе всегда подвержено риску «если оно запущено дважды, одно и то же событие обрабатывается дважды». Предотвращение повторного запуска мы рассмотрели в статье «Предотвращение повторного запуска Windows-приложения — именованные мьютексы и активация уже запущенного экземпляра при повторном запуске». А статья «Почему стоит использовать .NET Generic Host и BackgroundService в настольном приложении» служит основой для построения самой начинки резидентного приложения — цикла мониторинга или периодической обработки.

4. Резидентность в области уведомлений в приложении WPF

В WPF нет элемента управления, эквивалентного NotifyIcon. Это документированный пробел платформы, явно указанный как «No equivalent control» в официальной таблице соответствия «элементы управления WPF, эквивалентные элементам управления WinForms».3

Есть два реалистичных варианта.

Вариант Обзор Рекомендация
Использовать NotifyIcon из WinForms вместе с WPF Включить в csproj одновременно UseWPF и UseWindowsForms Без дополнительных зависимостей. Достаточно, если нужны только значок и меню
Сторонняя библиотека (например, H.NotifyIcon) Реализации значка в трее, созданные для WPF/WinUI Выбирайте, если хотите писать на XAML или нужен более богатый всплывающий интерфейс. Учитывайте, что это неофициальный компонент Microsoft

Первый вариант — это просто перечисление двух свойств рядом в файле проекта в стиле SDK.4

<PropertyGroup>
  <TargetFramework>net8.0-windows</TargetFramework>
  <UseWPF>true</UseWPF>
  <UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>

После этого можно просто создать System.Windows.Forms.NotifyIcon напрямую из Application в WPF. Только меню окажется в стиле WinForms (ContextMenuStrip), но для чего-то настолько простого, как меню значка в трее, это практически никогда не создаёт проблем.

5. Три подводных камня значков в области уведомлений

5.1. Значки по умолчанию попадают в «скрытые значки» — принудительно показать их нельзя

Запрос сделать значок вашего приложения постоянно видимым на панели задач возникает неизбежно, но программно этого сделать нельзя. В официальной документации прямо указано, что значки по умолчанию добавляются в область переполнения (скрытые значки), и только пользователь может решить, повысить ли значок до постоянного отображения в области уведомлений.18

В Windows 11 пользователь включает или отключает это для каждого приложения через «Параметры → Персонализация → Панель задач → Другие значки области уведомлений». Если постоянное отображение важно с эксплуатационной точки зрения, правильным решением будет описать это в руководстве по установке или справке («включите, пожалуйста, этот параметр»), а не пытаться навязать это в коде.

5.2. Значок исчезает при перезапуске Проводника — TaskbarCreated

Когда Проводник аварийно завершается или перезапускается и пересоздаёт панель задач, все зарегистрированные значки в области уведомлений исчезают. Оболочка при создании панели задач рассылает сообщение TaskbarCreated всем окнам верхнего уровня, и на уровне Win32 действует правило: при получении этого сообщения значок нужно перерегистрировать.19

NotifyIcon из WinForms обрабатывает это сообщение внутри себя и автоматически перерегистрируется, поэтому обычно об этом можно не думать. Если вы сами вызываете Shell_NotifyIcon через P/Invoke (или поддерживаете нативное приложение со значком в трее), убедитесь, что эта логика перерегистрации реализована. Симптом «после перезапуска Проводника пропадает только значок, а процесс всё ещё жив» — это почти всегда именно эта проблема.

5.3. Забытый вызов Dispose оставляет значок-призрак

На уровне Win32 значок в области уведомлений необходимо явно удалить (NIM_DELETE), как только он становится не нужен, а в WinForms эту обязанность берёт на себя NotifyIcon.Dispose().18 Убедитесь, что путь выхода всегда проходит через Visible = falseDispose(). Любой разработчик резидентных приложений хотя бы раз видел эту картину: процесс аварийно завершается без очистки, и значок-призрак остаётся висеть в трее, пока пользователь не наведёт на него курсор мыши.

Как в коде из главы 3, безопаснее всего очищать значок в трее в обработчике меню «Выход» перед вызовом Close().

6. Toast-уведомления — какой API использовать

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 из настольного приложения есть известное ограничение: toast не отобразится, если в меню «Пуск» не зарегистрирован ярлык с AUMID (AppUserModelID).21 Именно эту сложность и устраняет AppNotification, рассматриваемая в следующей главе.

7. Минимальная реализация Windows App SDK AppNotification

API уведомлений приложений из Windows App SDK работает даже из обычного неупакованного 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) =>
{
    // Сюда приходят клики по тосту и нажатия кнопок (различаются через 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 перед завершением очищает ресурсы и гарантирует корректный запуск приложения при следующем клике по уведомлению. Встройте это в обработчик меню «Выход» из главы 3, рядом с NotifyIcon.Dispose() (существует также отдельный UnregisterAll() для случаев вроде удаления приложения, которое больше никогда не будет использовать уведомления, — он полностью очищает регистрационную информацию).22
  • Для неупакованных приложений Register() автоматически выполняет регистрацию COM-сервера и работу, эквивалентную AUMID. Настройка ярлыка и регистрация в реестре, которые требовались в предыдущем поколении «сырого» WinRT, больше не нужны. Если вместо этого вы упаковываете через MSIX, наоборот, потребуется объявить COM-активатор в Package.appxmanifest.9
  • Будьте внимательны, если выбираете самодостаточное (self-contained) развёртывание, включающее среду выполнения Windows App SDK. AppNotificationManager зависит от Singleton-пакета Windows App SDK, и существуют официально задокументированные особенности вызова его из самодостаточного приложения. Проверьте это, прежде чем определяться со способом распространения.23
  • Уведомления из процессов, повышенных до администратора, не поддерживаются. Show не выбрасывает исключение, а просто тихо завершается неудачей, из-за чего первопричину сложно заметить. Для приложений, требующих повышения прав, рассмотрите изоляцию только уведомлений в непонятый процесс без повышения прав (этот подход к изоляции рассмотрен в статье «Конкретный способ изолировать в Windows-приложении только те операции, которые требуют прав администратора»).10
  • При клике по toast, если приложение в данный момент не запущено, будет запущен новый процесс. В резидентном приложении в трее это нужно сочетать с предотвращением множественного запуска (глава 3), чтобы работа передавалась уже существующему экземпляру.

8. Продвинутое использование — toast с индикатором выполнения и очистка уведомлений

8.1. Обновление прогресса внутри toast — UpdateAsync

Для длительной операции вроде «импортируется 120 записей заказов» правильный подход — не отправлять новое уведомление снова и снова, а обновлять индикатор выполнения внутри одного toast. В AppNotification есть механизм привязки данных и обновления, созданный именно для этого.24

Построение происходит в три этапа. Сначала превратите каждое значение индикатора выполнения в заполнитель с помощью методов Bind, придайте уведомлению идентичность через TagGroup, при необходимости), а затем покажите его. После этого достаточно передать 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. «Обновление» и «замена» — когда что использовать

Обновить внешний вид можно и повторным Show нового уведомления с тем же Tag — это «замена», — но поведение отличается от «обновления».24

  Замена (повторный Show с тем же Tag) Обновление (UpdateAsync)
Положение в центре уведомлений Перемещается наверх Остаётся на месте
Что можно изменить Всё, включая макет Только привязанный индикатор выполнения и заголовочный текст
Всплывающее отображение Может появиться снова Не появляется снова (переписывается тихо)
Если пользователь уже закрыл его Всегда доставляется Завершается неудачей

Официальная рекомендация здесь тоже однозначна: для промежуточного прогресса — обновление, для завершения — замена. Нет необходимости показывать всплывающее уведомление каждый раз, когда прогресс меняется с 50% до 65%, но сообщение «импорт завершён (3 ошибки)» должно дойти до пользователя, даже если он уже закрыл уведомление о прогрессе. На практике распространённый шаблон — убрать индикатор выполнения при завершении и заменить его другим макетом с кнопками, например «Посмотреть детали ошибок».

8.3. Очистка старых уведомлений, оставшихся в центре уведомлений

Toast-уведомления накапливаются в центре уведомлений, пока пользователь их не очистит. Если резидентное приложение ежедневно отправляет несколько уведомлений, центр уведомлений заполняется старыми уведомлениями вашего приложения, что может привести к тому, что важные уведомления окажутся погребены под ними.

Прикрепление Tag / Group позволяет удалять уведомления собственного приложения программно (по тегу, по группе или все сразу). Также можно задать срок действия. Стоит определить правила очистки — «уведомления о прогрессе удалять по завершении», «сокращать срок действия информационных уведомлений» — одновременно с проектированием того, как отправляются уведомления.26

9. Учёт в архитектуре случаев, когда уведомление «не доходит»

Toast-уведомление не является гарантированным механизмом доставки. Существуют совершенно нормальные сценарии, в которых оно не отображается или остаётся незамеченным.

Случай Поведение
Включена «Фокусировка внимания» (не беспокоить) Баннер не отображается; уведомление сразу попадает в центр уведомлений
Уведомления отключены для приложения или для всей системы Ничего не отображается
AppNotification из процесса, повышенного до администратора Не поддерживается. Ничего не отображается
Запущено полноэкранное приложение и т. п. ОС может подавить его по своему усмотрению

Режим «не беспокоить» называется «Фокусировка внимания (Focus assist)» на Windows 10 и управляется через переключатель «Не беспокоить» в «Параметры → Система → Уведомления» на 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, соблюдайте ограничение в 127 символов для Text (.NET 6+) и обеспечьте надёжный вызов Dispose() на пути выхода. Используйте значок и подсказку также как постоянно видимый индикатор текущего состояния.
  • «Оставаться в трее даже при закрытии кнопкой ×» реализуется отменой FormClosing, но отменяйте закрытие только при CloseReason.UserClosing и всегда предусматривайте явное меню «Выход».
  • Только пользователь может решить, будет ли значок отображаться постоянно. Исчезновение значка при перезапуске Проводника решается перерегистрацией по TaskbarCreated (в WinForms это делается автоматически).
  • Для любой новой разработки toast-уведомлений единственный реальный выбор — AppNotificationManager из Windows App SDK. Он работает даже из неупакованного exe без идентификатора пакета, а Register() автоматизирует утомительную регистрацию. Уведомления Community Toolkit архивированы, а «сырой» WinRT находится только на поддержке.
  • Для промежуточного прогресса длительной операции тихо перезаписывайте индикатор выполнения внутри одного toast с помощью UpdateAsync, а по завершении используйте замену, чтобы показать всплывающее уведомление, — это стандартный шаблон. Прикрепляйте 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 уведомлений по типу приложения, включая рекомендацию Windows App SDK для WinForms/WPF/неупакованного Win32 и статус «только поддержка» для 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. О том, что API уведомлений приложений Windows App SDK поддерживает как упакованные, так и неупакованные приложения, не требуя идентификатора пакета.  2

  9. Microsoft Learn, Quickstart: App notifications in the Windows App SDK. Об использовании AppNotificationManager/AppNotificationBuilder, автоматической регистрации через Register() для неупакованных приложений, необходимости подписки на NotificationInvoked до Register(), а также о декларациях манифеста для упакованных приложений.  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). О шаблоне события Closing в WPF: установка 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). О требовании регистрации ярлыка с AUMID в меню «Пуск» при использовании «сырого» ToastNotificationManager. 

  22. Microsoft Learn, AppNotificationManager.Register Method. О необходимости вызова Unregister перед завершением приложения для очистки ресурсов и обеспечения корректного последующего запуска по уведомлению, а также об использовании UnregisterAll для очистки регистрационной информации, если уведомления больше никогда не будут использоваться. 

  23. Microsoft Learn, AppNotificationManager.UpdateAsync Method / Windows App SDK deployment architecture. О том, что класс AppNotificationManager зависит от Singleton-пакета Windows App SDK, и об особенностях его вызова из самодостаточного (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” над настройками режима «не беспокоить» с согласия пользователя. 

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Японская эра, праздники и даты закрытия периода в бизнес-приложениях — устойчивый к смене эры дизайн, JapaneseCalendar и расчёт рабочих дней на практике

Показать «Рэйва 8» в отчёте, рассчитать рабочие дни без учёта праздников, оплатить в последний рабочий день месяца, следующего за закрыти...

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Как создать приложение, резидентное в области уведомлений, на WPF?
В WPF нет элемента управления, эквивалентного NotifyIcon, поэтому минимальная конфигурация — включить в csproj одновременно UseWPF и UseWindowsForms и использовать NotifyIcon из WinForms. Если нужен стиль написания, дружественный к XAML, вариантом может быть сторонняя библиотека вроде H.NotifyIcon, но при принятии решения учитывайте, что это неофициальный компонент Microsoft.
Какой API следует использовать для toast-уведомлений?
Для любой новой разработки по состоянию на 2026 год — будь то WinForms, WPF или неупакованное приложение — официально рекомендуется AppNotificationManager из Windows App SDK. Microsoft.Toolkit.Uwp.Notifications архивирован и больше не входит в актуальный Community Toolkit, а ToastNotificationManager из WinRT находится в статусе «только поддержка». Если существующий код уже работает на одном из этих двух вариантов, спешить с переписыванием не обязательно, но выбирать их для нового проекта нет причин.
Что нужно проверить, если toast-уведомление не отображается?
Начните с настроек операционной системы. Если включён «Фокусировка внимания» (режим «не беспокоить»), уведомления не показываются баннером, а сразу попадают в центр уведомлений; также уведомления могут быть отключены для конкретного приложения или для всей системы в приложении «Параметры». На стороне реализации две типичные причины — это то, что AppNotification не поддерживается при отправке из процесса, повышенного до администратора, и то, что вызов «сырого» Windows.UI.Notifications из настольного приложения без AUMID ничего не отображает.
Может ли служба Windows отправлять toast-уведомления?
Нет, не может. Начиная с Windows Vista службы выполняются в сеансе 0 и не имеют возможности отображать UI для взаимодействия с пользователем. Если нужно уведомить пользователя, стандартный подход — разместить резидентный процесс-агент в области уведомлений в пользовательском сеансе и организовать его взаимодействие со службой через IPC, например именованные каналы, при этом сам агент отвечает за отображение уведомления.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог