Przeglądaj źródła

Limit IFocusManager API, extend IInputElement API, remove visible static properties

Max Katz 2 lat temu
rodzic
commit
20064647dd
42 zmienionych plików z 192 dodań i 178 usunięć
  1. 3 1
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  2. 38 22
      src/Avalonia.Base/Input/FocusManager.cs
  3. 4 32
      src/Avalonia.Base/Input/IFocusManager.cs
  4. 3 0
      src/Avalonia.Base/Input/IFocusScope.cs
  5. 3 1
      src/Avalonia.Base/Input/IInputElement.cs
  6. 8 0
      src/Avalonia.Base/Input/IInputRoot.cs
  7. 1 7
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  8. 8 9
      src/Avalonia.Base/Input/InputElement.cs
  9. 3 1
      src/Avalonia.Base/Input/KeyboardDevice.cs
  10. 2 2
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  11. 2 0
      src/Avalonia.Base/Input/MouseDevice.cs
  12. 4 2
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  13. 3 2
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  14. 1 1
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
  15. 1 1
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  16. 1 1
      src/Avalonia.Controls/Calendar/Calendar.cs
  17. 1 2
      src/Avalonia.Controls/ComboBox.cs
  18. 2 2
      src/Avalonia.Controls/ContextMenu.cs
  19. 6 5
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  20. 3 3
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  21. 2 2
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  22. 6 5
      src/Avalonia.Controls/ItemsControl.cs
  23. 3 3
      src/Avalonia.Controls/Primitives/Popup.cs
  24. 4 1
      src/Avalonia.Controls/TopLevel.cs
  25. 1 2
      src/Avalonia.Controls/TreeView.cs
  26. 1 1
      src/Avalonia.Controls/TreeViewItem.cs
  27. 2 2
      src/Avalonia.Controls/WindowBase.cs
  28. 4 4
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  29. 1 1
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  30. 1 1
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  31. 1 1
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  32. 29 29
      tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
  33. 3 1
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  34. 5 5
      tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
  35. 4 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  36. 4 4
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  37. 2 2
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  38. 7 4
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  39. 4 4
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  40. 8 8
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  41. 2 2
      tests/Avalonia.LeakTests/ControlTests.cs
  42. 1 0
      tests/Avalonia.UnitTests/TestRoot.cs

+ 3 - 1
src/Avalonia.Base/Input/AccessKeyHandler.cs

@@ -141,9 +141,11 @@ namespace Avalonia.Input
 
                 if (MainMenu == null || !MainMenu.IsOpen)
                 {
+                    var focusManager = FocusManager.GetFocusManager(e.Source as IInputElement);
+                    
                     // TODO: Use FocusScopes to store the current element and restore it when context menu is closed.
                     // Save currently focused input element.
-                    _restoreFocusElement = FocusManager.Instance?.Current;
+                    _restoreFocusElement = focusManager?.GetFocusedElement();
 
                     // When Alt is pressed without a main menu, or with a closed main menu, show
                     // access key markers in the window (i.e. "_File").

+ 38 - 22
src/Avalonia.Base/Input/FocusManager.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.CompilerServices;
 using Avalonia.Interactivity;
+using Avalonia.Metadata;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Input
@@ -10,6 +11,7 @@ namespace Avalonia.Input
     /// <summary>
     /// Manages focus for the application.
     /// </summary>
+    [Unstable]
     public class FocusManager : IFocusManager
     {
         /// <summary>
@@ -21,7 +23,7 @@ namespace Avalonia.Input
         /// <summary>
         /// Initializes a new instance of the <see cref="FocusManager"/> class.
         /// </summary>
-        static FocusManager()
+        internal FocusManager()
         {
             InputElement.PointerPressedEvent.AddClassHandler(
                 typeof(IInputElement),
@@ -29,15 +31,12 @@ namespace Avalonia.Input
                 RoutingStrategies.Tunnel);
         }
 
-        /// <summary>
-        /// Gets the instance of the <see cref="IFocusManager"/>.
-        /// </summary>
-        public static IFocusManager? Instance => AvaloniaLocator.Current.GetService<IFocusManager>();
+        private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
 
         /// <summary>
         /// Gets the currently focused <see cref="IInputElement"/>.
         /// </summary>
-        public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
+        public IInputElement? GetFocusedElement() => Current;
 
         /// <summary>
         /// Gets the current focus scope.
@@ -54,7 +53,7 @@ namespace Avalonia.Input
         /// <param name="control">The control to focus.</param>
         /// <param name="method">The method by which focus was changed.</param>
         /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
-        public void Focus(
+        public bool Focus(
             IInputElement? control, 
             NavigationMethod method = NavigationMethod.Unspecified,
             KeyModifiers keyModifiers = KeyModifiers.None)
@@ -67,7 +66,7 @@ namespace Avalonia.Input
                 if (scope != null)
                 {
                     Scope = scope;
-                    SetFocusedElement(scope, control, method, keyModifiers);
+                    return SetFocusedElement(scope, control, method, keyModifiers);
                 }
             }
             else if (Current != null)
@@ -79,28 +78,29 @@ namespace Avalonia.Input
                         _focusScopes.TryGetValue(scope, out var element) &&
                         element != null)
                     {
-                        Focus(element, method);
-                        return;
+                        return Focus(element, method);
                     }
                 }
 
                 if (Scope is object)
                 {
                     // Couldn't find a focus scope, clear focus.
-                    SetFocusedElement(Scope, null);
+                    return SetFocusedElement(Scope, null);
                 }
             }
