From d79cc924e8669134a0ecc6b20dd15cc601f9581c Mon Sep 17 00:00:00 2001 From: "Gordon Lam (SH)" Date: Fri, 3 Apr 2026 17:46:27 +0800 Subject: [PATCH 1/2] Set default window icon from host executable during window creation 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: #10856 Relates: WinUI-Gallery#1512 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp b/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp index 204e073f44..d9467ef78f 100644 --- a/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp +++ b/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp @@ -136,6 +136,71 @@ void DesktopWindowImpl::OnCreate() noexcept // instead of whenever user code calls appwindow api, providing subclassing consistency ctl::ComPtr appWindow; IFCFAILFAST(get_AppWindowImpl(&appWindow)); + + // Set a default window icon from the application's executable. + // + // The WinUI window class is registered with hIcon=NULL because g_hInstance refers + // to the WinUI framework DLL, which does not contain the app's icon. As a result, + // windows start with no icon in the taskbar, Alt+Tab, and title bar. + // + // For packaged apps (including sparse-packaged apps), the shell may briefly show the + // correct icon from the package manifest during launch, but once the WinUI window is + // created with a NULL-icon class, the icon reverts to the system default. + // See: https://github.com/microsoft/microsoft-ui-xaml/issues/10856 + // https://github.com/microsoft/WinUI-Gallery/issues/1512 + // + // We load the icon from the host executable (not the framework DLL) using + // GetModuleHandleW(nullptr). This works for both packaged and unpackaged apps: + // - Packaged apps: packaging tools embed the app icon into the exe as a resource + // - Unpackaged apps: the exe typically contains its own icon resource + // + // If the exe has no icon resource, LoadImageW returns NULL and we silently skip — + // preserving the existing behavior (no icon). Apps can always override this default + // by calling AppWindow.SetIcon() after window creation. + { + HMODULE hExeModule = ::GetModuleHandleW(nullptr); + if (hExeModule) + { + // Enumerate the first icon group resource in the executable — this is the icon + // the shell uses for the .exe file. Unlike a hardcoded resource ID (which varies + // by project template), EnumResourceNamesW always finds the first icon group + // regardless of its ID. + // LR_SHARED lets the system manage the icon lifetime — no DestroyIcon needed. + struct IconSearchContext { HMODULE module; LPCWSTR resourceId; }; + IconSearchContext ctx { hExeModule, nullptr }; + + ::EnumResourceNamesW(hExeModule, RT_GROUP_ICON, + [](HMODULE, LPCWSTR, LPWSTR lpName, LONG_PTR lParam) -> BOOL + { + auto* pCtx = reinterpret_cast(lParam); + pCtx->resourceId = lpName; + return FALSE; // stop after first + }, + reinterpret_cast(&ctx)); + + if (ctx.resourceId) + { + HICON hIconLarge = static_cast(::LoadImageW( + hExeModule, ctx.resourceId, IMAGE_ICON, + ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), + LR_DEFAULTCOLOR | LR_SHARED)); + + HICON hIconSmall = static_cast(::LoadImageW( + hExeModule, ctx.resourceId, IMAGE_ICON, + ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR | LR_SHARED)); + + if (hIconLarge) + { + ::SendMessageW(m_hwnd.get(), WM_SETICON, ICON_BIG, reinterpret_cast(hIconLarge)); + } + if (hIconSmall) + { + ::SendMessageW(m_hwnd.get(), WM_SETICON, ICON_SMALL, reinterpret_cast(hIconSmall)); + } + } + } + } } DesktopWindowImpl::~DesktopWindowImpl() From b0629f4380f060015d28b9584dce0aadeffd8ff4 Mon Sep 17 00:00:00 2001 From: "Gordon Lam (SH)" Date: Thu, 9 Apr 2026 09:58:29 +0800 Subject: [PATCH 2/2] Address PR review: DPI-aware icon sizing and remove unused field - 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> --- src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp b/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp index d9467ef78f..61eecd9511 100644 --- a/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp +++ b/src/dxaml/xcp/dxaml/lib/DesktopWindowImpl.cpp @@ -166,8 +166,8 @@ void DesktopWindowImpl::OnCreate() noexcept // by project template), EnumResourceNamesW always finds the first icon group // regardless of its ID. // LR_SHARED lets the system manage the icon lifetime — no DestroyIcon needed. - struct IconSearchContext { HMODULE module; LPCWSTR resourceId; }; - IconSearchContext ctx { hExeModule, nullptr }; + struct IconSearchContext { LPCWSTR resourceId; }; + IconSearchContext ctx { nullptr }; ::EnumResourceNamesW(hExeModule, RT_GROUP_ICON, [](HMODULE, LPCWSTR, LPWSTR lpName, LONG_PTR lParam) -> BOOL @@ -180,14 +180,15 @@ void DesktopWindowImpl::OnCreate() noexcept if (ctx.resourceId) { + UINT dpi = ::GetDpiForWindow(m_hwnd.get()); HICON hIconLarge = static_cast(::LoadImageW( hExeModule, ctx.resourceId, IMAGE_ICON, - ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), + ::GetSystemMetricsForDpi(SM_CXICON, dpi), ::GetSystemMetricsForDpi(SM_CYICON, dpi), LR_DEFAULTCOLOR | LR_SHARED)); HICON hIconSmall = static_cast(::LoadImageW( hExeModule, ctx.resourceId, IMAGE_ICON, - ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), + ::GetSystemMetricsForDpi(SM_CXSMICON, dpi), ::GetSystemMetricsForDpi(SM_CYSMICON, dpi), LR_DEFAULTCOLOR | LR_SHARED)); if (hIconLarge)