Browse Source

Fix default values of window properties not being sent to `IWindowImpl` (#12656)

* Added failing test for Topmost platform configuration

* Added CreatePlatformImplBinding system to transfer data to TopLevel platform implementations
Tom Edwards 2 years ago
parent
commit
745b579e8f

+ 26 - 11
src/Avalonia.Controls/TopLevel.cs

@@ -121,6 +121,7 @@ namespace Avalonia.Controls
         private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
         private readonly IDisposable? _pointerOverPreProcessorSubscription;
         private readonly IDisposable? _backGestureSubscription;
+        private readonly Dictionary<AvaloniaProperty, Action> _platformImplBindings = new();
         private Size _clientSize;
         private Size? _frameSize;
         private WindowTransparencyLevel _actualTransparencyLevel;
@@ -212,6 +213,13 @@ namespace Avalonia.Controls
             impl.ScalingChanged = HandleScalingChanged;
             impl.TransparencyLevelChanged = HandleTransparencyLevelChanged;
 
+            CreatePlatformImplBinding(TransparencyLevelHintProperty, hint => PlatformImpl.SetTransparencyLevelHint(hint ?? Array.Empty<WindowTransparencyLevel>()));
+            CreatePlatformImplBinding(ActualThemeVariantProperty, variant =>
+            {
+                variant ??= ThemeVariant.Default;
+                PlatformImpl.SetFrameThemeVariant((PlatformThemeVariant?)variant ?? PlatformThemeVariant.Light);
+            });
+
             _keyboardNavigationHandler?.SetOwner(this);
             _accessKeyHandler?.SetOwner(this);
 
@@ -405,6 +413,22 @@ namespace Avalonia.Controls
         /// </returns>
         public IPlatformHandle? TryGetPlatformHandle() => (PlatformImpl as IWindowBaseImpl)?.Handle;
 
+        private protected void CreatePlatformImplBinding<TValue>(StyledProperty<TValue> property, Action<TValue> onValue)
+        {
+            _platformImplBindings.TryGetValue(property, out var actions);
+            _platformImplBindings[property] = actions + UpdatePlatformImpl;
+
+            UpdatePlatformImpl(); // execute the action now to handle the default value, which may have been overridden
+
+            void UpdatePlatformImpl()
+            {
+                if (PlatformImpl is not null)
+                {
+                    onValue(GetValue(property));
+                }
+            }
+        }
+
         /// <summary>
         /// Gets the renderer for the window.
         /// </summary>
@@ -561,18 +585,9 @@ namespace Avalonia.Controls
         {
             base.OnPropertyChanged(change);
 
-            if (change.Property == TransparencyLevelHintProperty)
-            {
-                if (PlatformImpl != null)
-                {
-                    PlatformImpl.SetTransparencyLevelHint(
-                        change.GetNewValue<IReadOnlyList<WindowTransparencyLevel>>() ?? Array.Empty<WindowTransparencyLevel>());
-                }
-            }
-            else if (change.Property == ActualThemeVariantProperty)
+            if (_platformImplBindings.TryGetValue(change.Property, out var bindingAction))
             {
-                var newThemeVariant = change.GetNewValue<ThemeVariant?>() ?? ThemeVariant.Default;
-                PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)newThemeVariant ?? PlatformThemeVariant.Light);
+                bindingAction();
             }
         }
         

+ 15 - 30
src/Avalonia.Controls/Window.cs

@@ -177,35 +177,6 @@ namespace Avalonia.Controls
         static Window()
         {
             BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
-            TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string?)e.NewValue));
-            ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue!));
-
-            IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon?)e.NewValue)?.PlatformImpl));
-
-            CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue!));
-
-            WindowStateProperty.Changed.AddClassHandler<Window>(
-                (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue!; });
-
-            ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler<Window>(
-                (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue!); });
-
-            ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler<Window>(
-                (w, e) =>
-                {
-                    if (w.PlatformImpl != null)
-                    {
-                        w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue!);
-                    }
-                });
-
-            ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler<Window>(
-                (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue!); });
-
-            MinWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue!, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
-            MinHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue!), new Size(w.MaxWidth, w.MaxHeight)));
-            MaxWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue!, w.MaxHeight)));
-            MaxHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue!)));
         }
 
         /// <summary>