+
+            return false;
         }
 
-        public IInputElement? GetFocusedElement(IInputElement e)
+        public void ClearFocus()
         {
-            if (e is IFocusScope scope)
-            {
-                _focusScopes.TryGetValue(scope, out var result);
-                return result;
-            }
+            Focus(null);
+        }
 
-            return null;
+        public IInputElement? GetFocusedElement(IFocusScope scope)
+        {
+            _focusScopes.TryGetValue(scope, out var result);
+            return result;
         }
 
         /// <summary>
@@ -114,7 +114,7 @@ namespace Avalonia.Input
         /// If the specified scope is the current <see cref="Scope"/> then the keyboard focus
         /// will change.
         /// </remarks>
-        public void SetFocusedElement(
+        public bool SetFocusedElement(
             IFocusScope scope,
             IInputElement? element,
             NavigationMethod method = NavigationMethod.Unspecified,
@@ -124,7 +124,7 @@ namespace Avalonia.Input
 
             if (element is not null && !CanFocus(element))
             {
-                return;
+                return false;
             }
 
             if (_focusScopes.TryGetValue(scope, out var existingElement))
@@ -144,6 +144,8 @@ namespace Avalonia.Input
             {
                 KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers);
             }
+
+            return true;
         }
 
         /// <summary>
@@ -185,6 +187,20 @@ namespace Avalonia.Input
 
         public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
 
+        /// <summary>
+        /// Public API customers should use TopLevel.GetTopLevel(control).FocusManager.
+        /// But since we have split projects, we can't access TopLevel from Avalonia.Base.
+        /// That's why we need this helper method instead.
+        /// </summary>
+        internal static FocusManager? GetFocusManager(IInputElement? element)
+        {
+            // Element might not be a visual, and not attached to the root.
+            // But IFocusManager is always expected to be a FocusManager. 
+            return (FocusManager?)((IInputRoot?)(element as Visual)?.VisualRoot)?.FocusManager
+                   // In our unit tests some elements might not have a root. Remove when  
+                ?? (FocusManager?)AvaloniaLocator.Current.GetService<IFocusManager>();
+        }
+        
         /// <summary>
         /// Checks if the specified element can be focused.
         /// </summary>
@@ -221,7 +237,7 @@ namespace Avalonia.Input
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
-        private static void OnPreviewPointerPressed(object? sender, RoutedEventArgs e)
+        private void OnPreviewPointerPressed(object? sender, RoutedEventArgs e)
         {
             if (sender is null)
                 return;
@@ -237,7 +253,7 @@ namespace Avalonia.Input
                 {
                     if (element is IInputElement inputElement && CanFocus(inputElement))
                     {
-                        Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers);
+                        Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers);
 
                         break;
                     }

+ 4 - 32
src/Avalonia.Base/Input/IFocusManager.cs

@@ -11,40 +11,12 @@ namespace Avalonia.Input
         /// <summary>
         /// Gets the currently focused <see cref="IInputElement"/>.
         /// </summary>
-        IInputElement? Current { get; }
+        IInputElement? GetFocusedElement();
 
         /// <summary>
-        /// Gets the current focus scope.
+        /// Clears currently focused element.
         /// </summary>
-        IFocusScope? Scope { get; }
-
-        /// <summary>
-        /// Focuses a control.
-        /// </summary>
-        /// <param name="control">The control to focus.</param>
-        /// <param name="method">The method by which focus was changed.</param>
-        /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
-        void Focus(
-            IInputElement? control,
-            NavigationMethod method = NavigationMethod.Unspecified,
-            KeyModifiers keyModifiers = KeyModifiers.None);
-
-        /// <summary>
-        /// Notifies the focus manager of a change in focus scope.
-        /// </summary>
-        /// <param name="scope">The new focus scope.</param>
-        /// <remarks>
-        /// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
-        /// when it activates, e.g. when a Window is activated.
-        /// </remarks>
-        void SetFocusScope(IFocusScope scope);
-
-        /// <summary>
-        /// Notifies the focus manager that a focus scope has been removed.
-        /// </summary>
-        /// <param name="scope">The focus scope to be removed.</param>
-        /// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
-        /// when it deactivates or closes, e.g. when a Window is closed.
-        void RemoveFocusScope(IFocusScope scope);
+        [Unstable("This API might be removed in 11.x minor updates. Please consider focusing another element instead of removing focus at all for better UX.")]
+        void ClearFocus();
     }
 }

+ 3 - 0
src/Avalonia.Base/Input/IFocusScope.cs

@@ -1,5 +1,8 @@
+using Avalonia.Metadata;
+
 namespace Avalonia.Input
 {
+    [NotClientImplementable]
     public interface IFocusScope
     {
     }

+ 3 - 1
src/Avalonia.Base/Input/IInputElement.cs

@@ -119,7 +119,9 @@ namespace Avalonia.Input
         /// <summary>
         /// Focuses the control.
         /// </summary>
-        void Focus();
+        /// <param name="method">The method by which focus was changed.</param>
+        /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
+        bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None);
 
         /// <summary>
         /// Gets the key bindings for the element.

