수정 이력(7건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 관련 기사 링크를 한국어 permalink에 맞추는 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 글 서두에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건) 대응으로 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
- 콜드 스타트 시 전달에 경합이 남아 있던 것을 고쳤습니다. 폼의 유무를 확인한 뒤 맡겨 두기까지의 사이에 폼이 표시되면, 그 사이에 실행된 인수 처리는 아무것도 찾지 못하고, 직후에 맡긴 조작이 흘러가지 않은 채로 남습니다. UI 준비 완료 플래그와 보관 위치를 같은 잠금으로 다루도록 바꿨습니다.
- 토스트 클릭이 앱 시작 그 자체였을 때 조작이 조용히 버려지던 문제를 고쳤습니다. 핸들러가 동작하는 시점에는 아직 폼이 하나도 없으므로, `Application.OpenForms`를 보고 `null`이면 아무것도 하지 않는다고 쓰면, 이 글이 다루는 핵심인 콜드 스타트 경로가 동작하지 않습니다. 일단 맡아 두었다가 폼이 표시된 뒤에 흘려보내는 형태로 바꿨습니다.
- 목적별로 어느 장을 읽으면 되는지의 안내를 서두에 추가했습니다. 전체 그림만, 트레이 상주 앱을 만든다, 알림 API를 고른다, 같은 목적마다 읽을 위치를 제시합니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635428)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「Windows 앱의 작업 트레이 상주와 토스트 알림 ── NotifyIcon의 함정과 AppNotification 고르는 법」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-tray-icon-toast-notification-guide/
- DOI(등록된 아카이브)
- 10.5281/zenodo.21635428
- DOI(마지막 등록 버전)
- 10.5281/zenodo.21635429
「처리가 끝나면 사용자에게 부담 없이 알리고 싶다」, 「창을 닫아도 모니터링은 계속하고 싶다」── 업무용 앱에서는 작업 트레이(알림 영역) 상주와 토스트 알림이 세트로 등장하는 단골 요구사항입니다. 그런데 막상 구현해 보면, 닫혔어야 할 앱이 종료되지 않거나, Explorer를 다시 시작했더니 아이콘이 사라지거나, 개발 중에는 나오던 토스트가 배포 대상에서는 표시되지 않는 식의 자잘한 문제가 잇따릅니다.
게다가 토스트 알림은 UWP 시절부터 API 세대 교체가 이어져 온 영역입니다. 검색해서 나온 샘플 코드가 지금 와서는 유지보수만 하는 옛 API였던 경우도 드물지 않습니다. 이 글에서는 NotifyIcon으로 트레이에 상주시키는 설계와, 2026년 시점의 토스트 알림 API 고르는 법을 실무 함정과 함께 정리합니다.
이 글의 읽는 법
전부 11장으로 길기 때문에, 목적별 입구를 적어 둡니다. 처음부터 순서대로 읽을 필요는 없습니다.
| 목적 | 읽을 곳 |
|---|---|
| 전체 그림만 알고 싶다 | 1장(결론)과 11장(정리) |
| 이제부터 트레이 상주 앱을 만든다 | 2~5장 ── NotifyIcon 기본, 종료 경로 설계, WPF에서 병용, 함정 3가지 |
| 토스트 알림 API를 무엇으로 할지 정하고 싶다 | 6장의 비교표. 판단만 필요하면 여기면 충분합니다 |
| AppNotification을 구현한다 | 7장 ── 최소 구현, csproj 설정, 배포 대상에 런타임 도입 |
| 진행률 막대처럼 한 단계 나아간 알림을 만든다 | 8장. 일단 동작시키는 단계에서는 건너뛰어도 됩니다 |
| 「알림이 안 나온다」를 조사하고 있다 | 9장 ── OS 설정 확인 위치와, 상승된 프로세스의 제약 |
| Windows 서비스에서 사용자에게 알리고 싶다 | 10장 |
이 글에서 쓰는 용어
| 용어 | 의미 |
|---|---|
| 알림 영역(작업 트레이) | 작업 표시줄 오른쪽 끝에 아이콘이 늘어선 영역. Windows 용어로는 「알림 영역(notification area)」입니다1 |
| 토스트 알림 | 화면 오른쪽 아래에 배너로 나타났다가 알림 센터에 남는 알림 |
| TFM | Target Framework Moniker(타깃 프레임워크 모니커). csproj의 <TargetFramework>에 쓰는 식별자로, net8.0-windows10.0.19041.0처럼 .NET 버전과 대상 플랫폼을 나타냅니다 |
| MSIX | Windows의 앱 패키지 형식. 이 형식으로 배포된 앱을 「패키징된(packaged) 앱」이라고 부릅니다 |
| 패키징되지 않은(unpackaged) 앱 | MSIX로 패키징하지 않은 기존형 exe입니다. WPF·WinForms·Win32 데스크톱 앱은 기본이 이쪽입니다2 |
| AUMID | AppUserModelID. Windows가 앱을 고유하게 식별하는 ID이며, 토스트 알림의 보낸 사람을 식별하는 데 쓰입니다 |
| 세션 0 | Windows Vista 이후 서비스 전용으로 할당되는 세션. 대화형 UI를 가질 수 없습니다(10장)3 |
1. 먼저 결론
- 트레이 상주의 기본은 지금도
System.Windows.Forms.NotifyIcon입니다.Icon을 설정하고Visible = true로 두지 않으면 표시되지 않습니다(기본값은false). 툴팁의Text는 .NET 6 이후 최대 127자입니다(그 이전에는 63자).45 - WPF에는 NotifyIcon에 해당하는 컨트롤이 없습니다. 공식 대조표에도 「동등한 컨트롤 없음」이라고 명시되어 있습니다. csproj에서
UseWPF와UseWindowsForms를 함께 켜고 WinForms의 NotifyIcon을 쓰는 구성이 최소입니다.67 - 「닫기(×) 버튼을 눌러도 트레이에 남긴다」는
FormClosing에서e.Cancel = true로 두고Hide()하는 설계입니다. 반드시 명시적인 종료 수단(트레이 메뉴의 「종료」)을 따로 마련하고,CloseReason을 확인해 OS 종료까지 막지 않도록 합니다.8 - 토스트 알림 API는 2026년 시점의 공식 권장이 분명합니다. WinForms / WPF / 패키징되지 않은 앱 모두 Windows App SDK의
AppNotificationManager(NuGet: Microsoft.WindowsAppSDK)가 권장입니다.Windows.UI.Notifications는 「유지보수만」, Community Toolkit의 알림 구성 요소는 보관 처리되었습니다.910 - Windows App SDK의 AppNotification은 패키지 ID 없는 일반 exe에서도 쓸 수 있습니다.
AppNotificationManager.Default.Register()가 COM 서버 등록과 AUMID에 해당하는 처리를 자동으로 하므로, 이전 세대에서 필요했던 시작 메뉴 바로가기는 필요 없습니다. 다만 관리자 권한으로 상승한 프로세스에서의 알림은 지원되지 않습니다. 111213 - 토스트는 「반드시 사용자 눈에 들어오는」 수단이 아닙니다. 방해 금지(Focus Assist) 중에는 배너로 표시되지 않고 알림 센터로 바로 가며, 설정에서 알림 자체를 끌 수도 있습니다. 「알림이 표시되었다」를 업무 흐름의 전제로 두지 않는 설계가 필요합니다.1415
- Windows 서비스에서는 토스트를 보낼 수 없습니다(세션 0에는 UI가 없습니다). 사용자 세션 쪽에 트레이 상주 에이전트를 두고 IPC로 연계하는 것이 정석입니다.3
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 18건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. NotifyIcon으로 트레이에 상주시키는 기본
작업 표시줄 오른쪽 끝 알림 영역에 아이콘을 내는 구성 요소가 System.Windows.Forms.NotifyIcon입니다. 백그라운드에서 계속 동작하는 앱이 상태 표시와 조작 메뉴의 입구로 씁니다.4
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입니다.4Text에는 길이 제한이 있습니다. .NET 6 이후는 최대 127자, .NET 5 이전과 .NET Framework는 63자이며, 넘으면ArgumentException이 발생합니다. 상태를 툴팁에 너무 욱여넣는 구현은, 어느 순간 예외로 갑자기 죽는 원인이 됩니다.516- 오른쪽 클릭 메뉴는
ContextMenuStrip으로 연결합니다. 트레이 아이콘은 사용자가 보는 유일한 입구가 되는 경우가 많으므로, 「열기」와 「종료」는 최소한 마련합니다.17
하나 더, 트레이 아이콘은 알림의 입구인 동시에 항상 보이는 상태 표시이기도 합니다. Icon과 Text는 실행 중에 바꿀 수 있으므로, 「정상일 때는 보통 아이콘, 이상일 때는 경고 아이콘」, 「툴팁에 마지막 처리 시각을 낸다」처럼 현재 상태를 반영해 두면, 사용자가 토스트를 놓쳐도 아이콘만 보면 지금 상태를 알 수 있습니다. 토스트가 「그 순간의 일을 알리는」 수단, 트레이 아이콘이 「현재 상태를 계속 보이는」 수단, 이라는 역할 분담입니다.
3. 「닫아도 트레이에 남는」 설계와 올바른 종료 경로
상주 앱의 단골이 「닫기(×) 버튼을 눌러도 프로세스는 끝나지 않고 트레이에 남는다」는 동작입니다. 구현 자체는 FormClosing을 취소하면 간단히 쓸 수 있지만, 대충 쓰면 앱을 종료할 수단이 없어지거나, Windows 종료를 가로막는 두 가지 사고로 이어집니다.
private bool _exitRequested;
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 사용자가 ×만 눌렀다면 닫지 않고 숨깁니다
if (!_exitRequested && e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
return;
}
// 「종료」 메뉴를 거쳤거나 OS 종료 등은 그대로 닫습니다
base.OnFormClosing(e);
}
private void ExitApplication()
{
_exitRequested = true;
_trayIcon.Visible = false;
_trayIcon.Dispose();
Close();
}
FormClosing은 폼이 닫히기 전에 발생하며, e.Cancel = true로 닫기를 취소할 수 있습니다. 이때 반드시 CloseReason을 확인하시기 바랍니다. UserClosing(사용자 조작)일 때만 취소하고, WindowsShutDown처럼 OS에서 비롯된 닫기는 취소하지 않는 것이 올바른 동작입니다.818
WPF에서도 생각은 같고, Window.Closing에서 e.Cancel = true로 두고 Hide()하는 패턴이 공식 문서에도 그대로 실려 있습니다.19
참고로 상주 앱은 성격상 「이중으로 뜨면 같은 이벤트를 이중으로 처리한다」는 문제와 맞닿아 있습니다. 다중 실행 방지는 「Windows 앱의 다중 실행 방지 ── 명명된 Mutex와 이중 실행 시 활성화」에서 정리했습니다. 또한 상주 앱의 알맹이(모니터링 루프나 정기 처리)를 만드는 법은 「.NET Generic Host와 BackgroundService를 데스크톱 앱에서 쓰는 이유」가 토대가 됩니다.
4. WPF 앱에서 트레이 상주
WPF에는 NotifyIcon에 해당하는 컨트롤이 없습니다. 이는 공식 「WinForms 컨트롤과 동등한 WPF 컨트롤」 대조표에 「No equivalent control」이라고 명시된, 사양상의 공백입니다.6
현실적인 선택지는 두 가지입니다.
| 선택지 | 개요 | 판단의 눈금 |
|---|---|---|
| WinForms의 NotifyIcon을 함께 쓴다 | csproj에서 UseWPF + UseWindowsForms를 둘 다 켭니다 |
추가 의존 없음. 아이콘과 메뉴만이면 충분합니다 |
| 서드파티(H.NotifyIcon 등) | WPF/WinUI용 트레이 아이콘 구현 | XAML로 쓰고 싶다, 풍부한 팝업이 필요할 때. Microsoft 비공식인 점은 고려합니다 |
전자는 SDK 스타일 프로젝트 파일에 속성 두 개를 나란히 적기만 하면 됩니다.7
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
System.Windows.Forms.NotifyIcon을 WPF의 Application에서 그대로 new해서 쓸 수 있습니다. 메뉴만 WinForms(ContextMenuStrip)가 되지만, 트레이 메뉴 정도라면 실무상 문제는 거의 없습니다.
5. 트레이 아이콘의 함정 세 가지
5.1. 아이콘은 기본으로 「숨겨진 아이콘」에 들어간다 ── 강제 표시는 할 수 없다
자기 아이콘을 작업 표시줄에 항상 보이게 하고 싶다는 요청은 반드시 나오지만, 프로그램에서는 할 수 없습니다. 공식 문서에, 아이콘은 기본으로 오버플로(숨겨진 아이콘) 영역에 추가되며, 알림 영역으로의 승격(항상 표시)을 정할 수 있는 것은 사용자뿐이라고 명시되어 있습니다.1
Windows 11에서는 「설정 → 개인 설정 → 작업 표시줄 → 기타 시스템 트레이 아이콘」에서 사용자가 앱마다 켜고 끕니다. 항상 표시가 운영상 중요하다면, 설치 절차서나 도움말에 「이 설정을 켜 주세요」라고 적는 것이 올바른 대응입니다.
절차서에는 메뉴 전체 경로까지 적어 두면 전화로도 안내할 수 있습니다. 문면 예는 다음과 같습니다.
- 작업 표시줄의 빈곳을 오른쪽 클릭하고 「작업 표시줄 설정」을 고릅니다.
- 열린 화면에서 「기타 시스템 트레이 아이콘」을 엽니다.
- 목록에서 이 앱을 찾아 스위치를 켭니다.
※ 아이콘이 보이지 않을 때는 작업 표시줄 오른쪽의 「^」(꺾쇠) 버튼을 누르면, 숨어 있는 아이콘이 잠시 표시됩니다.
5.2. Explorer가 다시 시작되면 아이콘이 사라진다 ── TaskbarCreated
Explorer가 충돌이나 다시 시작으로 작업 표시줄을 다시 만들면, 등록해 둔 트레이 아이콘은 사라집니다. 셸은 작업 표시줄을 만들 때 TaskbarCreated 메시지를 모든 최상위 창에 브로드캐스트하므로, 이를 받으면 아이콘을 다시 등록하는 것이 Win32 수준의 약속입니다.20
WinForms의 NotifyIcon은 이 메시지를 내부에서 처리해 자동으로 다시 등록하므로, 보통은 의식할 필요가 없습니다. 직접 Shell_NotifyIcon을 P/Invoke하는 경우(또는 트레이 아이콘을 가진 네이티브 앱을 유지보수하는 경우)는 이 재등록 처리가 들어가 있는지 확인하시기 바랍니다. 「Explorer를 다시 시작했더니 아이콘만 사라지고 프로세스는 살아 있다」는 증상은 거의 이것입니다.
5.3. Dispose를 잊으면 아이콘이 남는다
Win32로 보면 트레이 아이콘은 필요 없어지면 명시적으로 삭제(NIM_DELETE)해야 하며, WinForms에서는 그것을 NotifyIcon.Dispose()가 맡습니다.1 종료 경로에서 Visible = false → Dispose()를 반드시 타도록 하시기 바랍니다. 프로세스가 비정상 종료하는 등으로 삭제되지 않은 채로 남으면, 마우스 커서를 올릴 때까지 유령 아이콘이 남는 광경은 상주 앱을 만드는 사람이라면 한 번은 보는 장면입니다.
3장의 코드처럼 「종료」 메뉴 처리에서 트레이 아이콘을 정리한 뒤 Close()하는 형태로 두는 것이 안전합니다.
6. 토스트 알림 ── 어느 API를 쓸 것인가
토스트 알림은 화면 오른쪽 아래에 배너로 표시되고, 알림 센터에 남는 알림입니다. 데스크톱 앱에서 내는 방법은 지난 10년 동안 세대 교체가 이어져 왔고, 검색 결과에는 옛 샘플이 대량으로 남아 있습니다. 2026년 시점의 정리는 다음과 같습니다.9
| 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에서는 토스트로 표시되고, Windows 11에서는 잠깐 표시되며 알림 센터에 남지 않습니다 | 간단한 용도라면 가능합니다. 동작이 OS마다 달라진다는 점에 주의합니다 |
보탬이 두 가지 있습니다.
ShowBalloonTip은 「옛 API지만 실은 살아 있다」는 칸입니다. Windows 10에서는 풍선이 토스트로 그려지고, 닫힐 때까지 알림 센터에 남습니다. 반면 Windows 11에서는 예전 풍선에 가까운 잠깐의 표시가 되고, 사라진 뒤에는 알림 센터에 남지 않습니다. 표시 시간 지정(timeout)도 지금은 무시되며, OS 쪽 접근성 설정을 따릅니다. 트레이 상주 앱이 자기 상태 변화를 가볍게 알리는 정도라면 충분하지만, 「알림 센터에 이력을 남기고 싶다」면 Windows 11에서는 도움이 되지 않는다고 기억해 두시기 바랍니다.21- 그대로의
Windows.UI.Notifications를 데스크톱 앱에서 쓰는 고전적인 방법에는, AUMID(AppUserModelID)가 붙은 바로가기를 시작 메뉴에 등록해 두지 않으면 토스트가 표시되지 않는다는 잘 알려진 제약이 있습니다.22 이 번거로움을 해소한 것이 다음 장의 AppNotification입니다.
표의 「간단한 용도라면 가능」이 어디까지인지는 기능으로 선을 그을 수 있습니다. 다음 다섯 가지를 모두 충족하면 ShowBalloonTip으로 충분하며, Windows App SDK 의존을 늘릴 필요는 없습니다.
- 애초에 트레이 아이콘을 가진 앱이다(풍선은 트레이 아이콘에 딸린 알림이므로, 이것이 전제입니다)
- 알림에 조작 버튼이나 입력란이 필요 없다. 클릭하면 앱 창을 앞으로 내는 정도로 충분하다
- 알림 센터에 이력으로 남길 필요가 없다. Windows 11에서는 남지 않으므로, 「나중에 다시 보는」 용도에는 쓸 수 없습니다21
- 진행률 막대나, 이미 표시된 알림의 내용 업데이트가 필요 없다
- 알림은 「눈치채면 빨리 대응할 수 있는」 보조이며, 놓쳐도 업무가 돌아간다
반대로 다음 중 하나라도 해당하면, 그대로 7장의 AppNotification을 쓰시기 바랍니다.
| 요건 | 이유 |
|---|---|
| 알림에 버튼(「로그 열기」 등)이나 입력란을 두고 싶다 | 풍선은 제목·본문·아이콘뿐이며, 조작 요소를 가질 수 없습니다 |
| Windows 11에서도 알림 센터에 남기고 싶다 | 풍선은 Windows 11에서는 남지 않습니다21 |
| 긴 처리의 진행을 알림 한 장으로 갱신하고 싶다 | 진행률 막대와 UpdateAsync는 AppNotification 쪽 기능입니다(8장) |
| 앱이 시작되지 않은 상태에서의 복귀도 다루고 싶다 | 풍선은 앱이 시작되어 트레이 아이콘을 내지 않으면 존재하지 않습니다. AppNotification은 알림 클릭으로 프로세스를 시작할 수 있습니다(7장) |
| 옛 알림을 나중에 코드로 지우고 싶다·바꾸고 싶다 | Tag / Group으로 관리하는 것은 AppNotification 쪽 기능입니다(8장) |
판단이 갈릴 때의 눈금은 하나이며, 「그 알림에 대해 사용자에게 무언가를 조작하게 하고 싶은가」입니다. 읽히기만 하면 풍선, 누르게 하려면 AppNotification입니다.
7. Windows App SDK AppNotification의 최소 구현
Windows App SDK의 앱 알림 API는 패키징하지 않은 일반 exe에서도, 패키지 ID 없이 동작합니다. 11 다만 「NuGet만 넣으면 끝」이 아니라, 개발 쪽과 배포 쪽 모두에 전제가 있습니다. 순서대로 보겠습니다.
7.1. 프로젝트 쪽 준비 ── NuGet과 csproj
공식 전제는 .NET 6 이후의 WPF 또는 WinForms 프로젝트일 것, 그 프로젝트가 WinRT API를 호출할 수 있게 구성되어 있을 것, 그리고 Windows App SDK를 쓰도록 구성되어 있을 것의 세 가지입니다.12
할 일은 두 가지입니다. 먼저 NuGet 패키지 Microsoft.WindowsAppSDK를 추가합니다(Visual Studio라면 「도구 → NuGet 패키지 관리자 → 솔루션의 NuGet 패키지 관리」에서). 이 패키지에는 Windows App SDK 각 구성 요소의 하위 패키지가 들어 있으며, 공식도 우선 메인 패키지를 넣는 것을 권장합니다.2
다음으로 csproj를 편집합니다. 4장의 WPF+WinForms 예에 Windows App SDK 분을 더하면 다음 형태가 됩니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<!-- WinRT API를 호출하려면 TFM에 Windows 버전까지 넣습니다 -->
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<!-- 패키징되지 않은(MSIX가 아닌) 앱에서, 시작 시 런타임을 자동 초기화합니다 -->
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
<ItemGroup>
<!-- 버전은 NuGet 패키지 관리자에서 고른 값이 들어갑니다 -->
<PackageReference Include="Microsoft.WindowsAppSDK" Version="(선택한 안정 버전)" />
</ItemGroup>
</Project>
<WindowsPackageType>None</WindowsPackageType>이 핵심입니다. 패키징되지 않은 앱은 Windows App SDK 기능을 쓰기 전에 런타임을 초기화해야 하며, 이 속성을 적어 두면 시작 시 자동 초기화(auto-initialization)가 켜집니다. 빌드하면 MddBootstrapAutoInitializer.cs와 WindowsAppSDK-VersionInfo.cs가 프로젝트에 추가됩니다. 세밀한 제어가 필요할 때만 자동 초기화 대신 부트스트랩 API를 직접 호출합니다.2
7.2. 배포 대상에 런타임 도입
여기가 놓치기 쉽습니다. 패키징되지 않은 앱의 경우, Windows App SDK 런타임을 사용자 PC에 전달할 책임은 배포하는 쪽에 있습니다.23 exe만 배포해서는 동작하지 않습니다.
전제로, 패키징되지 않은 앱에서는 Visual C++ 재배포 가능 패키지도 필요합니다.23 도입 방법은 두 가지입니다.
| 방법 | 내용 | 맞는 경우 |
|---|---|---|
| 설치 프로그램을 실행한다 | 공식 배포의 WindowsAppRuntimeInstall.exe를 실행합니다. x86 / x64 / Arm64 각각에 별도 설치 프로그램이 있으며, 그 이름에 해당하는 아키텍처의 패키지만 배치합니다23 |
보통은 이쪽입니다 |
| MSIX 패키지를 직접 넣는다 | 자사 설치 프로그램이나 MSI에 MSIX 패키지를 함께 넣어 배치합니다23 | 배치를 세밀하게 제어하고 싶을 때 |
설치 프로그램은 UI가 없으므로, 자사 설치 프로그램에서 연쇄(chain)해 호출하는 형태가 됩니다. 무인 실행 옵션이 준비되어 있습니다.23
:: 화면 표시·텍스트 출력을 전혀 내지 않고 도입합니다
WindowsAppRuntimeInstall.exe --quiet
:: 사용 중인 Windows App SDK 프로세스를 종료시킨 뒤 강제 업데이트합니다(1.1 이후)
WindowsAppRuntimeInstall.exe --force
도입 때 짚어 둘 점은 세 가지입니다.23
- 관리자 권한 유무에 따라 결과가 달라집니다. 상승해 실행되고, 실행 사용자가 관리자 권한을 가진 경우에는 시스템 전체(앞으로 추가되는 사용자 포함)에 등록됩니다. 그렇지 않으면 실행한 사용자만 도입됩니다. 반환값
0x80070005가 바로 이 「상승하지 않았거나/관리자 권한이 없는」 경우입니다. - 반환값으로 성패를 판정합니다.
0x0이 성공,0x80073d06은 하나 이상의 패키지 도입 실패입니다. 자사 설치 프로그램에서 연쇄할 때는 이 반환값을 반드시 확인하세요. - 개발 머신에 넣는 런타임은, 참조하는 NuGet 패키지의 버전·릴리스 채널에 맞춥니다.2 「개발 머신에서는 나오는데 배포 대상에서는 안 나온다」의 원인이 여기인 경우는 드물지 않습니다.
MSIX로 패키징해 배포하는 경우에는 런타임 배포를 패키지 종속성으로 다룰 수 있지만, 대신 Package.appxmanifest에 COM 액티베이터 기재가 필요합니다(7.3 마지막을 참조).
7.3. 최소 구현
using System;
using System.Windows.Forms;
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
internal static class ToastActivation
{
private static readonly object Gate = new();
private static Action? _pending;
private static bool _uiReady;
// 시작 시 한 번만. 핸들러 등록 → Register() 순서가 중요합니다
public static void Initialize()
{
AppNotificationManager.Default.NotificationInvoked += (_, args) =>
{
// 토스트 클릭이나 버튼 누름이 여기로 옵니다(인수는 args.Arguments로 구분)
if (!args.Arguments.TryGetValue("action", out var action) || action != "openLog")
{
return;
}
// NotificationInvoked는 백그라운드 스레드에서 발생합니다.
// UI를 건드리는 처리는 반드시 UI 스레드로 넘기세요
Dispatch(ShowLogWindow);
};
AppNotificationManager.Default.Register();
}
// MainForm의 OnShown에서 호출합니다. 폼이 생기기 전에 도착한 분을 여기서 흘립니다
public static void MarkUiReady()
{
Action? pending;
lock (Gate)
{
_uiReady = true;
pending = _pending;
_pending = null;
}
// OnShown은 UI 스레드이므로 그대로 실행해도 됩니다
pending?.Invoke();
}
private static void Dispatch(Action action)
{
lock (Gate)
{
if (!_uiReady)
{
// 토스트 클릭이 앱 시작 그 자체였던 경우, 여기에 온
// 시점에는 아직 폼이 하나도 없습니다. 맡아 두었다가 MarkUiReady가 흘립니다
_pending = action;
return;
}
}
MainWindow()?.Invoke(action);
// WPF라면: Application.Current.Dispatcher.Invoke(action);
}
private static Form? MainWindow() =>
Application.OpenForms.Count > 0 ? Application.OpenForms[0] : null;
private static void ShowLogWindow() { /* 로그 창을 엽니다 */ }
}
// MainForm 쪽. 시작 계기가 토스트 클릭이었던 경우는 여기서 받습니다
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
ToastActivation.MarkUiReady();
}
ToastActivation.Initialize();
// 알림을 냅니다
var notification = new AppNotificationBuilder()
.AddText("수주 가져오기가 완료되었습니다")
.AddText("신규 12건, 오류 1건")
.AddButton(new AppNotificationButton("로그 열기")
.AddArgument("action", "openLog"))
.BuildNotification();
AppNotificationManager.Default.Show(notification);
실무에서 주의할 점은 다음과 같습니다.
NotificationInvoked구독은Register()보다 앞에서 합니다. 순서를 틀리면, 토스트 클릭으로 앱이 시작될 때 이벤트를 놓칩니다.12- 클릭으로 앱이 시작된 경우, 핸들러가 동작하는 시점에는 아직 폼이 하나도 없습니다.
Application.OpenForms를 보고null이면 아무것도 하지 않는다고 쓰면, 이 경로의 조작이 조용히 버려집니다. 위처럼 일단 맡아 두었다가, 폼이 표시된 뒤에 흘리세요. 순서를 맞춰 이벤트를 받아도, 여기서 버리면 마찬가지입니다. NotificationInvoked는 백그라운드 스레드에서 발생합니다. 공식 샘플에도 「백그라운드 스레드에서 발생하므로, UI 업데이트는 UI 스레드로 디스패치할 것」이라고 명시되어 있습니다. 핸들러에서 창을 열거나 컨트롤을 건드리는 처리를 그대로 쓰면, 스레드 관련 예외나 알 수 없는 멈춤을 만납니다. WPF라면Application.Current.Dispatcher.Invoke(...), WinForms라면Control.Invoke(...)로 UI 스레드에 넘기세요.12- 앱 종료 전에는
AppNotificationManager.Default.Unregister()를 호출합니다. 공식 문서에, 종료 전 Unregister로 리소스를 정리하고, 이후 알림 클릭에서 앱이 올바르게 시작되도록 해야 한다고 명시되어 있습니다. 3장의 「종료」 메뉴 처리에NotifyIcon.Dispose()와 나란히 넣으세요(앞으로 알림 기능을 전혀 쓰지 않는 앱을 제거할 때 등은, 등록 정보까지 지우는UnregisterAll()이 따로 있습니다).24 - 패키징되지 않은 앱에서는
Register()가 COM 서버 등록·AUMID에 해당하는 처리를 자동으로 합니다. 이전 세대(그대로의 WinRT)에서 필요했던 바로가기 설치나 레지스트리 등록은 필요 없습니다. MSIX로 패키징하는 경우에는 반대로 Package.appxmanifest에 COM 액티베이터 기재가 필요합니다.12 - Windows App SDK 런타임을 함께 넣는 self-contained 배포를 고를 때는 주의가 필요합니다.
AppNotificationManager는 Windows App SDK의 Singleton 패키지에 의존하며, self-contained 앱에서 호출할 때의 고려 사항이 공식으로 안내되어 있습니다. 배포 방식을 정하기 전에 확인하세요.25 - 관리자로 상승한 프로세스에서의 알림은 지원되지 않습니다.
Show는 예외를 내지 않고 조용히 실패하므로, 원인을 알아채기 어려운 문제입니다. 상승이 필요한 앱은 알림만 비상승 프로세스에 맡기는 분리를 검토하세요(이 분리의 생각은 「Windows 앱에서 「관리자 권한이 필요한 처리만」 분리하는 구체적인 작성법」에서 다룹니다).13 - 토스트가 클릭되었을 때 앱이 실행 중이 아니면 프로세스가 새로 시작됩니다. 트레이 상주 앱에서는 다중 실행 방지(3장 참조)와 조합해, 기존 인스턴스로 처리를 넘기는 설계를 해 둘 필요가 있습니다.
8. 한 단계 나아간 사용법 ── 진행률 막대가 있는 토스트와 알림 뒷정리
8.1. 진행을 토스트 안에서 갱신한다 ── UpdateAsync
「수주 데이터 120건을 가져오는 중」처럼 긴 처리의 진행은, 알림을 여러 번 다시 내는 것이 아니라 한 장의 토스트 안 진행률 막대를 다시 쓰는 것이 올바른 구현입니다. AppNotification에는 이를 위한 데이터 바인딩과 업데이트 장치가 있습니다.26
구현은 세 단계입니다. 먼저 진행률 막대의 각 값을 Bind 계열 메서드로 플레이스홀더로 두고, Tag(그리고 필요하면 Group)로 알림을 식별할 수 있게 한 뒤 표시합니다. 그다음은 AppNotificationProgressData를 UpdateAsync에 넘기기만 하면, 이미 표시된 토스트의 내용만 바뀝니다.
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);
시퀀스 번호는 업데이트 순서를 나타내는 0이 아닌 정수이며, 여러 업데이트가 앞뒤로 도착하면 가장 큰 번호가 표시됩니다. 비동기 처리에서 동시에 업데이트를 던져도 옛 값으로 되감기지 않게 하는 장치이므로, 단조 증가시키세요.27
8.2. 「갱신」과 「교체」의 구분
같은 Tag로 새 알림을 다시 Show하는 「교체」로도 겉보기에는 갱신할 수 있지만, 동작이 다릅니다.26
| 교체(같은 Tag로 다시 Show) | 갱신(UpdateAsync) | |
|---|---|---|
| 알림 센터 안 위치 | 맨 앞으로 이동합니다 | 그 자리에 머뭅니다 |
| 바꿀 수 있는 내용 | 레이아웃을 포함해 전부 | 바인드한 진행률 막대와 맨 앞 텍스트만 |
| 팝업 | 다시 표시할 수 있습니다 | 다시 표시되지 않습니다(조용히 바뀝니다) |
| 사용자가 이미 닫은 경우 | 항상 보내집니다 | 실패합니다 |
공식 문서의 권장도 분명하며, 중간 경과는 갱신, 완료는 교체입니다. 진행이 50%에서 65%가 된 것을 매번 팝업으로 알릴 필요는 없지만, 「가져오기 완료(오류 3건)」은 사용자가 진행 알림을 이미 닫았더라도 도착하기를 바라기 때문입니다. 완료 때는 진행률 막대를 빼고 버튼(「오류 내용 보기」 등)을 붙인 다른 레이아웃으로 교체하는 형태가 실무의 정석이 됩니다.
8.3. 알림 센터에 남은 옛 알림을 치운다
토스트는 사용자가 지울 때까지 알림 센터에 쌓입니다. 상주 앱이 매일 여러 건을 내면, 알림 센터가 자기 앱의 옛 알림으로 가득 차 오히려 중요한 알림이 묻힙니다.
Tag / Group을 붙여 두면, 자기 앱의 알림을 코드에서 삭제할 수 있습니다(태그 지정·그룹 지정·전체 삭제). 만료도 설정할 수 있으므로, 「진행 계열은 완료 때 지운다」「정보 계열은 만료를 짧게 둔다」 같은 정리 규칙을, 알림을 내는 설계와 동시에 정해 두는 것이 좋습니다.28
9. 알림이 「도착하지 않는」 경우를 설계에 넣는다
토스트 알림은 확실한 전달 수단이 아닙니다. 표시되지 않거나 눈에 띄지 않는 경로가 정상 동작으로 존재합니다.
| 경우 | 동작 |
|---|---|
| 방해 금지(Focus Assist / Do not disturb)가 켜짐 | 배너는 표시되지 않고, 알림은 알림 센터로 바로 갑니다 |
| 앱 단위·시스템 전체에서 알림이 꺼짐 | 표시되지 않습니다 |
| 관리자로 상승한 프로세스의 AppNotification | 지원되지 않습니다. 표시되지 않습니다 |
| 전체 화면 앱 실행 중 등 | OS 판단으로 억제되는 경우가 있습니다 |
방해 금지는 Windows 10에서는 「집중 지원(Focus assist)」, Windows 11에서는 「설정 → 시스템 → 알림」의 「방해 금지」 토글로, 사용자나 그룹 정책이 제어합니다. OS 업그레이드 직후 1시간은 자동으로 켜지는 사양까지 있습니다.14 집중 지원의 동작은 「알림을 내지 않는다」가 아니라 「배너 표시를 억제한 채 알림 센터로 보낸다」이므로, 사용자가 나중에 알림 센터를 열면 보입니다.15
확인할 위치는 메뉴 전체 경로로 갖고 있으면 현장에서 빠릅니다. 「알림이 안 나온다」는 전화나 채팅으로 안내하는 경우가 많기 때문입니다.
| 확인하고 싶은 것 | 어디를 볼까(Windows 11) |
|---|---|
| 방해 금지가 켜져 있지 않은가 | 설정 → 시스템 → 알림 → 「방해 금지」 토글 |
| 자동으로 방해 금지가 되는 조건이 설정되어 있지 않은가 | 같은 「알림」 화면 안에 있는, 자동으로 방해 금지를 켜는 조건 설정 |
| 자기 앱의 알림이 꺼져 있지 않은가 | 같은 「알림」 화면을 아래로 스크롤해, 앱별 목록에서 자기 앱을 찾습니다 |
| 배너만 안 나오는 것인지, 알림 자체가 도착하지 않은 것인지 | 작업 표시줄 오른쪽 끝의 날짜와 시각을 클릭해 알림 센터를 열고, 알림이 쌓여 있는지 봅니다 |
| 트레이 아이콘이 숨어 있을 뿐은 아닌가 | 설정 → 개인 설정 → 작업 표시줄 → 기타 시스템 트레이 아이콘(5.1장) |
Windows 10의 경우, 방해 금지에 해당하는 것은 「설정 → 시스템 → 집중 지원」입니다. 알림 센터에 도착했는지를 먼저 확인하는 것이 원인을 가르는 지름길이며, 도착해 있으면 OS 쪽 배너 억제(방해 금지 등), 도착하지 않았으면 앱 쪽 송신 자체를 의심하는 식으로 분기할 수 있습니다.
설계에 반영할 점은 단순합니다.
- 토스트는 「눈치채 주면 빨리 대응할 수 있는」 보조 채널로 쓰고, 업무상 필수 정보는 앱 안 화면·목록에서 반드시 확인할 수 있게 합니다. 「토스트가 나왔을 것」을 업무 흐름의 전제로 두지 마세요.
- 꼭 끼어들어야 하는 알림(긴급 정지 경고 등)은
AppNotificationScenario.Urgent(중요한 알림)로 사용자 허가 아래 방해 금지를 뚫는 장치가 있지만29, 남용하면 사용자는 앱 알림 자체를 끕니다. 정말 긴급한 것만으로 좁혀야 합니다.
외부 장치의 이상 등을 화면에 어떻게 낼 것인가라는, 더 넓은 이야기는 「외부 기기 상태 확인과 표시의 베스트 프랙티스」도 참고하세요.
10. Windows 서비스에서 알리고 싶을 때
「상주 서비스가 이상을 감지하면, 로그온 중인 사용자에게 토스트로 알리고 싶다」는 요건은 자연스럽게 나오지만, 서비스에서 직접 토스트를 보낼 수는 없습니다. Windows Vista 이후 서비스는 세션 0이라는 전용 세션에서 동작하며, 사용자와 대화하는 UI를 가질 수 없기 때문입니다.3
정석은 UI를 사용자 세션 쪽 프로세스로 분리하는 구성입니다.
[세션 0] [사용자 세션]
Windows 서비스 ←─ IPC ─→ 트레이 상주 에이전트
(감시·업무 처리) 명명된 파이프 등 (NotifyIcon + AppNotification)
- 서비스는 감시·업무 처리에 전념하고, 알리고 싶은 사건을 IPC로 에이전트에 보냅니다.
- 에이전트는 사용자 로그온 때 시작하는 트레이 상주 앱(이 글의 2~8장 그 자체)이며, 받은 사건을 토스트나 아이콘 변화로 표시합니다.
- IPC 수단 선택은 「Windows의 프로세스 간 통신을 어떻게 고를까」, 서비스 쪽 만드는 법은 「Windows서비스 만드는 법과 운영」, 세션 구조 자체는 「Windows 세션 분리를 어떻게 이해할 것인가 ── Session 0·RDP·다중 사용자 동시 실행」에서 정리했습니다.
여러 사용자가 동시에 로그온하는 환경(RDP·공유 단말)에서는, 에이전트는 세션마다 하나씩 동작하게 됩니다. 「누구에게 알려야 하는가」(전원인지, 특정 사용자만인지)를 요건으로 먼저 정해 두면 설계가 흔들리지 않습니다.
11. 정리
- 트레이 상주는
NotifyIcon이 기본입니다.Icon+Visible = true로 표시하고,Text는 127자 제한(.NET 6+), 종료 경로에서는Dispose()를 반드시 거칩니다. 아이콘과 툴팁은 「현재 상태」를 보이는 상시 표시로도 씁니다. - 「×로 닫아도 트레이에 남긴다」는
FormClosing취소로 구현하지만,CloseReason.UserClosing일 때만 취소하고, 명시적인 「종료」 메뉴를 반드시 마련합니다. - 아이콘의 항상 표시는 사용자만 정할 수 있습니다. Explorer 재시작 때 사라짐은
TaskbarCreated재등록으로 대응합니다(WinForms는 자동). - 토스트 알림의 신규 구현은 Windows App SDK의
AppNotificationManager한 가지입니다. 패키징되지 않은 exe에서도 패키지 ID 없이 동작하고,Register()가 번거로운 등록을 자동화합니다. Community Toolkit 알림은 보관 처리되었고, 그대로의 WinRT는 유지보수만입니다. 버튼도 이력도 진행도 필요 없는 가벼운 알림이라면ShowBalloonTip으로 끝내는 판단도 남습니다(6장). - 패키징되지 않은 앱에서는, Windows App SDK 런타임을 배포 대상에 전달하는 것은 배포 쪽 책임입니다. csproj에
<WindowsPackageType>None</WindowsPackageType>을 적고, 설치 프로그램에서WindowsAppRuntimeInstall.exe를 연쇄시키는 것까지를 배포 설계에 넣으세요(7.2장). - 긴 처리의 중간 경과는
UpdateAsync로 한 장의 토스트 진행률 막대를 조용히 다시 쓰고, 완료는 교체로 팝업시키는 것이 정석입니다.Tag/Group을 붙여, 옛 알림 삭제까지 설계에 넣습니다. - 토스트는 방해 금지나 알림 끄기로 도착하지 않을 수 있는 보조 채널입니다. 필수 정보는 앱 안에서 확인할 수 있게 설계하고, 상승한 프로세스에서의 알림이 지원되지 않는다는 점에도 주의하세요.
- 서비스에서의 알림은, 사용자 세션 쪽 트레이 상주 에이전트+IPC라는 분리 구성으로 구현합니다.
관련 기사
- Windows 앱의 다중 실행 방지 ── 이름 있는 Mutex와 이중 실행 시 활성화
- Windows서비스 만드는 법과 운영 ── 작업 스케줄러와의 구분부터 BackgroundService의 서비스화까지
- Windows의 프로세스 간 통신을 어떻게 고를까 ── Named Pipe / TCP / gRPC / 공유 메모리 / COM 판단표
- Windows 세션 분리를 어떻게 이해할 것인가 ── Session 0·RDP·다중 사용자 동시 실행
- .NET Generic Host와 BackgroundService를 데스크톱 앱에서 쓰는 이유
- Windows 앱에서 “관리자 권한이 필요한 처리만” 분리하는 구체적인 작성법
- 외부 기기의 상태 확인과 표시의 베스트 프랙티스 - 『접속 중』만으로 끝내지 않는 설계
관련 상담 영역
合同会社小村ソフト에서는 트레이 상주형 감시 도구와 알림 에이전트의 설계·구현, Windows 서비스와 조합한 상주 시스템의 구성 검토, 기존 상주 앱의 장애 조사를 다룹니다.
참고 링크
-
Microsoft Learn, Notification Area (Windows Shell). 아이콘이 기본으로 오버플로 영역에 추가된다는 점, 알림 영역으로의 승격을 정할 수 있는 것은 사용자뿐이라는 점, 필요 없어진 아이콘은 NIM_DELETE로 삭제해야 한다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Use the Windows App SDK in an existing project. WPF·WinForms·Win32 데스크톱 앱이 기본으로 패키징되지 않는다는 점, NuGet 패키지
Microsoft.WindowsAppSDK를 도입하고 메인 패키지를 넣는 것이 권장된다는 점, 패키징되지 않은 앱은 Windows App SDK 기능을 쓰기 전에 런타임 초기화가 필요하며<WindowsPackageType>None</WindowsPackageType>으로 시작 시 자동 초기화가 켜진다는 점, 빌드 때MddBootstrapAutoInitializer.cs와WindowsAppSDK-VersionInfo.cs가 추가된다는 점, 개발 머신에 도입하는 런타임을 NuGet 패키지 버전·릴리스 채널에 맞춰야 한다는 점에 대해. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Service Changes for Windows Vista / Interactive Services. 세션 0이 서비스 전용이며 사용자와의 대화를 지원하지 않는다는 점, 사용자에게 표시하려면 CreateProcessAsUser로 사용자 세션에 프로세스를 만들고 IPC로 연계하는 방법이 권장된다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, NotifyIcon Class / NotifyIcon.Visible Property. 알림 영역에 아이콘을 표시하는 구성 요소라는 점, 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의 Maintenance only 위치에 대해. ↩ ↩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가 패키지·비패키지 모두 대응이며 패키지 ID를 필요로 하지 않는다는 점에 대해. ↩ ↩2
-
Microsoft Learn, Quickstart: App notifications in the Windows App SDK. AppNotificationManager/AppNotificationBuilder 사용법, 패키징되지 않은 앱에서 Register()에 의한 자동 등록, NotificationInvoked를 Register() 전에 구독해야 한다는 점, 패키지 앱에서의 매니페스트 기재에 대해. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, App notifications overview. 관리자로 상승한 앱에서의 알림 송신이 지원되지 않는다는 점에 대해. ↩ ↩2
-
Microsoft Learn, Notifications don’t appear or aren’t displayed. 방해 금지(Quiet Hours / Focus Assist)에 의한 알림 억제, OS 업그레이드 후 1시간의 자동 활성화, 알림이 표시되지 않을 때의 확인 절차에 대해. ↩ ↩2
-
Microsoft Learn, Focus assist. 집중 지원이 알림의 배너 표시를 억제하고 알림 센터로 직접 보내는 장치라는 점에 대해. ↩ ↩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, The Taskbar. 작업 표시줄 생성 때 TaskbarCreated 메시지가 브로드캐스트되며, 수신하면 트레이 아이콘을 다시 등록해야 한다는 점에 대해. ↩
-
Microsoft Learn, Shell_NotifyIconW function. 풍선 알림이 Windows 10에서는 토스트(배너)로 표시되고 알림 센터에 남는다는 점, Windows 11에서는 잠깐의 표시가 되며 알림 센터에 남지 않는다는 점, 표시 시간이 접근성 설정을 따른다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Send a local toast notification from a C# app (Win32/クラシックAPI). 그대로의 ToastNotificationManager를 쓰는 경우, AUMID가 붙은 바로가기를 시작 메뉴에 등록하는 것이 필수라는 점에 대해. ↩
-
Microsoft Learn, Windows App SDK deployment guide for framework-dependent apps packaged with external location or unpackaged. 패키징되지 않은 앱의 개발자가 사용자에게 Windows App SDK 런타임을 배포할 책임을 진다는 점, Visual C++ 재배포 가능 패키지가 필요하다는 점, 설치 프로그램 방식과 MSIX 패키지 직접 배치의 두 방식이 있다는 점, 설치 프로그램이 x86·x64·Arm64마다 나뉘며 그 이름 아키텍처의 패키지만 배치한다는 점,
--quiet와--force옵션, 상승이고 관리자 권한인 경우에는 시스템 전체에, 그렇지 않으면 실행 사용자만 도입된다는 점, 반환값0x0·0x80073d06·0x80070005의 의미, 자사 설치에서 ShellExecute로 연쇄할 수 있다는 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
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. 시퀀스 번호가 업데이트 순서를 지정하는 0이 아닌 정수이며, 여러 업데이트 데이터를 수신하면 가장 큰 번호의 데이터가 표시된다는 점에 대해. ↩
-
Microsoft Learn, Remove app notifications. 태그·그룹 부여, 표시된 알림 삭제, 만료 설정에 대해. ↩
-
Microsoft Learn, App notification content. scenario=”urgent”의 중요한 알림이 사용자 허가 아래 방해 금지 설정보다 우선된다는 점에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 프린터 드라이버 제공 종료 ── 업무 앱의 장표·라벨 인쇄는 어떻게 대비할 것인가
Microsoft는 v3/v4 프린터 드라이버의 제공 종료를 단계적으로 진행하고 있으며, 2026년 7월부터는 IPP 클래스 드라이버가 우선됩니다. Windows protected print mode에서 무엇이 사라지는지, 업무 앱의 장표·라벨 ...
WinForms / WPF 앱의 CI/CD 실전 ── GitHub Actions로 빌드부터 서명·배포까지 자동화하기
WinForms / WPF 앱의 CI/CD를 GitHub Actions로 구성하는 실무 가이드입니다. windows-latest에서 빌드+테스트를 돌리는 최소 YAML, 태그 기반 버전 부여, signtool 서명 연동, MSI/MSIX/Clic...
슬립・최대 절전 모드・Modern Standby와 장시간 가동 앱 ── '한밤중에 멈춰 있었다'를 설계로 막는다
장시간 동작하는 Windows 앱이 '아침에 보니 멈춰 있었다'가 되는 원인을 S3 슬립/최대 절전 모드/Modern Standby의 차이에서 정리합니다. 슬립 중 타이머와 TCP 연결의 동작, SetThreadExecutionState로 억제하...
WinForms/WPF 앱의 다국어화 ── resx·satellite assembly·culture 전환의 실무
Windows 데스크톱 앱의 다국어화를 정리합니다. CurrentCulture와 CurrentUICulture의 차이, resx와 satellite assembly의 구조, WPF에서 현실적인 방식 선택, 런타임 언어 전환, 서식·RTL까지 설명...
Windows 업무 앱의 인쇄와 PDF 출력 ── System.Drawing.Printing / WPF / 장표 라이브러리 구분
PrintDocument로 하는 WinForms 인쇄, WPF의 FlowDocument/FixedDocument 인쇄, PDF 출력 선택지를 요건별 판단표로 정리합니다. 페이지 나눔 제어, DPI 어긋남, Windows 서비스에서의 인쇄 위험까지...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
UI 스레드 & 타이머
WPF / WinForms UI 스레드, async 흐름, Dispatcher 사용, 타이머 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기술 상담 & 설계 리뷰
설계 방향, 아키텍처 경계, 수명 관리, 기존 Windows 자산 처리 방법을 정리하는 데 도움을 드립니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- WPF로 작업 트레이 상주 앱을 만들려면 어떻게 해야 하나요?
- WPF에는 NotifyIcon에 해당하는 컨트롤이 없으므로, csproj에서 UseWPF와 UseWindowsForms를 함께 켜고 WinForms의 NotifyIcon을 쓰는 구성이 최소입니다. XAML에 가깝게 쓰고 싶다면 H.NotifyIcon 같은 서드파티 라이브러리라는 선택지도 있지만, Microsoft 공식이 아니라는 점을 감안해 채택 여부를 판단하시기 바랍니다.
- 토스트 알림 API는 어떤 것을 써야 하나요?
- 2026년 시점의 신규 구현이라면 WinForms/WPF/패키징되지 않은 앱 모두 Windows App SDK의 AppNotificationManager가 공식 권장입니다. Microsoft.Toolkit.Uwp.Notifications는 보관(archive) 처리되어 현재 Community Toolkit에 들어 있지 않고, WinRT의 ToastNotificationManager는 유지보수만 하는 위치입니다. 기존 코드가 뒤의 둘로 동작하고 있다면 서둘러 다시 쓸 필요는 없지만, 새로 고를 이유는 없습니다.
- 토스트 알림이 표시되지 않을 때는 무엇을 확인하면 되나요?
- 먼저 OS 쪽 설정입니다. 방해 금지(Focus Assist)가 켜져 있으면 알림은 배너로 표시되지 않고 알림 센터로 바로 가며, 설정 앱에서 앱 단위·시스템 전체 알림이 꺼져 있는 경우에도 표시되지 않습니다. 구현 쪽에서는 관리자 권한으로 상승한 프로세스의 AppNotification은 지원되지 않아 표시에 실패한다는 점, 그대로의 Windows.UI.Notifications를 AUMID 없는 데스크톱 앱에서 호출해도 표시되지 않는다는 점이 대표적인 원인입니다.
- Windows 서비스에서 토스트 알림을 보낼 수 있나요?
- 보낼 수 없습니다. Windows Vista 이후 서비스는 세션 0에서 동작하며, 사용자와 대화하는 UI를 가질 수 없기 때문입니다. 사용자에게 알리려면 사용자 세션 쪽에 트레이 상주 에이전트 프로세스를 두고, 서비스와는 명명된 파이프 등의 IPC로 연계해 알림 표시는 에이전트가 맡는 구성이 정석입니다.