@@ -230,7 +201,21 @@ namespace Avalonia.Controls
             impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged;
             this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, WindowResizeReason.Application));
 
-            PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
+            CreatePlatformImplBinding(TitleProperty, title => PlatformImpl!.SetTitle(title));
+            CreatePlatformImplBinding(IconProperty, icon => PlatformImpl!.SetIcon(icon?.PlatformImpl));
+            CreatePlatformImplBinding(CanResizeProperty, canResize => PlatformImpl!.CanResize(canResize));
+            CreatePlatformImplBinding(ShowInTaskbarProperty, show => PlatformImpl!.ShowTaskbarIcon(show));
+
+            CreatePlatformImplBinding(WindowStateProperty, state => PlatformImpl!.WindowState = state);
+            CreatePlatformImplBinding(ExtendClientAreaToDecorationsHintProperty, hint => PlatformImpl!.SetExtendClientAreaToDecorationsHint(hint));
+            CreatePlatformImplBinding(ExtendClientAreaChromeHintsProperty, hint => PlatformImpl!.SetExtendClientAreaChromeHints(hint));
+
+            CreatePlatformImplBinding(MinWidthProperty, UpdateMinMaxSize);
+            CreatePlatformImplBinding(MaxWidthProperty, UpdateMinMaxSize);
+            CreatePlatformImplBinding(MinHeightProperty, UpdateMinMaxSize);
+            CreatePlatformImplBinding(MaxHeightProperty, UpdateMinMaxSize);
+
+            void UpdateMinMaxSize(double _) => PlatformImpl!.SetMinMaxSize(new Size(MinWidth, MinHeight), new Size(MaxWidth, MaxHeight));
         }
 
         /// <summary>

+ 13 - 6
src/Avalonia.Controls/WindowBase.cs

@@ -36,20 +36,17 @@ namespace Avalonia.Controls
         private bool _isActive;
         private int _ignoreVisibilityChanges;
         private WindowBase? _owner;
-
-        protected bool IgnoreVisibilityChanges => _ignoreVisibilityChanges > 0; 
+        
+        protected bool IgnoreVisibilityChanges => _ignoreVisibilityChanges > 0;
 
         static WindowBase()
         {
             IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
-            IsVisibleProperty.Changed.AddClassHandler<WindowBase>((x,e) => x.IsVisibleChanged(e));
-
-            
-            TopmostProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue!));
         }
 
         public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)
         {
+            CreatePlatformImplBinding(TopmostProperty, topmost => PlatformImpl!.SetTopmost(topmost));
         }
 
         public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver? dependencyResolver) : base(impl, dependencyResolver)
@@ -192,6 +189,16 @@ namespace Avalonia.Controls
             }
         }
 
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            base.OnPropertyChanged(change);
+
+            if (change.Property == IsVisibleProperty)
+            {
+                IsVisibleChanged(change);
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnClosed(EventArgs e)
         {

+ 25 - 0
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -653,6 +653,23 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Window_Topmost_By_Default_Should_Configure_PlatformImpl_When_Constructed()
+        {
+            var windowImpl = MockWindowingPlatform.CreateWindowMock();
+
+            var windowServices = TestServices.StyledWindow.With(
+                windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object));
+
+            using (UnitTestApplication.Start(windowServices))
+            {
+                var window = new TopmostWindow();
+
+                Assert.True(window.Topmost);
+                windowImpl.Verify(i => i.SetTopmost(true));
+            }
+        }
+
         public class SizingTests
         {
             [Fact]
@@ -1099,5 +1116,13 @@ namespace Avalonia.Controls.UnitTests
                 return base.MeasureOverride(availableSize);
             }
         }
+
+        private class TopmostWindow : Window
+        {
+            static TopmostWindow()
+            {
+                TopmostProperty.OverrideDefaultValue<TopmostWindow>(true);
+            }
+        }
     }
 }