+ 8 - 0
src/Avalonia.Base/Input/IInputRoot.cs

@@ -18,6 +18,14 @@ namespace Avalonia.Input
         /// </summary>
         IKeyboardNavigationHandler KeyboardNavigationHandler { get; }
 
+        /// <summary>
+        /// Gets focus manager of the root.
+        /// </summary>
+        /// <remarks>
+        /// Focus manager can be null only if application wasn't initialized yet.
+        /// </remarks>
+        IFocusManager? FocusManager { get; }
+        
         /// <summary>
         /// Gets or sets the input element that the pointer is currently over.
         /// </summary>

+ 1 - 7
src/Avalonia.Base/Input/IKeyboardDevice.cs

@@ -44,13 +44,7 @@ namespace Avalonia.Input
     }
 
     [NotClientImplementable]
-    public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
+    public interface IKeyboardDevice : IInputDevice
     {
-        IInputElement? FocusedElement { get; }
-
-        void SetFocusedElement(
-            IInputElement? element, 
-            NavigationMethod method,
-            KeyModifiers modifiers);
     }
 }

+ 8 - 9
src/Avalonia.Base/Input/InputElement.cs

@@ -458,9 +458,10 @@ namespace Avalonia.Input
                 SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
                 PseudoClasses.Set(":disabled", !value);
 
-                if (!IsEffectivelyEnabled && FocusManager.Instance?.Current == this)
+                if (!IsEffectivelyEnabled && FocusManager.GetFocusManager(this) is {} focusManager
+                    && Equals(focusManager.GetFocusedElement(), this))
                 {
-                    FocusManager.Instance?.Focus(null);
+                    focusManager.ClearFocus();
                 }
             }
         }
@@ -491,12 +492,10 @@ namespace Avalonia.Input
         public GestureRecognizerCollection GestureRecognizers
             => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
 
-        /// <summary>
-        /// Focuses the control.
-        /// </summary>
-        public void Focus()
+        /// <inheritdoc />
+        public bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None)
         {
-            FocusManager.Instance?.Focus(this);
+            return FocusManager.GetFocusManager(this)?.Focus(this, method, keyModifiers) ?? false; 
         }
 
         /// <inheritdoc/>
@@ -506,7 +505,7 @@ namespace Avalonia.Input
 
             if (IsFocused)
             {
-                FocusManager.Instance?.Focus(null);
+                FocusManager.GetFocusManager(this)?.ClearFocus();
             }
         }
 
@@ -649,7 +648,7 @@ namespace Avalonia.Input
             }
             else if (change.Property == IsVisibleProperty && !change.GetNewValue<bool>() && IsFocused)
             {
-                FocusManager.Instance?.Focus(null);
+                FocusManager.GetFocusManager(this)?.ClearFocus();
             }
         }
 

+ 3 - 1
src/Avalonia.Base/Input/KeyboardDevice.cs

@@ -3,9 +3,11 @@ using System.Runtime.CompilerServices;
 using Avalonia.Input.Raw;
 using Avalonia.Input.TextInput;
 using Avalonia.Interactivity;
+using Avalonia.Metadata;
 
 namespace Avalonia.Input
 {
+    [Unstable]
     public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged
     {
         private IInputElement? _focusedElement;
@@ -13,7 +15,7 @@ namespace Avalonia.Input
 
         public event PropertyChangedEventHandler? PropertyChanged;
 
-        public static IKeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
+        internal static KeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>() as KeyboardDevice;
 
         public IInputManager? InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
 

+ 2 - 2
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@@ -88,7 +88,7 @@ namespace Avalonia.Input
                 var method = direction == NavigationDirection.Next ||
                              direction == NavigationDirection.Previous ?
                              NavigationMethod.Tab : NavigationMethod.Directional;
-                FocusManager.Instance?.Focus(next, method, keyModifiers);
+                next.Focus(method, keyModifiers);
             }
         }
 
