diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5cf1a5b71a..2dd5c7bc03 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -65,9 +65,9 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly DirectProperty ActualTransparencyLevelProperty = - AvaloniaProperty.RegisterDirect(nameof(ActualTransparencyLevel), - o => o.ActualTransparencyLevel, - unsetValue: WindowTransparencyLevel.None); + AvaloniaProperty.RegisterDirect(nameof(ActualTransparencyLevel), + o => o.ActualTransparencyLevel, + unsetValue: WindowTransparencyLevel.None); /// /// Defines the property. @@ -78,7 +78,7 @@ namespace Avalonia.Controls /// public static readonly StyledProperty ActualThemeVariantProperty = ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); - + /// public static readonly StyledProperty RequestedThemeVariantProperty = ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); @@ -102,7 +102,7 @@ namespace Avalonia.Controls /// /// Defines the event. /// - public static readonly RoutedEvent BackRequestedEvent = + public static readonly RoutedEvent BackRequestedEvent = RoutedEvent.Register(nameof(BackRequested), RoutingStrategies.Bubble); private static readonly WeakEvent @@ -131,7 +131,7 @@ namespace Avalonia.Controls private readonly PresentationSource _source; internal new PresentationSource PresentationSource => _source; internal IInputRoot InputRoot => _source; - + /// /// Initializes static members of the class. /// @@ -199,9 +199,9 @@ namespace Avalonia.Controls _scaling = ValidateScaling(impl.RenderScaling); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; - - + + _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); @@ -211,7 +211,7 @@ namespace Avalonia.Controls _applicationThemeHost = TryGetService(dependencyResolver); - + impl.Closed = HandleClosed; impl.Paint = HandlePaint; @@ -257,9 +257,7 @@ namespace Avalonia.Controls impl.LostFocus += PlatformImpl_LostFocus; - - - if(impl.TryGetFeature() is {} systemNavigationManager) + if (impl.TryGetFeature() is { } systemNavigationManager) { systemNavigationManager.BackRequested += (_, e) => { @@ -286,14 +284,14 @@ namespace Avalonia.Controls KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, Key = rawKeyEventArgs.Key, PhysicalKey = rawKeyEventArgs.PhysicalKey, - KeyDeviceType= rawKeyEventArgs.KeyDeviceType, + KeyDeviceType = rawKeyEventArgs.KeyDeviceType, KeySymbol = rawKeyEventArgs.KeySymbol }; - backRequested = keymap.Any( key => key.Matches(keyEvent)); + backRequested = keymap.Any(key => key.Matches(keyEvent)); } } - else if(e is RawPointerEventArgs pointerEventArgs) + else if (e is RawPointerEventArgs pointerEventArgs) { backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down; } @@ -321,7 +319,7 @@ namespace Avalonia.Controls /// Gets or sets a method called when the TopLevel's scaling changes. /// public event EventHandler? ScalingChanged; - + /// /// Gets or sets the client size of the window. /// @@ -359,7 +357,7 @@ namespace Avalonia.Controls { get => _actualTransparencyLevel; private set => SetAndRaise(ActualTransparencyLevelProperty, ref _actualTransparencyLevel, value); - } + } /// /// Gets or sets the that transparency will blend with when transparency is not supported. @@ -439,7 +437,7 @@ namespace Avalonia.Controls public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics; internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this); - + /// /// Gets the access key handler for the window. /// @@ -448,7 +446,7 @@ namespace Avalonia.Controls /// /// Gets or sets the keyboard navigation handler for the window. /// - + /// /// Helper for setting the color of the platform's system bars. /// @@ -573,7 +571,7 @@ namespace Avalonia.Controls return Disposable.Create(() => { }); } } - + /// /// Enqueues a callback to be called on the next animation tick /// @@ -582,7 +580,7 @@ namespace Avalonia.Controls Dispatcher.UIThread.VerifyAccess(); MediaContext.Instance.RequestAnimationFrame(action); } - + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -602,7 +600,7 @@ namespace Avalonia.Controls private void InvalidateChildInsetsPadding() { if (Content is Control child - && InsetsManager is {} insetsManager) + && InsetsManager is { } insetsManager) { insetsManager.SafeAreaChanged -= InsetsManagerOnSafeAreaChanged; _insetsPaddings?.Dispose(); @@ -643,12 +641,12 @@ namespace Avalonia.Controls { _source.Dispose(); StopRendering(); - + Debug.Assert(PlatformImpl != null); // The PlatformImpl is completely invalid at this point PlatformImpl = null; _scaling = 1.0; - + if (_globalStyles is object) { _globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded; @@ -658,14 +656,14 @@ namespace Avalonia.Controls { _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } - + _backGestureSubscription?.Dispose(); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); _source.RootVisual = null!; - + OnClosed(EventArgs.Empty); LayoutManager.Dispose(); @@ -781,8 +779,8 @@ namespace Avalonia.Controls private static void OnToolTipServiceEnabledChanged(AvaloniaPropertyChangedEventArgs args) { if (args.GetNewValue() - && args.Priority != BindingPriority.Inherited - && args.Sender is Visual visual + && args.Priority != BindingPriority.Inherited + && args.Sender is Visual visual && GetTopLevel(visual) is { } topLevel) { topLevel.UpdateToolTip(visual.Bounds.Translate((Vector)visual.TranslatePoint(default, topLevel)!)); @@ -803,11 +801,7 @@ namespace Avalonia.Controls void PlatformImpl_LostFocus() { - var focused = (Visual?)FocusManager?.GetFocusedElement(); - if (focused == null) - return; - while (focused.VisualParent != null) - focused = focused.VisualParent; + var focused = TopLevel.GetTopLevel((Visual?)FocusManager?.GetFocusedElement()); if (focused == this) KeyboardDevice.Instance?.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None, false); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 02a880d166..470030ef0b 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -253,6 +253,34 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void TopLevel_Should_Unfocus_When_Impl_Focus_Is_Lost() + { + using (UnitTestApplication.Start(TestServices.RealFocus)) + { + var impl = CreateMockTopLevelImpl(true); + var content = new TextBox() + { + Focusable = true + }; + var target = new TestTopLevel(impl.Object) + { + Template = CreateTemplate(), + Focusable = true, + Content = content + }; + + target.LayoutManager.ExecuteInitialLayoutPass(); + + content.Focus(); + Assert.True(content.IsFocused); + + impl.Object.LostFocus?.Invoke(); + + Assert.False(content.IsFocused); + } + } + [Fact] public void Reacts_To_Changes_In_Global_Styles() {