Răsfoiți Sursa

Merge branch 'leaks-again' of https://github.com/Perspex/Perspex into fix_carousel_presenter

Dan Walmsley 9 ani în urmă
părinte
comite
0b2be540d5

+ 17 - 0
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@@ -377,6 +377,23 @@ namespace Perspex.Controls.Primitives
             }
         }
 
+        /// <inheritdoc/>
+        protected override void OnContainersDematerialized(ItemContainerEventArgs e)
+        {
+            base.OnContainersDematerialized(e);
+
+            var panel = (InputElement)Presenter.Panel;
+
+            foreach (var container in e.Containers)
+            {
+                if (KeyboardNavigation.GetTabOnceActiveElement(panel) == container.ContainerControl)
+                {
+                    KeyboardNavigation.SetTabOnceActiveElement(panel, null);
+                    break;
+                }
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnDataContextChanging()
         {

+ 7 - 4
src/Perspex.Input/FocusManager.cs

@@ -39,7 +39,7 @@ namespace Perspex.Input
         /// <summary>
         /// Gets the currently focused <see cref="IInputElement"/>.
         /// </summary>
-        public IInputElement Current => KeyboardDevice.Instance.FocusedElement;
+        public IInputElement Current => KeyboardDevice.Instance?.FocusedElement;
 
         /// <summary>
         /// 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))
                 {

+ 5 - 5
src/Perspex.Input/Gestures.cs

@@ -18,7 +18,7 @@ namespace Perspex.Input
             RoutingStrategies.Bubble,
             typeof(Gestures));
 
-        private static IInteractive s_lastPress;
+        private static WeakReference s_lastPress;
 
         static Gestures()
         {
@@ -34,9 +34,9 @@ namespace Perspex.Input
 
                 if (e.ClickCount <= 1)
                 {
-                    s_lastPress = e.Source;
+                    s_lastPress = new WeakReference(e.Source);
                 }
-                else if (e.ClickCount == 2 && s_lastPress == e.Source)
+                else if (s_lastPress?.IsAlive == true && e.ClickCount == 2 && s_lastPress.Target == e.Source)
                 {
                     e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
                 }
@@ -49,9 +49,9 @@ namespace Perspex.Input
             {
                 var e = (PointerReleasedEventArgs)ev;
 
-                if (s_lastPress == e.Source)
+                if (s_lastPress?.IsAlive == true && s_lastPress.Target == e.Source)
                 {
-                    s_lastPress.RaiseEvent(new RoutedEventArgs(TappedEvent));
+                    ((IInteractive)s_lastPress.Target).RaiseEvent(new RoutedEventArgs(TappedEvent));
                 }
             }
         }

+ 4 - 4
src/Perspex.Input/InputElement.cs

@@ -376,9 +376,9 @@ namespace Perspex.Input
         }
 
         /// <inheritdoc/>
-        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
         }
 
         /// <inheritdoc/>
-        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        protected override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
-            base.OnAttachedToVisualTree(e);
+            base.OnAttachedToVisualTreeCore(e);
             UpdateIsEnabledCore();
         }
 

+ 0 - 24
tests/Perspex.Controls.UnitTests/ListBoxTests.cs

@@ -104,30 +104,6 @@ namespace Perspex.Controls.UnitTests
                 dataContexts);
         }
 
-        [Fact]
-        public void Setting_SelectedItem_Should_Set_Panel_Keyboard_Navigation()
-        {
-            var target = new ListBox
-            {
-                Template = new FuncControlTemplate(CreateListBoxTemplate),
-                Items = new[] { "Foo", "Bar", "Baz " },
-            };
-
-            ApplyTemplate(target);
-
-            target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
-            {
-                RoutedEvent = InputElement.PointerPressedEvent,
-                MouseButton = MouseButton.Left,
-            });
-
-            var panel = target.Presenter.Panel;
-
-            Assert.Equal(
-                KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
-                panel.Children[1]);
-        }
-
         private Control CreateListBoxTemplate(ITemplatedControl parent)
         {
             return new ScrollViewer

+ 52 - 0
tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@@ -8,6 +8,7 @@ using Perspex.Collections;
 using Perspex.Controls.Presenters;
 using Perspex.Controls.Primitives;
 using Perspex.Controls.Templates;
+using Perspex.Input;
 using Perspex.Interactivity;
 using Perspex.Markup.Xaml.Data;
 using Perspex.UnitTests;
@@ -604,6 +605,57 @@ namespace Perspex.Controls.UnitTests.Primitives
             Assert.Equal(0, root.SelectedIndex);
         }
 
+        [Fact]
+        public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement()
+        {
+            var target = new ListBox
+            {
+                Template = Template(),
+                Items = new[] { "Foo", "Bar", "Baz " },
+            };
+
+            target.ApplyTemplate();
+            target.Presenter.ApplyTemplate();
+
+            target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
+            {
+                RoutedEvent = InputElement.PointerPressedEvent,
+                MouseButton = MouseButton.Left,
+            });
+
+            var panel = target.Presenter.Panel;
+
+            Assert.Equal(
+                KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel),
+                panel.Children[1]);
+        }
+
+        [Fact]
+        public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement()
+        {
+            var items = new ObservableCollection<string>(new[] { "Foo", "Bar", "Baz " });
+
+            var target = new ListBox
+            {
+                Template = Template(),
+                Items = items,
+            };
+
+            target.ApplyTemplate();
+            target.Presenter.ApplyTemplate();
+
+            target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
+            {
+                RoutedEvent = InputElement.PointerPressedEvent,
+                MouseButton = MouseButton.Left,
+            });
+
+            items.RemoveAt(1);
+
+            var panel = target.Presenter.Panel;
+
+            Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel));
+        }
 
         private FuncControlTemplate Template()
         {

+ 49 - 0
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);
+            }
+        }
+    }
+}

+ 5 - 0
tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj

@@ -60,6 +60,7 @@
     </Reference>
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="InputElement_Focus.cs" />
     <Compile Include="InputElement_HitTesting.cs" />
     <Compile Include="KeyboardNavigationTests_Arrows.cs" />
     <Compile Include="KeyboardNavigationTests_Tab.cs" />
@@ -103,6 +104,10 @@
       <Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
       <Name>Perspex.Styling</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Perspex.UnitTests\Perspex.UnitTests.csproj">
+      <Project>{88060192-33D5-4932-B0F9-8BD2763E857D}</Project>
+      <Name>Perspex.UnitTests</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

+ 2 - 1
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();
 

+ 15 - 0
tests/Perspex.UnitTests/TestServices.cs

@@ -36,12 +36,19 @@ namespace Perspex.UnitTests
         public static readonly TestServices MockThreadingInterface = new TestServices(
             threadingInterface: Mock.Of<IPlatformThreadingInterface>(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<IKeyboardDevice> 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<IKeyboardDevice> 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<IKeyboardDevice> 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,

+ 2 - 0
tests/Perspex.UnitTests/UnitTestApplication.cs

@@ -40,8 +40,10 @@ namespace Perspex.UnitTests
         {
             PerspexLocator.CurrentMutable
                 .Bind<IAssetLoader>().ToConstant(Services.AssetLoader)
+                .Bind<IFocusManager>().ToConstant(Services.FocusManager)
                 .BindToSelf<IGlobalStyles>(this)
                 .Bind<IInputManager>().ToConstant(Services.InputManager)
+                .Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
                 .Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
                 .Bind<IPclPlatformWrapper>().ToConstant(Services.PlatformWrapper)
                 .Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)