@@ -99,7 +99,7 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
         {
-            var current = FocusManager.Instance?.Current;
+            var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
 
             if (current != null && e.Key == Key.Tab)
             {

+ 2 - 0
src/Avalonia.Base/Input/MouseDevice.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Reactive;
 using Avalonia.Input.Raw;
+using Avalonia.Metadata;
 using Avalonia.Platform;
 using Avalonia.Utilities;
 #pragma warning disable CS0618
@@ -11,6 +12,7 @@ namespace Avalonia.Input
     /// <summary>
     /// Represents a mouse device.
     /// </summary>
+    [Unstable]
     public class MouseDevice : IMouseDevice, IDisposable
     {
         private int _clickCount;

+ 4 - 2
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@@ -190,9 +190,11 @@ namespace Avalonia.Input.Navigation
         private static IInputElement? FocusedElement(IInputElement? e)
         {
             // Focus delegation is enabled only if keyboard focus is outside the container
-            if (e != null && !e.IsKeyboardFocusWithin)
+            if (e != null && !e.IsKeyboardFocusWithin && e is IFocusScope scope)
             {
-                var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e);
+                var focusManager = FocusManager.GetFocusManager(e);
+
+                var focusedElement = focusManager?.GetFocusedElement(scope);
                 if (focusedElement != null)
                 {
                     if (!IsFocusScope(e))

+ 3 - 2
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -3958,7 +3958,7 @@ namespace Avalonia.Controls
             {
                 bool focusLeftDataGrid = true;
                 bool dataGridWillReceiveRoutedEvent = true;
-                Visual focusedObject = FocusManager.Instance.Current as Visual;
+                Visual focusedObject = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
                 DataGridColumn editingColumn = null;
 
                 while (focusedObject != null)
@@ -4865,7 +4865,8 @@ namespace Avalonia.Controls
             if (!ctrl)
             {
                 // If Enter was used by a TextBox, we shouldn't handle the key
-                if (FocusManager.Instance.Current is TextBox focusedTextBox && focusedTextBox.AcceptsReturn)
+                if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is TextBox focusedTextBox
+                    && focusedTextBox.AcceptsReturn)
                 {
                     return false;
                 }

+ 1 - 1
src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs

@@ -695,7 +695,7 @@ namespace Avalonia.Controls
         {
             Control? focusedElement = null;
 
-            if (FocusManager.Instance?.Current is Visual child)
+            if (TopLevel.GetTopLevel(_owner)?.FocusManager?.GetFocusedElement() is Visual child)
             {
                 var parent = child.GetVisualParent();
                 var owner = _owner;

+ 1 - 1
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@@ -762,7 +762,7 @@ namespace Avalonia.Controls
         /// otherwise, false.</returns>
         protected bool HasFocus()
         {
-            Visual? focused = FocusManager.Instance?.Current as Visual;
+            Visual? focused = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
 
             while (focused != null)
             {

+ 1 - 1
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -1567,7 +1567,7 @@ namespace Avalonia.Controls
             base.OnPointerReleased(e);
             if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
             {
-                FocusManager.Instance?.Focus(this);
+                Focus();
             }
         }
 

+ 1 - 2
src/Avalonia.Controls/ComboBox.cs

@@ -230,8 +230,7 @@ namespace Avalonia.Controls
                 var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
                 if (firstChild != null)
                 {
-                    FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional);
-                    e.Handled = true;
+                    e.Handled = firstChild.Focus(NavigationMethod.Directional);
                 }
             }
         }

+ 2 - 2
src/Avalonia.Controls/ContextMenu.cs

@@ -360,7 +360,7 @@ namespace Avalonia.Controls
 
         private void PopupOpened(object? sender, EventArgs e)
         {
-            _previousFocus = FocusManager.Instance?.Current;
+            _previousFocus = FocusManager.GetFocusManager(this)?.GetFocusedElement();
             Focus();
 
             _popupHostChangedHandler?.Invoke(_popup!.Host);
@@ -390,7 +390,7 @@ namespace Avalonia.Controls
             }
 
             // HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic.
-            FocusManager.Instance?.Focus(_previousFocus);
+            _previousFocus?.Focus();
 
             RaiseEvent(new RoutedEventArgs
             {

+ 6 - 5
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -323,10 +323,11 @@ namespace Avalonia.Controls
                     e.Handled = true;
                     break;
                 case Key.Tab:
-                    if (FocusManager.Instance?.Current is IInputElement focus)
+                    var focusManager = FocusManager.GetFocusManager(this);
+                    if (focusManager?.GetFocusedElement() is { } focus)
                     {
                         var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
-                        KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+                        nextFocus?.Focus(NavigationMethod.Tab);
                         e.Handled = true;
                     }
                     break;
@@ -449,15 +450,15 @@ namespace Avalonia.Controls
 
             if (monthCol < dayCol && monthCol < yearCol)
             {
-                KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None);
+                _monthSelector?.Focus(NavigationMethod.Pointer);
             }
             else if (dayCol < monthCol && dayCol < yearCol)
             {
-                KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None);
+                _monthSelector?.Focus(NavigationMethod.Pointer);
             }
             else if (yearCol < monthCol && yearCol < dayCol)
             {
-                KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None);
+                _yearSelector?.Focus(NavigationMethod.Pointer);
             }
         }
 

+ 3 - 3
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@@ -161,10 +161,10 @@ namespace Avalonia.Controls
                     e.Handled = true;
                     break;
                 case Key.Tab:
-                    if (FocusManager.Instance?.Current is IInputElement focus)
+                    if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is { } focus)
                     {
                         var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
-                        KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+                        nextFocus?.Focus(NavigationMethod.Tab);
                         e.Handled = true;
                     }
                     break;
@@ -216,7 +216,7 @@ namespace Avalonia.Controls
             _periodSelector.SelectedValue = hr >= 12 ? 1 : 0;
 
             SetGrid();
-            KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None);
+            _hourSelector?.Focus(NavigationMethod.Pointer);
         }
 
         private void SetGrid()

+ 2 - 2
src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs

@@ -250,14 +250,14 @@ namespace Avalonia.Controls.Primitives
                 // Try and focus content inside Flyout
                 if (Popup.Child.Focusable)
                 {
-                    FocusManager.Instance?.Focus(Popup.Child);
+                    Popup.Child.Focus();
                 }
                 else
                 {
                     var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next);
                     if (nextFocus != null)
                     {
-                        FocusManager.Instance?.Focus(nextFocus);
+                        nextFocus.Focus();
                     }
                 }
             }

+ 6 - 5
src/Avalonia.Controls/ItemsControl.cs

