diff --git a/src/Avalonia.Controls/TopLevelHost.Decorations.cs b/src/Avalonia.Controls/TopLevelHost.Decorations.cs
index 11f3dc3ada..a13cb4ecab 100644
--- a/src/Avalonia.Controls/TopLevelHost.Decorations.cs
+++ b/src/Avalonia.Controls/TopLevelHost.Decorations.cs
@@ -6,6 +6,7 @@ using Avalonia.Automation.Peers;
using Avalonia.Controls.Chrome;
using Avalonia.Input;
using Avalonia.Reactive;
+using Avalonia.Styling;
namespace Avalonia.Controls;
@@ -53,7 +54,7 @@ internal partial class TopLevelHost
/// When non-null (including ), the decoration
/// infrastructure is kept alive and parts/fullscreen state are updated.
///
- internal void UpdateDrawnDecorations(DrawnWindowDecorationParts? parts, WindowState windowState)
+ internal void UpdateDrawnDecorations(DrawnWindowDecorationParts? parts, WindowState windowState, ControlTheme? theme)
{
if (parts == null)
{
@@ -68,6 +69,14 @@ internal partial class TopLevelHost
// Layers persist across part changes; pseudo-classes driven by EnabledParts
// control visibility of individual decoration elements in the theme.
_decorations.EnabledParts = enabledParts;
+
+ var oldTheme = _decorations.Theme;
+ if (oldTheme != theme)
+ {
+ _decorations.Theme = theme;
+ _decorations.ApplyStyling();
+ }
+
if (_resizeGrips != null)
_resizeGrips.IsVisible = enabledParts.HasFlag(DrawnWindowDecorationParts.ResizeGrips);
}
@@ -75,6 +84,7 @@ internal partial class TopLevelHost
{
_decorations = new WindowDrawnDecorations();
_decorations.EnabledParts = enabledParts;
+ _decorations.Theme = theme;
// Set up logical parenting
LogicalChildren.Add(_decorations);
@@ -161,6 +171,7 @@ internal partial class TopLevelHost
if (_decorations == null)
return;
+ _decorations.ApplyStyling();
_decorations.ApplyTemplate();
var content = _decorations.Content;
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 0797f39733..37b75b1b27 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -140,6 +140,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty WindowDecorationsProperty =
AvaloniaProperty.Register(nameof(WindowDecorations), WindowDecorations.Full);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty WindowDecorationsThemeProperty =
+ AvaloniaProperty.Register(nameof(WindowDecorationsTheme));
+
///
/// Defines the property.
///
@@ -376,6 +382,15 @@ namespace Avalonia.Controls
set => SetValue(WindowDecorationsProperty, value);
}
+ ///
+ /// Gets or sets the theme used to render the window decorations when they are not drawn by the system.
+ ///
+ public ControlTheme? WindowDecorationsTheme
+ {
+ get => GetValue(WindowDecorationsThemeProperty);
+ set => SetValue(WindowDecorationsThemeProperty, value);
+ }
+
[Obsolete("Use WindowDecorations instead.")]
public WindowDecorations SystemDecorations
{
@@ -704,7 +719,7 @@ namespace Avalonia.Controls
// Detect forced mode: platform needs managed decorations but app hasn't opted in
_isForcedDecorationMode = parts != null && !IsExtendedIntoWindowDecorations;
- TopLevelHost.UpdateDrawnDecorations(parts, WindowState);
+ TopLevelHost.UpdateDrawnDecorations(parts, WindowState, WindowDecorationsTheme);
if (parts != null)
{
@@ -739,7 +754,7 @@ namespace Avalonia.Controls
if (TopLevelHost.Decorations == null)
return;
- TopLevelHost.UpdateDrawnDecorations(ComputeDecorationParts(), WindowState);
+ TopLevelHost.UpdateDrawnDecorations(ComputeDecorationParts(), WindowState, WindowDecorationsTheme);
}
private Chrome.DrawnWindowDecorationParts? ComputeDecorationParts()
@@ -1493,6 +1508,7 @@ namespace Avalonia.Controls
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
+
if (change.Property == WindowDecorationsProperty)
{
var (_, typedNewValue) = change.GetOldAndNewValue();
@@ -1500,6 +1516,11 @@ namespace Avalonia.Controls
PlatformImpl?.SetWindowDecorations(typedNewValue);
}
+ else if (change.Property == WindowDecorationsThemeProperty)
+ {
+ UpdateDrawnDecorations();
+ }
+
else if (change.Property == OwnerProperty)
{
var oldParent = change.OldValue as Window;
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 3b2386b1d5..646dad30ec 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -2,9 +2,13 @@ using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
+using Avalonia.Controls.Chrome;
using Avalonia.Controls.Platform;
+using Avalonia.Controls.Templates;
+using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Platform;
+using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
@@ -1590,6 +1594,54 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void WindowDecorationsTheme_Should_Apply_To_Decorations()
+ {
+ using var app = UnitTestApplication.Start(TestServices.StyledWindow);
+
+ var windowImpl = MockWindowingPlatform.CreateWindowMock();
+ windowImpl.Setup(x => x.NeedsManagedDecorations).Returns(true);
+ windowImpl.Setup(x => x.RequestedDrawnDecorations).Returns(
+ PlatformRequestedDrawnDecoration.TitleBar | PlatformRequestedDrawnDecoration.Border);
+
+ var window = new Window(windowImpl.Object);
+
+ var (theme1, content1) = CreateTheme();
+ window.WindowDecorationsTheme = theme1;
+ window.Show();
+
+ var decorations = window.TopLevelHost.Decorations;
+ Assert.NotNull(decorations);
+ Assert.Same(theme1, decorations.Theme);
+ Assert.Same(content1, decorations.Content);
+
+ var (theme2, content2) = CreateTheme();
+ window.WindowDecorationsTheme = theme2;
+
+ Assert.Same(theme2, decorations.Theme);
+ Assert.Same(content2, decorations.Content);
+
+ static (ControlTheme theme, WindowDrawnDecorationsContent content) CreateTheme()
+ {
+ var content = new WindowDrawnDecorationsContent();
+
+ var template = new WindowDrawnDecorationsTemplate
+ {
+ Content = (IServiceProvider? _) => new TemplateResult(content, new NameScope())
+ };
+
+ var theme = new ControlTheme(typeof(WindowDrawnDecorations))
+ {
+ Setters =
+ {
+ new Setter(WindowDrawnDecorations.TemplateProperty, template)
+ }
+ };
+
+ return (theme, content);
+ }
+ }
+
private class TopmostWindow : Window
{
static TopmostWindow()