Browse Source

Defer default icon loading until Window is shown (#20898)

* 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
pull/17825/merge
Nathan Nguyen 1 week ago
committed by GitHub
parent
commit
be262bf45c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      src/Avalonia.Controls/Window.cs
  2. 6
      src/Avalonia.X11/X11Window.cs
  3. 3
      src/Windows/Avalonia.Win32/WindowImpl.cs
  4. 20
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

12
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<IAssetLoader>() 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 };
}

6
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;

3
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();

20
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<IWindowIconImpl?>()), Times.AtLeastOnce());
}
}
private class TopmostWindow : Window
{
static TopmostWindow()

Loading…
Cancel
Save