@@ -566,19 +566,20 @@ namespace Avalonia.Controls
         {
             if (!e.Handled)
             {
-                var focus = FocusManager.Instance;
+                var focus = FocusManager.GetFocusManager(this);
                 var direction = e.Key.ToNavigationDirection();
                 var container = Presenter?.Panel as INavigableContainer;
 
-                if (container == null ||
-                    focus?.Current == null ||
+                if (focus == null ||
+                    container == null ||
+                    focus.GetFocusedElement() == null ||
                     direction == null ||
                     direction.Value.IsTab())
                 {
                     return;
                 }
 
-                Visual? current = focus.Current as Visual;
+                Visual? current = focus.GetFocusedElement() as Visual;
 
                 while (current != null)
                 {
@@ -588,7 +589,7 @@ namespace Avalonia.Controls
 
                         if (next != null)
                         {
-                            focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers);
+                            next.Focus(NavigationMethod.Directional, e.KeyModifiers);
                             e.Handled = true;
                         }
 

+ 3 - 3
src/Avalonia.Controls/Primitives/Popup.cs

@@ -727,7 +727,7 @@ namespace Avalonia.Controls.Primitives
 
             Closed?.Invoke(this, EventArgs.Empty);
 
-            var focusCheck = FocusManager.Instance?.Current;
+            var focusCheck = FocusManager.GetFocusManager(this)?.GetFocusedElement();
 
             // Focus is set to null as part of popup closing, so we only want to
             // set focus to PlacementTarget if this is the case
@@ -744,7 +744,7 @@ namespace Avalonia.Controls.Primitives
 
                     if (e is object)
                     {
-                        FocusManager.Instance?.Focus(e);
+                        e.Focus();
                     }
                 }
                 else
@@ -752,7 +752,7 @@ namespace Avalonia.Controls.Primitives
                     var anc = this.FindLogicalAncestorOfType<Control>();
                     if (anc != null)
                     {
-                        FocusManager.Instance?.Focus(anc);
+                        anc.Focus();
                     }
                 }
             }

+ 4 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -452,6 +452,9 @@ namespace Avalonia.Controls
         /// </summary>
         public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
 
+        /// <inheritdoc />
+        public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
+
         /// <inheritdoc/>
         Point IRenderRoot.PointToClient(PixelPoint p)
         {
@@ -725,7 +728,7 @@ namespace Avalonia.Controls
 
         void PlatformImpl_LostFocus()
         {
-            var focused = (Visual?)FocusManager.Instance?.Current;
+            var focused = (Visual?)FocusManager?.GetFocusedElement();
             if (focused == null)
                 return;
             while (focused.VisualParent != null)

+ 1 - 2
src/Avalonia.Controls/TreeView.cs

@@ -560,8 +560,7 @@ namespace Avalonia.Controls
 
                     if (next != null)
                     {
-                        FocusManager.Instance?.Focus(next, NavigationMethod.Directional);
-                        e.Handled = true;
+                        e.Handled = next.Focus(NavigationMethod.Directional);
                     }
                 }
                 else

+ 1 - 1
src/Avalonia.Controls/TreeViewItem.cs

@@ -238,7 +238,7 @@ namespace Avalonia.Controls
                         }
                         else
                         {
-                            FocusManager.Instance?.Focus(treeViewItem, NavigationMethod.Directional);
+                            treeViewItem.Focus(NavigationMethod.Directional);
                         }
 
                         return true;

+ 2 - 2
src/Avalonia.Controls/WindowBase.cs

@@ -234,7 +234,7 @@ namespace Avalonia.Controls
 
                 if (this is IFocusScope scope)
                 {
-                    FocusManager.Instance?.RemoveFocusScope(scope);
+                    ((FocusManager?)FocusManager)?.RemoveFocusScope(scope);
                 }
 
                 base.HandleClosed();
@@ -326,7 +326,7 @@ namespace Avalonia.Controls
 
             if (scope != null)
             {
-                FocusManager.Instance?.SetFocusScope(scope);
+                ((FocusManager?)FocusManager)?.SetFocusScope(scope);
             }
 
             IsActive = true;

+ 4 - 4
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics
         private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options,
             Window? owner, Application? app)
         {
-            var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control;
+            var focusedControl = owner?.FocusManager?.GetFocusedElement() as Control;
             AvaloniaObject root = topLevelGroup switch
             {
                 ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!),
@@ -98,7 +98,7 @@ namespace Avalonia.Diagnostics
             if (s_open.TryGetValue(topLevelGroup, out var mainWindow))
             {
                 mainWindow.Activate();
-                mainWindow.SelectedControl(focussedControl);
+                mainWindow.SelectedControl(focusedControl);
                 return Disposable.Empty;
             }
             if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged)
@@ -110,7 +110,7 @@ namespace Avalonia.Diagnostics
                     if (group.Key.Items.Contains(singleTopLevel))
                     {
                         group.Value.Activate();
-                        group.Value.SelectedControl(focussedControl);
+                        group.Value.SelectedControl(focusedControl);
                         return Disposable.Empty;
                     }
                 }
@@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics
                 Tag = topLevelGroup
             };
             window.SetOptions(options);
-            window.SelectedControl(focussedControl);
+            window.SelectedControl(focusedControl);
             window.Closed += DevToolsClosed;
             s_open.Add(topLevelGroup, window);
             if (options.ShowAsChildWindow && owner is not null)

