From be262bf45cfd13b6251910207ea4958816baec27 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:05:17 +1100 Subject: [PATCH] Defer default icon loading until Window is shown (#20898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: verify ShowCore applies default icon when no custom icon is set Adds a test that verifies Window.Show() applies the default icon via SetIcon when no custom icon has been set. Currently fails because ShowCore has no default icon logic — the fallback only exists in the constructor binding where it eagerly loads the icon. Relates to #20478 * fix: defer default icon loading from constructor to ShowCore The default icon was eagerly loaded during Window construction via CreatePlatformImplBinding, even when a custom icon would be set or no icon was needed. This caused unnecessary I/O (assembly resource loading) on every first Window instantiation. Move the default icon fallback from the binding lambda to ShowCore, so LoadDefaultIcon only runs when the window is actually shown and no custom icon has been set. Fixes #20478 --- src/Avalonia.Controls/Window.cs | 12 +++++++++-- src/Avalonia.X11/X11Window.cs | 6 ++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 3 +++ .../WindowTests.cs | 20 +++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index d92a46a70a..db3ec6a077 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -248,7 +248,7 @@ namespace Avalonia.Controls this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, WindowResizeReason.Application)); CreatePlatformImplBinding(TitleProperty, title => PlatformImpl!.SetTitle(title)); - CreatePlatformImplBinding(IconProperty, icon => PlatformImpl!.SetIcon((icon ?? s_defaultIcon.Value)?.PlatformImpl)); + CreatePlatformImplBinding(IconProperty, SetEffectiveIcon); CreatePlatformImplBinding(CanResizeProperty, canResize => PlatformImpl!.CanResize(canResize)); CreatePlatformImplBinding(CanMinimizeProperty, canMinimize => PlatformImpl!.SetCanMinimize(canMinimize)); CreatePlatformImplBinding(CanMaximizeProperty, canMaximize => PlatformImpl!.SetCanMaximize(canMaximize)); @@ -892,6 +892,8 @@ namespace Avalonia.Controls _shown = true; IsVisible = true; + SetEffectiveIcon(Icon); + // If window position was not set before then platform may provide incorrect scaling at this time, // but we need it for proper calculation of position and in some cases size (size to content) SetExpectedScaling(owner); @@ -1378,7 +1380,7 @@ namespace Avalonia.Controls private static WindowIcon? LoadDefaultIcon() { - // Use AvaloniaLocator instead of static AssetLoader, so it won't fail on Unit Tests without any asset loader. + // Use AvaloniaLocator instead of static AssetLoader, so it won't fail on Unit Tests without any asset loader. if (AvaloniaLocator.Current.GetService() is { } assetLoader && Assembly.GetEntryAssembly()?.GetName()?.Name is { } assemblyName && Uri.TryCreate($"avares://{assemblyName}/!__AvaloniaDefaultWindowIcon", UriKind.Absolute, out var path) @@ -1390,6 +1392,12 @@ namespace Avalonia.Controls return null; } + private void SetEffectiveIcon(WindowIcon? icon) + { + icon ??= _shown ? s_defaultIcon.Value : null; + PlatformImpl?.SetIcon(icon?.PlatformImpl); + } + private static bool CoerceCanMaximize(AvaloniaObject target, bool value) => value && target is not Window { CanResize: false }; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index a57e1986ac..14e0f0dea8 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -71,6 +71,7 @@ namespace Avalonia.X11 private bool _useCompositorDrivenRenderWindowResize = false; private bool _usePositioningFlags = false; private X11WindowMode _mode; + private IWindowIconImpl? _iconImpl; private enum XSyncState { @@ -1530,6 +1531,11 @@ namespace Avalonia.X11 public void SetIcon(IWindowIconImpl? icon) { + if (ReferenceEquals(_iconImpl, icon)) + return; + + _iconImpl = icon; + if (icon != null) { var data = ((X11IconData)icon).Data; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cc2e7211f1..db42fca251 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -809,6 +809,9 @@ namespace Avalonia.Win32 public void SetIcon(IWindowIconImpl? icon) { + if (ReferenceEquals(_iconImpl, icon)) + return; + _iconImpl = (IconImpl?)icon; ClearIconCache(); RefreshIcon(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 7ab69c8d86..59a84462ef 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -1188,6 +1188,26 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Show_Should_Apply_Default_Icon_When_No_Custom_Icon_Is_Set() + { + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object); + + using (UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: windowingPlatform))) + { + var target = new Window(); + + // Clear any SetIcon calls from construction. + windowImpl.Invocations.Clear(); + + target.Show(); + + // ShowCore should apply the default icon when no custom icon was set. + windowImpl.Verify(x => x.SetIcon(It.IsAny()), Times.AtLeastOnce()); + } + } + private class TopmostWindow : Window { static TopmostWindow()