From 763775fa824cc6263e9c397447651db9aec5e761 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Oct 2017 23:26:31 +0200 Subject: [PATCH] Clear mouse capture when control removed. When a captured control is removed from the visual tree, clear the mouse capture. Note that our capture logic needs to be improved - it shouldn't be possible to capture a non-visible or non-enabled control. This doesn't address that but it does fix the crash in #1203. Fixes #1203 --- src/Avalonia.Input/MouseDevice.cs | 22 +++++++++++++++++-- .../MouseDeviceTests.cs | 19 +++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 875a5ebaee..50cdb5945c 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -20,6 +20,8 @@ namespace Avalonia.Input private int _clickCount; private Rect _lastClickRect; private uint _lastClickTime; + private IInputElement _captured; + private IDisposable _capturedSubscription; /// /// Gets the control that is currently capturing by the mouse, if any. @@ -31,8 +33,23 @@ namespace Avalonia.Input /// public IInputElement Captured { - get; - protected set; + get => _captured; + protected set + { + _capturedSubscription?.Dispose(); + _capturedSubscription = null; + + if (value != null) + { + _capturedSubscription = Observable.FromEventPattern( + x => value.DetachedFromVisualTree += x, + x => value.DetachedFromVisualTree -= x) + .Take(1) + .Subscribe(_ => Captured = null); + } + + _captured = value; + } } /// @@ -55,6 +72,7 @@ namespace Avalonia.Input /// public virtual void Capture(IInputElement control) { + // TODO: Check visibility and enabled state before setting capture. Captured = control; } diff --git a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs index 4a834fd55d..6aca69b88f 100644 --- a/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Input.Raw; -using Avalonia.Layout; using Avalonia.Rendering; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -12,6 +11,24 @@ namespace Avalonia.Input.UnitTests { public class MouseDeviceTests { + [Fact] + public void Capture_Is_Cleared_When_Control_Removed() + { + Canvas control; + var root = new TestRoot + { + Child = control = new Canvas(), + }; + var target = new MouseDevice(); + + target.Capture(control); + Assert.Same(control, target.Captured); + + root.Child = null; + + Assert.Null(target.Captured); + } + [Fact] public void MouseMove_Should_Update_PointerOver() {