+ 1 - 1
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -107,7 +107,7 @@ namespace Avalonia.LinuxFramebuffer
 
                     if (_topLevel is IFocusScope scope)
                     {
-                        FocusManager.Instance?.SetFocusScope(scope);
+                        ((FocusManager)_topLevel.FocusManager).SetFocusScope(scope);
                     }
                 }
 

+ 1 - 1
src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs

@@ -22,7 +22,7 @@ namespace Avalonia.Win32.Embedding
             UnmanagedMethods.SetParent(WindowHandle, Handle);
             _root.Prepare();
             if (_root.IsFocused)
-                FocusManager.Instance.Focus(null);
+                _root.FocusManager.ClearFocus();
             _root.GotFocus += RootGotFocus;
 
             FixPosition();

+ 1 - 1
src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs

@@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
 
 namespace Avalonia.Win32.Input
 {
-    class WindowsKeyboardDevice : KeyboardDevice
+    internal class WindowsKeyboardDevice : KeyboardDevice
     {
         private readonly byte[] _keyStates = new byte[256];
 

+ 29 - 29
tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs

@@ -23,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Input
 
                 target.Focus();
 
-                Assert.Same(target, FocusManager.Instance.Current);
+                Assert.Same(target, root.FocusManager.GetFocusedElement());
             }
         }
         
@@ -39,14 +39,14 @@ namespace Avalonia.Base.UnitTests.Input
                     Child = target = new Button() { IsVisible = false}
                 };
                 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
 
                 target.Focus();
                 
                 Assert.False(target.IsFocused);
                 Assert.False(target.IsKeyboardFocusWithin);
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
         
@@ -67,14 +67,14 @@ namespace Avalonia.Base.UnitTests.Input
                     }
                 };
                 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
 
                 target.Focus();
                 
                 Assert.False(target.IsFocused);
                 Assert.False(target.IsKeyboardFocusWithin);
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -100,11 +100,11 @@ namespace Avalonia.Base.UnitTests.Input
 
                 first.Focus();
 
-                Assert.Same(first, FocusManager.Instance.Current);
+                Assert.Same(first, root.FocusManager.GetFocusedElement());
 
                 second.Focus();
 
-                Assert.Same(first, FocusManager.Instance.Current);
+                Assert.Same(first, root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -120,14 +120,14 @@ namespace Avalonia.Base.UnitTests.Input
                     Child = target = new Button() { IsEnabled = false }
                 };
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
 
                 target.Focus();
 
                 Assert.False(target.IsFocused);
                 Assert.False(target.IsKeyboardFocusWithin);
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -148,14 +148,14 @@ namespace Avalonia.Base.UnitTests.Input
                     }
                 };
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
 
                 target.Focus();
 
                 Assert.False(target.IsFocused);
                 Assert.False(target.IsKeyboardFocusWithin);
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -201,7 +201,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target.Focus();
                 target.IsVisible = false;
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -224,7 +224,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target.Focus();
                 container.IsVisible = false;
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -243,7 +243,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target.Focus();
                 target.IsEnabled = false;
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -266,7 +266,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target.Focus();
                 container.IsEnabled = false;
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -285,7 +285,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target.Focus();
                 root.Child = null;
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
 
@@ -312,13 +312,13 @@ namespace Avalonia.Base.UnitTests.Input
                 target2.ApplyTemplate();
 
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus"));
                 Assert.False(target2.IsFocused);
                 Assert.False(target2.Classes.Contains(":focus"));
 
-                FocusManager.Instance?.Focus(target2, NavigationMethod.Tab);
+                target2.Focus(NavigationMethod.Tab);
                 Assert.False(target1.IsFocused);
                 Assert.False(target1.Classes.Contains(":focus"));
                 Assert.True(target2.IsFocused);
@@ -348,19 +348,19 @@ namespace Avalonia.Base.UnitTests.Input
                 target1.ApplyTemplate();
                 target2.ApplyTemplate();
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.False(target1.Classes.Contains(":focus-visible"));
                 Assert.False(target2.IsFocused);
                 Assert.False(target2.Classes.Contains(":focus-visible"));
 
-                FocusManager.Instance?.Focus(target2, NavigationMethod.Tab);
+                target2.Focus(NavigationMethod.Tab);
                 Assert.False(target1.IsFocused);
                 Assert.False(target1.Classes.Contains(":focus-visible"));
                 Assert.True(target2.IsFocused);
                 Assert.True(target2.Classes.Contains(":focus-visible"));
 
-                FocusManager.Instance?.Focus(target1, NavigationMethod.Directional);
+                target1.Focus(NavigationMethod.Directional);
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus-visible"));
                 Assert.False(target2.IsFocused);
@@ -390,7 +390,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target1.ApplyTemplate();
                 target2.ApplyTemplate();
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus-within"));
                 Assert.True(target1.IsKeyboardFocusWithin);
@@ -425,7 +425,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target1.ApplyTemplate();
                 target2.ApplyTemplate();
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus-within"));
                 Assert.True(target1.IsKeyboardFocusWithin);
@@ -436,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Input
                 Assert.True(root.Classes.Contains(":focus-within"));
                 Assert.True(root.IsKeyboardFocusWithin);
                 
-                FocusManager.Instance?.Focus(target2);
+                target2.Focus();
                 
                 Assert.False(target1.IsFocused);
                 Assert.False(target1.Classes.Contains(":focus-within"));
