fix(Window): 修复全屏标题栏 Logo 重复挂载崩溃

This commit is contained in:
chinboy
2026-09-11 11:37:37 +08:00
parent 86adc66ee3
commit 95cf4bf423
8 changed files with 210 additions and 17 deletions
@@ -35,8 +35,8 @@
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding $parent[atom:Window].EffectiveLogo}"
ContentTemplate="{Binding $parent[atom:Window].EffectiveLogoTemplate}"
Content="{Binding $parent[atom:Window].EffectiveFullscreenLogo}"
ContentTemplate="{Binding $parent[atom:Window].EffectiveFullscreenLogoTemplate}"
IsVisible="{Binding $parent[atom:Window].IsEffectiveFullscreenLogoVisible}"/>
<TextBlock Name="FullscreenTitleText"
HorizontalAlignment="Left"
@@ -102,8 +102,8 @@
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{Binding $parent[atom:Window].EffectiveLogo}"
ContentTemplate="{Binding $parent[atom:Window].EffectiveLogoTemplate}"
Content="{Binding $parent[atom:Window].EffectiveFullscreenLogo}"
ContentTemplate="{Binding $parent[atom:Window].EffectiveFullscreenLogoTemplate}"
IsVisible="{Binding $parent[atom:Window].IsEffectiveFullscreenLogoVisible}"/>
<TextBlock Name="FullscreenTitleText"
HorizontalAlignment="Left"
@@ -180,7 +180,7 @@
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="^:fullscreen /template/ Panel#PART_TitleBarPresenter">
<Style Selector="^:fullscreen /template/ ContentPresenter#PART_TitleBarPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:fullscreen /template/ ContentPresenter#PART_TitleBar">
+51 -2
View File
@@ -358,6 +358,16 @@ public partial class Window : AvaloniaWindow,
nameof(EffectiveLogoTemplate),
o => o.EffectiveLogoTemplate);
internal static readonly DirectProperty<Window, object?> EffectiveFullscreenLogoProperty =
AvaloniaProperty.RegisterDirect<Window, object?>(
nameof(EffectiveFullscreenLogo),
o => o.EffectiveFullscreenLogo);
internal static readonly DirectProperty<Window, IDataTemplate?> EffectiveFullscreenLogoTemplateProperty =
AvaloniaProperty.RegisterDirect<Window, IDataTemplate?>(
nameof(EffectiveFullscreenLogoTemplate),
o => o.EffectiveFullscreenLogoTemplate);
internal static readonly DirectProperty<Window, bool> IsEffectiveFullscreenTitleVisibleProperty =
AvaloniaProperty.RegisterDirect<Window, bool>(
nameof(IsEffectiveFullscreenTitleVisible),
@@ -480,6 +490,28 @@ public partial class Window : AvaloniaWindow,
private set => SetAndRaise(EffectiveLogoTemplateProperty, ref _effectiveLogoTemplate, value);
}
private object? _effectiveFullscreenLogo;
internal object? EffectiveFullscreenLogo
{
get => _effectiveFullscreenLogo;
private set => SetAndRaise(
EffectiveFullscreenLogoProperty,
ref _effectiveFullscreenLogo,
value);
}
private IDataTemplate? _effectiveFullscreenLogoTemplate;
internal IDataTemplate? EffectiveFullscreenLogoTemplate
{
get => _effectiveFullscreenLogoTemplate;
private set => SetAndRaise(
EffectiveFullscreenLogoTemplateProperty,
ref _effectiveFullscreenLogoTemplate,
value);
}
private bool _isEffectiveFullscreenTitleVisible;
internal bool IsEffectiveFullscreenTitleVisible
@@ -1698,7 +1730,8 @@ public partial class Window : AvaloniaWindow,
change.Property == EffectiveLogoTemplateProperty ||
change.Property == LogoVisibilityProperty ||
change.Property == TitleProperty ||
change.Property == IsTitleVisibleProperty)
change.Property == IsTitleVisibleProperty ||
change.Property == WindowStateProperty)
{
UpdateEffectiveFullscreenLogoVisible();
}
@@ -1712,12 +1745,28 @@ public partial class Window : AvaloniaWindow,
private void UpdateEffectiveFullscreenLogoVisible()
{
var hasLogo = EffectiveLogo is not null || EffectiveLogoTemplate is not null;
IsEffectiveFullscreenLogoVisible = LogoVisibility switch
var isLogoVisibleByMode = LogoVisibility switch
{
WindowTitleBarLogoVisibility.Always => hasLogo,
WindowTitleBarLogoVisibility.Never => false,
_ => hasLogo && HasTitleContent(Title) && IsEffectiveFullscreenTitleVisible
};
IsEffectiveFullscreenLogoVisible = WindowState == WindowState.FullScreen && isLogoVisibleByMode;
UpdateEffectiveFullscreenLogo();
}
private void UpdateEffectiveFullscreenLogo()
{
if (IsEffectiveFullscreenLogoVisible)
{
EffectiveFullscreenLogo = EffectiveLogo;
EffectiveFullscreenLogoTemplate = EffectiveLogoTemplate;
}
else
{
EffectiveFullscreenLogo = null;
EffectiveFullscreenLogoTemplate = null;
}
}
private static bool HasTitleContent(object? title)
@@ -33,8 +33,8 @@
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsHitTestVisible="False"
Content="{TemplateBinding EffectiveLogo}"
ContentTemplate="{TemplateBinding EffectiveLogoTemplate}"
Content="{TemplateBinding EffectiveLogoPresenterContent}"
ContentTemplate="{TemplateBinding EffectiveLogoPresenterContentTemplate}"
IsVisible="{TemplateBinding IsEffectiveLogoVisible}" />
<ContentPresenter Name="PART_LeftAddOn"
DockPanel.Dock="Left"
@@ -120,8 +120,8 @@
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsHitTestVisible="False"
Content="{TemplateBinding EffectiveLogo}"
ContentTemplate="{TemplateBinding EffectiveLogoTemplate}"
Content="{TemplateBinding EffectiveLogoPresenterContent}"
ContentTemplate="{TemplateBinding EffectiveLogoPresenterContentTemplate}"
IsVisible="{TemplateBinding IsEffectiveLogoVisible}" />
<ContentPresenter Name="PART_LeftAddOn"
DockPanel.Dock="Left"
@@ -219,8 +219,8 @@
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Content="{TemplateBinding EffectiveLogo}"
ContentTemplate="{TemplateBinding EffectiveLogoTemplate}"
Content="{TemplateBinding EffectiveLogoPresenterContent}"
ContentTemplate="{TemplateBinding EffectiveLogoPresenterContentTemplate}"
IsVisible="{TemplateBinding IsEffectiveLogoVisible}" />
<ContentPresenter Name="PART_ContentPresenter"
HorizontalAlignment="Left"
@@ -169,6 +169,16 @@ public class WindowTitleBar : TemplatedControl,
nameof(EffectiveLogoTemplate),
o => o.EffectiveLogoTemplate);
internal static readonly DirectProperty<WindowTitleBar, object?> EffectiveLogoPresenterContentProperty =
AvaloniaProperty.RegisterDirect<WindowTitleBar, object?>(
nameof(EffectiveLogoPresenterContent),
o => o.EffectiveLogoPresenterContent);
internal static readonly DirectProperty<WindowTitleBar, IDataTemplate?> EffectiveLogoPresenterContentTemplateProperty =
AvaloniaProperty.RegisterDirect<WindowTitleBar, IDataTemplate?>(
nameof(EffectiveLogoPresenterContentTemplate),
o => o.EffectiveLogoPresenterContentTemplate);
internal static readonly DirectProperty<WindowTitleBar, bool> IsEffectiveLogoVisibleProperty =
AvaloniaProperty.RegisterDirect<WindowTitleBar, bool>(
nameof(IsEffectiveLogoVisible),
@@ -234,6 +244,28 @@ public class WindowTitleBar : TemplatedControl,
private set => SetAndRaise(EffectiveLogoTemplateProperty, ref _effectiveLogoTemplate, value);
}
private object? _effectiveLogoPresenterContent;
internal object? EffectiveLogoPresenterContent
{
get => _effectiveLogoPresenterContent;
private set => SetAndRaise(
EffectiveLogoPresenterContentProperty,
ref _effectiveLogoPresenterContent,
value);
}
private IDataTemplate? _effectiveLogoPresenterContentTemplate;
internal IDataTemplate? EffectiveLogoPresenterContentTemplate
{
get => _effectiveLogoPresenterContentTemplate;
private set => SetAndRaise(
EffectiveLogoPresenterContentTemplateProperty,
ref _effectiveLogoPresenterContentTemplate,
value);
}
private bool _isEffectiveLogoVisible;
internal bool IsEffectiveLogoVisible
@@ -486,12 +518,28 @@ public class WindowTitleBar : TemplatedControl,
private void UpdateEffectiveLogoVisible()
{
var hasLogo = EffectiveLogo is not null || EffectiveLogoTemplate is not null;
IsEffectiveLogoVisible = LogoVisibility switch
var isLogoVisibleByMode = LogoVisibility switch
{
WindowTitleBarLogoVisibility.Always => hasLogo,
WindowTitleBarLogoVisibility.Never => false,
_ => hasLogo && ShouldShowLogoInAutoMode()
};
IsEffectiveLogoVisible = HostWindowState != WindowState.FullScreen && isLogoVisibleByMode;
UpdateEffectiveLogoPresenterContent();
}
private void UpdateEffectiveLogoPresenterContent()
{
if (IsEffectiveLogoVisible)
{
EffectiveLogoPresenterContent = EffectiveLogo;
EffectiveLogoPresenterContentTemplate = EffectiveLogoTemplate;
}
else
{
EffectiveLogoPresenterContent = null;
EffectiveLogoPresenterContentTemplate = null;
}
}
private void UpdateEffectiveTitleVisible()
@@ -657,10 +657,19 @@ public class WindowResizeArtifactTests
{
var document = XDocument.Load(GetRepoFile(
"src/AtomUI.Desktop.Controls/Window/Themes/WindowDrawnDecorationsTheme.axaml"));
XNamespace av = "https://github.com/avaloniaui";
AssertTitleBarVisibilityContract(document, "PART_TitleBar");
AssertTitleBarVisibilityContract(document, "PART_TitleBarPresenter");
AssertTitleBarVisibilityContract(document, "WindowTitleBarShadowBackground");
document.Descendants(av + "Style")
.ShouldContain(style =>
(string?)style.Attribute("Selector") ==
"^:fullscreen /template/ ContentPresenter#PART_TitleBarPresenter");
document.Descendants(av + "Style")
.ShouldNotContain(style =>
(string?)style.Attribute("Selector") ==
"^:fullscreen /template/ Panel#PART_TitleBarPresenter");
}
[Fact]
@@ -1015,6 +1024,10 @@ public class WindowResizeArtifactTests
var logoPresenter = title.Descendants().Single(element =>
(string?)element.Attribute("Name") == "FullscreenLogoPresenter");
logoPresenter.Attribute("DockPanel.Dock")?.Value.ShouldBe("Left");
logoPresenter.Attribute("Content")?.Value.ShouldBe(
"{Binding $parent[atom:Window].EffectiveFullscreenLogo}");
logoPresenter.Attribute("ContentTemplate")?.Value.ShouldBe(
"{Binding $parent[atom:Window].EffectiveFullscreenLogoTemplate}");
var titleText = title.Descendants().Single(element =>
(string?)element.Attribute("Name") == "FullscreenTitleText");
titleText.Attribute("TextWrapping")?.Value.ShouldBe("NoWrap");
@@ -274,7 +274,7 @@ public class WindowTitleBarEffectiveLogoTests
}
[Fact]
public void Runtime_Icon_Changes_Update_Fullscreen_Logo_Visibility()
public void Fullscreen_Logo_Visibility_Is_Inactive_Until_Window_Enters_Fullscreen()
{
var window = new AtomUI.Desktop.Controls.Window
{
@@ -283,12 +283,55 @@ public class WindowTitleBarEffectiveLogoTests
};
GetIsEffectiveFullscreenLogoVisible(window).ShouldBeFalse();
GetEffectiveFullscreenLogo(window).ShouldBeNull();
GetEffectiveFullscreenLogoTemplate(window).ShouldBeNull();
window.Icon = CreateIcon();
GetIsEffectiveFullscreenLogoVisible(window).ShouldBeFalse();
GetEffectiveFullscreenLogo(window).ShouldBeNull();
GetEffectiveFullscreenLogoTemplate(window).ShouldBeNull();
window.WindowState = WindowState.FullScreen;
GetIsEffectiveFullscreenLogoVisible(window).ShouldBeTrue();
GetEffectiveFullscreenLogo(window).ShouldBe(window.Icon);
GetEffectiveFullscreenLogoTemplate(window).ShouldNotBeNull();
window.WindowState = WindowState.Normal;
GetIsEffectiveFullscreenLogoVisible(window).ShouldBeFalse();
GetEffectiveFullscreenLogo(window).ShouldBeNull();
GetEffectiveFullscreenLogoTemplate(window).ShouldBeNull();
window.Icon = null;
GetIsEffectiveFullscreenLogoVisible(window).ShouldBeFalse();
GetEffectiveFullscreenLogo(window).ShouldBeNull();
GetEffectiveFullscreenLogoTemplate(window).ShouldBeNull();
}
[Fact]
public void Hosted_Title_Bar_Releases_Logo_Visibility_When_Host_Enters_Fullscreen()
{
var logo = new Border();
var titleBar = new WindowTitleBar
{
Title = "AtomUI",
Logo = logo,
LogoVisibility = WindowTitleBarLogoVisibility.Always
};
titleBar.SetValue(WindowTitleBar.OsTypeProperty, OsType.Windows);
GetIsEffectiveLogoVisible(titleBar).ShouldBeTrue();
GetEffectiveLogoPresenterContent(titleBar).ShouldBe(logo);
GetEffectiveLogoPresenterContentTemplate(titleBar).ShouldBeNull();
titleBar.HostWindowState = WindowState.FullScreen;
GetIsEffectiveLogoVisible(titleBar).ShouldBeFalse();
GetEffectiveLogoPresenterContent(titleBar).ShouldBeNull();
GetEffectiveLogoPresenterContentTemplate(titleBar).ShouldBeNull();
titleBar.HostWindowState = WindowState.Normal;
GetIsEffectiveLogoVisible(titleBar).ShouldBeTrue();
GetEffectiveLogoPresenterContent(titleBar).ShouldBe(logo);
GetEffectiveLogoPresenterContentTemplate(titleBar).ShouldBeNull();
}
[Fact]
@@ -432,6 +475,20 @@ public class WindowTitleBarEffectiveLogoTests
.GetValue(window);
}
private static object? GetEffectiveFullscreenLogo(AtomUI.Desktop.Controls.Window window)
{
return typeof(AtomUI.Desktop.Controls.Window)
.GetProperty("EffectiveFullscreenLogo", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(window);
}
private static object? GetEffectiveFullscreenLogoTemplate(AtomUI.Desktop.Controls.Window window)
{
return typeof(AtomUI.Desktop.Controls.Window)
.GetProperty("EffectiveFullscreenLogoTemplate", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(window);
}
private static WindowTitleBar? GetTitleBar(AtomUI.Desktop.Controls.Window window)
{
return typeof(AtomUI.Desktop.Controls.Window)
@@ -446,6 +503,20 @@ public class WindowTitleBarEffectiveLogoTests
.GetValue(titleBar)!;
}
private static object? GetEffectiveLogoPresenterContent(WindowTitleBar titleBar)
{
return typeof(WindowTitleBar)
.GetProperty("EffectiveLogoPresenterContent", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(titleBar);
}
private static object? GetEffectiveLogoPresenterContentTemplate(WindowTitleBar titleBar)
{
return typeof(WindowTitleBar)
.GetProperty("EffectiveLogoPresenterContentTemplate", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(titleBar);
}
private static bool GetIsEffectiveFullscreenLogoVisible(AtomUI.Desktop.Controls.Window window)
{
return (bool)typeof(AtomUI.Desktop.Controls.Window)
@@ -128,6 +128,7 @@ public class WindowTitleBarLogoVisibilityTests
source.ShouldContain("WindowTitleBarLogoVisibility.Always => hasLogo");
source.ShouldContain("WindowTitleBarLogoVisibility.Never => false");
source.ShouldContain("_ => hasLogo && ShouldShowLogoInAutoMode()");
source.ShouldContain("HostWindowState != WindowState.FullScreen && isLogoVisibleByMode");
source.ShouldContain("HostWindowState != WindowState.FullScreen");
source.ShouldContain("OsType == OsType.macOS");
source.ShouldContain("string text => !string.IsNullOrWhiteSpace(text)");
@@ -198,8 +199,10 @@ public class WindowTitleBarLogoVisibilityTests
source.ShouldContain(
"titleBar.Bind(WindowTitleBar.LogoVisibilityProperty, this.GetObservable(LogoVisibilityProperty))");
source.ShouldContain("IsEffectiveFullscreenLogoVisibleProperty");
source.ShouldContain("EffectiveFullscreenLogoProperty");
source.ShouldContain("EffectiveFullscreenLogoTemplateProperty");
source.ShouldContain("UpdateEffectiveFullscreenLogoVisible()");
source.ShouldContain("_ => hasLogo && HasTitleContent(Title) && IsEffectiveFullscreenTitleVisible");
source.ShouldContain("WindowState == WindowState.FullScreen && isLogoVisibleByMode");
// Effective Logo 模式:回退解析只进 EffectiveLogo/EffectiveLogoTemplate 渲染层,
// 框架不再向 Logo/LogoTemplate/Icon 写入任何默认值
source.ShouldContain("UpdateEffectiveLogo()");
@@ -219,6 +222,12 @@ public class WindowTitleBarLogoVisibilityTests
Regex.Matches(source, "IsVisible=\"\\{TemplateBinding IsEffectiveLogoVisible\\}\"")
.Count
.ShouldBe(3);
Regex.Matches(source, "Content=\"\\{TemplateBinding EffectiveLogoPresenterContent\\}\"")
.Count
.ShouldBe(3);
Regex.Matches(source, "ContentTemplate=\"\\{TemplateBinding EffectiveLogoPresenterContentTemplate\\}\"")
.Count
.ShouldBe(3);
}
[Fact]
@@ -236,8 +245,11 @@ public class WindowTitleBarLogoVisibilityTests
foreach (var source in new[] { fullscreenPopoverSource, drawnDecorationsSource })
{
source.ShouldContain("Content=\"{Binding $parent[atom:Window].EffectiveLogo}\"");
source.ShouldContain("Content=\"{Binding $parent[atom:Window].EffectiveFullscreenLogo}\"");
source.ShouldContain(
"ContentTemplate=\"{Binding $parent[atom:Window].EffectiveFullscreenLogoTemplate}\"");
source.ShouldNotContain("Content=\"{Binding $parent[atom:Window].EffectiveLogo}\"");
source.ShouldNotContain(
"ContentTemplate=\"{Binding $parent[atom:Window].EffectiveLogoTemplate}\"");
}
}