Skip to content

Set default window icon from host executable during window creation#11054

Open
yeelam-gordon wants to merge 2 commits intomicrosoft:mainfrom
yeelam-gordon:fix/default-window-icon
Open

Set default window icon from host executable during window creation#11054
yeelam-gordon wants to merge 2 commits intomicrosoft:mainfrom
yeelam-gordon:fix/default-window-icon

Conversation

@yeelam-gordon
Copy link
Copy Markdown

@yeelam-gordon yeelam-gordon commented Apr 3, 2026

Summary

WinUI desktop windows show no icon (or the system default) in the taskbar, Alt+Tab, and title bar. This PR fixes the issue by loading the application's icon from the host executable during window creation.

The Problem

As reported in WinUI-Gallery#1512, WinUI apps display the default Windows icon instead of the app's own icon in Alt+Tab / taskbar:
image

(Screenshot from WinUI-Gallery#1512 -- the WinUI Gallery app shows a default icon in Alt+Tab instead of its actual icon)

Currently the workaround is for every app to manually call AppWindow.SetIcon("path/to/icon.ico") after window creation. But this is:

  1. Error-prone -- relative paths resolve against CWD, which differs between VS debugging and installed/packaged apps
  2. Unnecessary -- the app's icon is already embedded in the executable as a Win32 resource; WinUI just doesn't use it
  3. Not discoverable -- developers expect the icon from Package.appxmanifest or the exe resource to "just work"

Root Cause

DesktopWindowImpl::RegisterDesktopWindowClass() registers WinUIDesktopWin32WindowClass with hIcon = NULL:

WNDCLASSEXW wndClassEx = {};  // zero-initialized -- hIcon and hIconSm are NULL
wndClassEx.cbSize = sizeof(WNDCLASSEX);
wndClassEx.style = CS_HREDRAW | CS_VREDRAW;
wndClassEx.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndClassEx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndClassEx.lpszClassName = s_windowClassName;
// hIcon is NEVER set -- remains NULL

This is because g_hInstance refers to the WinUI framework DLL, not the application's executable. The DLL has no icon resources.

After window creation in OnCreate(), even though the AppWindow object is obtained, no icon is ever loaded or set. Every WinUI window starts with a blank/default icon.

Fix

In OnCreate(), after AppWindow initialization, we now load the app's own icon:

  1. Get the host executable's module via GetModuleHandleW(nullptr)
  2. Dynamically find the first icon group resource using EnumResourceNamesW with RT_GROUP_ICON
  3. Load both large and small icon sizes via LoadImageW with LR_SHARED
  4. Set the icon via WM_SETICON

Why this is better than the AppWindow.SetIcon() workaround

AppWindow.SetIcon() workaround This fix
Who does the work Every app developer WinUI framework (automatic)
Path resolution Relative paths break in packaged apps Uses module handle -- no paths involved
Icon source Requires a separate .ico file Uses the icon already embedded in the exe
Discoverability Developer must know to call it Just works -- icon appears automatically
Override N/A Apps can still call AppWindow.SetIcon() to override

Why EnumResourceNamesW instead of a hardcoded resource ID

Different project templates use different icon resource IDs (1, 101, 107, 128, etc.). EnumResourceNamesW(hExeModule, RT_GROUP_ICON, ...) dynamically finds the first icon group -- the same icon the Windows shell displays for the .exe -- regardless of its numeric ID.

Compatibility

Scenario Before After
App with icon resource in exe No icon shown Correct icon shown
App without icon resource System default System default (unchanged -- silently skips)
App calls AppWindow.SetIcon() later Works Works (later WM_SETICON overrides)
Fully packaged app No icon Icon from exe resource
Sparse-packaged app No icon Icon from exe resource
Unpackaged app No icon Icon from exe resource
Framework-dependent deployment No icon Icon from host exe
Self-contained deployment No icon Icon from host exe

Performance

Runs once per window during OnCreate() -- all calls are trivial:

  • GetModuleHandleW(nullptr) -- no module load, just returns cached handle
  • EnumResourceNamesW -- scans PE resource directory, stops at first hit
  • LoadImageW with LR_SHARED -- system-cached
  • SendMessageW to own window on same thread -- synchronous, no marshaling

Memory and Thread Safety

  • LR_SHARED icons are system-managed -- no DestroyIcon() needed, no leak
  • OnCreate() runs synchronously during CreateWindowExW() on the creating thread -- no threading concerns

Fixes #10856
Relates to microsoft/WinUI-Gallery#1512

WinUI's DesktopWindowImpl registers its window class (WinUIDesktopWin32WindowClass)
with hIcon=NULL because g_hInstance refers to the WinUI framework DLL, not the
application's executable. This causes windows to display no icon (or the system
default) in the taskbar, Alt+Tab, and title bar.

Fix: During OnCreate(), after AppWindow initialization, enumerate the first
RT_GROUP_ICON resource from the host executable via GetModuleHandleW(nullptr)
and set it on the window via WM_SETICON. This approach:

- Works for any resource ID (uses EnumResourceNamesW instead of hardcoded ID)
- Handles both packaged and unpackaged apps
- Uses LR_SHARED for system-managed icon lifetime (no leak)
- Silently skips if no icon resource exists (preserving current behavior)
- Does not interfere with AppWindow.SetIcon() which can override later

Fixes: microsoft#10856
Relates: WinUI-Gallery#1512

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the needs-triage Issue needs to be triaged by the area owners label Apr 3, 2026
@yeelam-gordon
Copy link
Copy Markdown
Author

/azp run

@azure-pipelines
Copy link
Copy Markdown

Commenter does not have sufficient privileges for PR 11054 in repo microsoft/microsoft-ui-xaml

- Use GetDpiForWindow + GetSystemMetricsForDpi instead of GetSystemMetrics
  to load icons at the correct size for the window's DPI
- Remove unused HMODULE field from IconSearchContext struct

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SparseApp (Unpackaged + Self-contained WinUI3) cannot locate module-specific PRI (AssemblyName.pri)

1 participant