@@ -478,7 +478,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target1.ApplyTemplate();
                 target2.ApplyTemplate();
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus-within"));
                 Assert.True(target1.IsKeyboardFocusWithin);
@@ -534,7 +534,7 @@ namespace Avalonia.Base.UnitTests.Input
                 target1.ApplyTemplate();
                 target2.ApplyTemplate();
 
-                FocusManager.Instance?.Focus(target1);
+                target1.Focus();
                 Assert.True(target1.IsFocused);
                 Assert.True(target1.Classes.Contains(":focus-within"));
                 Assert.True(target1.IsKeyboardFocusWithin);
@@ -545,7 +545,7 @@ namespace Avalonia.Base.UnitTests.Input
 
                 Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1);
                 
-                FocusManager.Instance?.Focus(target2);
+                target2.Focus();
                 
                 Assert.False(target1.IsFocused);
                 Assert.False(target1.Classes.Contains(":focus-within"));
@@ -578,9 +578,9 @@ namespace Avalonia.Base.UnitTests.Input
                 };
 
                 target.Focus();
-                FocusManager.Instance.Focus(null);
+                root.FocusManager.ClearFocus();
 
-                Assert.Null(FocusManager.Instance.Current);
+                Assert.Null(root.FocusManager.GetFocusedElement());
             }
         }
     }

+ 3 - 1
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@@ -20,7 +20,9 @@ namespace Avalonia.Base.UnitTests.Input
         [Fact]
         public void Close_Should_Remove_PointerOver()
         {
-            using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
+            using var app = UnitTestApplication.Start(new TestServices(
+                inputManager: new InputManager(),
+                focusManager: new FocusManager()));
 
             var renderer = RendererMocks.CreateRenderer();
             var device = CreatePointerDeviceMock().Object;

+ 5 - 5
tests/Avalonia.Controls.UnitTests/FlyoutTests.cs

@@ -288,10 +288,10 @@ namespace Avalonia.Controls.UnitTests
                 window.Show();
 
                 button.Focus();
-                Assert.True(FocusManager.Instance?.Current == button);
+                Assert.True(window.FocusManager.GetFocusedElement() == button);
                 button.Flyout.ShowAt(button);
                 Assert.False(button.IsFocused);
-                Assert.True(FocusManager.Instance?.Current == flyoutTextBox);
+                Assert.True(window.FocusManager.GetFocusedElement() == flyoutTextBox);
             }
         }
 
@@ -322,10 +322,10 @@ namespace Avalonia.Controls.UnitTests
                 window.Content = button;
                 window.Show();
 
-                FocusManager.Instance?.Focus(button);
-                Assert.True(FocusManager.Instance?.Current == button);
+                button.Focus();
+                Assert.True(window.FocusManager.GetFocusedElement() == button);
                 button.Flyout.ShowAt(button);
-                Assert.True(FocusManager.Instance?.Current == button);
+                Assert.True(window.FocusManager.GetFocusedElement() == button);
             }
         }
 

+ 4 - 2
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -576,8 +576,9 @@ namespace Avalonia.Controls.UnitTests
             });
 
             var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot);
+            var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
 
-            Assert.Equal(panel.Children[1], FocusManager.Instance!.Current);
+            Assert.Equal(panel.Children[1], focusManager?.GetFocusedElement());
         }
 
         [Fact]
@@ -601,8 +602,9 @@ namespace Avalonia.Controls.UnitTests
             });
 
             var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot);
+            var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
 
-            Assert.Equal(panel.Children[2], FocusManager.Instance!.Current);
+            Assert.Equal(panel.Children[2], focusManager?.GetFocusedElement());
         }
 
         [Fact]

+ 4 - 4
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -979,7 +979,7 @@ namespace Avalonia.Controls.UnitTests
             RaiseKeyEvent(button, Key.Tab);
 
             var item = target.ContainerFromIndex(0);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
         }
 
         [Fact]
@@ -1026,17 +1026,17 @@ namespace Avalonia.Controls.UnitTests
             RaiseKeyEvent(button, Key.Tab);
 
             var item = target.ContainerFromIndex(1);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
 
             RaiseKeyEvent(item, Key.Tab);
 
-            Assert.Same(button, FocusManager.Instance.Current);
+            Assert.Same(button, root.FocusManager.GetFocusedElement());
 
             target.Selection.AnchorIndex = 2;
             RaiseKeyEvent(button, Key.Tab);
 
             item = target.ContainerFromIndex(2);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
         }
 
         private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)

+ 2 - 2
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@@ -267,7 +267,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 target.KeyDown(item.Object, e);
 
                 parentItem.Verify(x => x.Close());
-                parentItem.Verify(x => x.Focus());
+                parentItem.Verify(x => x.Focus(It.IsAny<NavigationMethod>(), It.IsAny<KeyModifiers>()));
                 Assert.True(e.Handled);
             }
 
@@ -351,7 +351,7 @@ namespace Avalonia.Controls.UnitTests.Platform
                 target.KeyDown(item.Object, e);
 
                 parentItem.Verify(x => x.Close());
-                parentItem.Verify(x => x.Focus());
+                parentItem.Verify(x => x.Focus(It.IsAny<NavigationMethod>(), It.IsAny<KeyModifiers>()));
                 Assert.True(e.Handled);
             }
 

