diff --git a/src/Perspex.Input/FocusManager.cs b/src/Perspex.Input/FocusManager.cs
index 1ce645fd13..78935dc13e 100644
--- a/src/Perspex.Input/FocusManager.cs
+++ b/src/Perspex.Input/FocusManager.cs
@@ -39,7 +39,7 @@ namespace Perspex.Input
///
/// Gets the currently focused .
///
- public IInputElement Current => KeyboardDevice.Instance.FocusedElement;
+ public IInputElement Current => KeyboardDevice.Instance?.FocusedElement;
///
/// Gets the current focus scope.
@@ -82,9 +82,12 @@ namespace Perspex.Input
if (_focusScopes.TryGetValue(scope, out element))
{
Focus(element, method);
- break;
+ return;
}
}
+
+ // Couldn't find a focus scope, clear focus.
+ SetFocusedElement(Scope, null);
}
}
@@ -111,7 +114,7 @@ namespace Perspex.Input
if (Scope == scope)
{
- KeyboardDevice.Instance.SetFocusedElement(element, method, modifiers);
+ KeyboardDevice.Instance?.SetFocusedElement(element, method, modifiers);
}
}
@@ -176,7 +179,7 @@ namespace Perspex.Input
if (sender == e.Source)
{
var ev = (PointerPressedEventArgs)e;
- var element = (ev.Device.Captured as IInputElement) ?? (e.Source as IInputElement);
+ var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
if (element == null || !CanFocus(element))
{
diff --git a/src/Perspex.Input/InputElement.cs b/src/Perspex.Input/InputElement.cs
index 00e53df646..8c976bb155 100644
--- a/src/Perspex.Input/InputElement.cs
+++ b/src/Perspex.Input/InputElement.cs
@@ -376,9 +376,9 @@ namespace Perspex.Input
}
///
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
- base.OnDetachedFromVisualTree(e);
+ base.OnDetachedFromVisualTreeCore(e);
if (IsFocused)
{
@@ -387,9 +387,9 @@ namespace Perspex.Input
}
///
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
- base.OnAttachedToVisualTree(e);
+ base.OnAttachedToVisualTreeCore(e);
UpdateIsEnabledCore();
}
diff --git a/tests/Perspex.Input.UnitTests/InputElement_Focus.cs b/tests/Perspex.Input.UnitTests/InputElement_Focus.cs
new file mode 100644
index 0000000000..a3016cdd8a
--- /dev/null
+++ b/tests/Perspex.Input.UnitTests/InputElement_Focus.cs
@@ -0,0 +1,49 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Controls;
+using Perspex.UnitTests;
+using Xunit;
+
+namespace Perspex.Input.UnitTests
+{
+ public class InputElement_Focus
+ {
+ [Fact]
+ public void Focus_Should_Set_FocusManager_Current()
+ {
+ Button target;
+
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var root = new TestRoot
+ {
+ Child = target = new Button()
+ };
+
+ target.Focus();
+
+ Assert.Same(target, FocusManager.Instance.Current);
+ }
+ }
+
+ [Fact]
+ public void Focus_Should_Be_Cleared_When_Control_Is_Removed_From_VisualTree()
+ {
+ Button target;
+
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var root = new TestRoot
+ {
+ Child = target = new Button()
+ };
+
+ target.Focus();
+ root.Child = null;
+
+ Assert.Null(FocusManager.Instance.Current);
+ }
+ }
+ }
+}
diff --git a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
index e41c379026..d1b4bba60b 100644
--- a/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
+++ b/tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj
@@ -60,6 +60,7 @@
+
@@ -103,6 +104,10 @@
{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}
Perspex.Styling
+
+ {88060192-33D5-4932-B0F9-8BD2763E857D}
+ Perspex.UnitTests
+
diff --git a/tests/Perspex.UnitTests/TestRoot.cs b/tests/Perspex.UnitTests/TestRoot.cs
index 84a4a15aa7..48b8beaeba 100644
--- a/tests/Perspex.UnitTests/TestRoot.cs
+++ b/tests/Perspex.UnitTests/TestRoot.cs
@@ -3,6 +3,7 @@
using System;
using Perspex.Controls;
+using Perspex.Input;
using Perspex.Layout;
using Perspex.Platform;
using Perspex.Rendering;
@@ -10,7 +11,7 @@ using Perspex.Styling;
namespace Perspex.UnitTests
{
- public class TestRoot : Decorator, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot
+ public class TestRoot : Decorator, IFocusScope, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot
{
private readonly NameScope _nameScope = new NameScope();
diff --git a/tests/Perspex.UnitTests/TestServices.cs b/tests/Perspex.UnitTests/TestServices.cs
index 6383aeb516..bac9cc1c24 100644
--- a/tests/Perspex.UnitTests/TestServices.cs
+++ b/tests/Perspex.UnitTests/TestServices.cs
@@ -36,12 +36,19 @@ namespace Perspex.UnitTests
public static readonly TestServices MockThreadingInterface = new TestServices(
threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true));
+ public static readonly TestServices RealFocus = new TestServices(
+ focusManager: new FocusManager(),
+ keyboardDevice: () => new KeyboardDevice(),
+ inputManager: new InputManager());
+
public static readonly TestServices RealStyler = new TestServices(
styler: new Styler());
public TestServices(
IAssetLoader assetLoader = null,
+ IFocusManager focusManager = null,
IInputManager inputManager = null,
+ Func keyboardDevice = null,
ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null,
@@ -53,7 +60,9 @@ namespace Perspex.UnitTests
IWindowingPlatform windowingPlatform = null)
{
AssetLoader = assetLoader;
+ FocusManager = focusManager;
InputManager = inputManager;
+ KeyboardDevice = keyboardDevice;
LayoutManager = layoutManager;
PlatformWrapper = platformWrapper;
RenderInterface = renderInterface;
@@ -67,6 +76,8 @@ namespace Perspex.UnitTests
public IAssetLoader AssetLoader { get; }
public IInputManager InputManager { get; }
+ public IFocusManager FocusManager { get; }
+ public Func KeyboardDevice { get; }
public ILayoutManager LayoutManager { get; }
public IPclPlatformWrapper PlatformWrapper { get; }
public IPlatformRenderInterface RenderInterface { get; }
@@ -79,7 +90,9 @@ namespace Perspex.UnitTests
public TestServices With(
IAssetLoader assetLoader = null,
+ IFocusManager focusManager = null,
IInputManager inputManager = null,
+ Func keyboardDevice = null,
ILayoutManager layoutManager = null,
IPclPlatformWrapper platformWrapper = null,
IPlatformRenderInterface renderInterface = null,
@@ -92,7 +105,9 @@ namespace Perspex.UnitTests
{
return new TestServices(
assetLoader: assetLoader ?? AssetLoader,
+ focusManager: focusManager ?? FocusManager,
inputManager: inputManager ?? InputManager,
+ keyboardDevice: keyboardDevice ?? KeyboardDevice,
layoutManager: layoutManager ?? LayoutManager,
platformWrapper: platformWrapper ?? PlatformWrapper,
renderInterface: renderInterface ?? RenderInterface,
diff --git a/tests/Perspex.UnitTests/UnitTestApplication.cs b/tests/Perspex.UnitTests/UnitTestApplication.cs
index 04fd5daf68..3807a35fdb 100644
--- a/tests/Perspex.UnitTests/UnitTestApplication.cs
+++ b/tests/Perspex.UnitTests/UnitTestApplication.cs
@@ -40,8 +40,10 @@ namespace Perspex.UnitTests
{
PerspexLocator.CurrentMutable
.Bind().ToConstant(Services.AssetLoader)
+ .Bind().ToConstant(Services.FocusManager)
.BindToSelf(this)
.Bind().ToConstant(Services.InputManager)
+ .Bind().ToConstant(Services.KeyboardDevice?.Invoke())
.Bind().ToConstant(Services.LayoutManager)
.Bind().ToConstant(Services.PlatformWrapper)
.Bind().ToConstant(Services.RenderInterface)