+ 7 - 4
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@@ -642,10 +642,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
                 tb.Focus();
 
-                Assert.True(FocusManager.Instance?.Current == tb);
+                var focusManager = TopLevel.GetTopLevel(tb)!.FocusManager;
+                tb = Assert.IsType<TextBox>(focusManager.GetFocusedElement());
 
                 //Ensure focus remains in the popup
-                var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
+                var nextFocus = KeyboardNavigationHandler.GetNext(tb, NavigationDirection.Next);
 
                 Assert.True(nextFocus == b);
 
@@ -684,7 +685,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
                 p.Close();
 
-                var focus = FocusManager.Instance?.Current;
+                var focusManager = window.FocusManager;
+                var focus = focusManager.GetFocusedElement();
                 Assert.True(focus == window);
             }
         }
@@ -723,7 +725,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
                 windowTB.Focus();
 
-                var focus = FocusManager.Instance?.Current;
+                var focusManager = window.FocusManager;
+                var focus = focusManager.GetFocusedElement();
 
                 Assert.True(focus == windowTB);
 

+ 4 - 4
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -461,7 +461,7 @@ namespace Avalonia.Controls.UnitTests
             RaiseKeyEvent(button, Key.Tab);
 
             var item = target.ContainerFromIndex(0);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
         }
 
         [Fact]
@@ -513,17 +513,17 @@ namespace Avalonia.Controls.UnitTests
             RaiseKeyEvent(button, Key.Tab);
 
             var item = target.ContainerFromIndex(1);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
 
             RaiseKeyEvent(item, Key.Tab);
 
-            Assert.Same(button, FocusManager.Instance.Current);
+            Assert.Same(button, root.FocusManager.GetFocusedElement());
 
             target.Selection.AnchorIndex = 2;
             RaiseKeyEvent(button, Key.Tab);
 
             item = target.ContainerFromIndex(2);
-            Assert.Same(item, FocusManager.Instance.Current);
+            Assert.Same(item, root.FocusManager.GetFocusedElement());
         }
 
         private static IControlTemplate TabControlTemplate()

+ 8 - 8
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@@ -969,7 +969,6 @@ namespace Avalonia.Controls.UnitTests
         public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
         {
             using var app = Start();
-            var focus = FocusManager.Instance!;
             var navigation = AvaloniaLocator.Current.GetRequiredService<IKeyboardNavigationHandler>();
             var data = CreateTestTreeData();
 
@@ -984,6 +983,7 @@ namespace Avalonia.Controls.UnitTests
             {
                 Children = { target, button },
             });
+            var focus = root.FocusManager;
 
             root.LayoutManager.ExecuteInitialLayoutPass();
             ExpandAll(target);
@@ -994,20 +994,19 @@ namespace Avalonia.Controls.UnitTests
 
             target.SelectedItem = item;
             node.Focus();
-            Assert.Same(node, focus.Current);
+            Assert.Same(node, focus.GetFocusedElement());
 
-            navigation.Move(focus.Current!, NavigationDirection.Next);
-            Assert.Same(button, focus.Current);
+            navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
+            Assert.Same(button, focus.GetFocusedElement());
 
-            navigation.Move(focus.Current!, NavigationDirection.Next);
-            Assert.Same(node, focus.Current);
+            navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
+            Assert.Same(node, focus.GetFocusedElement());
         }
 
         [Fact]
         public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree()
         {
             using var app = Start();
-            var focus = FocusManager.Instance!;
             var data = CreateTestTreeData();
 
             var selectedNode = new Node { Value = "Out of Tree Selected Item" };
@@ -1025,6 +1024,7 @@ namespace Avalonia.Controls.UnitTests
             {
                 Children = { target, button },
             });
+            var focus = root.FocusManager;
 
             root.LayoutManager.ExecuteInitialLayoutPass();
             ExpandAll(target);
@@ -1035,7 +1035,7 @@ namespace Avalonia.Controls.UnitTests
 
             target.SelectedItem = selectedNode;
             node.Focus();
-            Assert.Same(node, focus.Current);
+            Assert.Same(node, focus.GetFocusedElement());
         }
 
         [Fact]

+ 2 - 2
tests/Avalonia.LeakTests/ControlTests.cs

@@ -561,7 +561,7 @@ namespace Avalonia.LeakTests
                 var window = new Window { Focusable = true };
                 window.Show();
 
-                Assert.Same(window, FocusManager.Instance.Current);
+                Assert.Same(window, window.FocusManager.GetFocusedElement());
 
                 // Context menu in resources means the baseline may not be 0.
                 var initialMenuCount = 0;
@@ -608,7 +608,7 @@ namespace Avalonia.LeakTests
                 var window = new Window { Focusable = true };
                 window.Show();
 
-                Assert.Same(window, FocusManager.Instance.Current);
+                Assert.Same(window, window.FocusManager.GetFocusedElement());
 
                 // Context menu in resources means the baseline may not be 0.
                 var initialMenuCount = 0;

+ 1 - 0
tests/Avalonia.UnitTests/TestRoot.cs

@@ -54,6 +54,7 @@ namespace Avalonia.UnitTests
         public IAccessKeyHandler AccessKeyHandler => null;
 
         public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
+        public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
 
         public IInputElement PointerOverElement { get; set; }