From 763775fa824cc6263e9c397447651db9aec5e761 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 12 Oct 2017 23:26:31 +0200 Subject: [PATCH 1/4] 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() { From 3402456284313264e31cf8485b3c762ff6b3d28a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 13 Oct 2017 14:38:48 +0200 Subject: [PATCH 2/4] Always raise logical tree attach/detach events. Previously, if the user overrode `OnAttachedToLogicalTree` or `OnDetachedFromLogicalTree` and forgot to call the base implementation, the events wouldn't be raised. It's very important that these events get raised so moved the code to raise them to `OnAttachedToLogicalTreeCore`/`OnDetachedFromLogicalTreeCore`. --- src/Avalonia.Controls/Control.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index f3705c9127..050315af7c 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -656,7 +656,6 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { - AttachedToLogicalTree?.Invoke(this, e); } /// @@ -665,7 +664,6 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { - DetachedFromLogicalTree?.Invoke(this, e); } /// @@ -844,6 +842,7 @@ namespace Avalonia.Controls InitializeStylesIfNeeded(true); OnAttachedToLogicalTree(e); + AttachedToLogicalTree?.Invoke(this, e); } foreach (var child in LogicalChildren.OfType()) @@ -864,6 +863,7 @@ namespace Avalonia.Controls _isAttachedToLogicalTree = false; _styleDetach.OnNext(this); OnDetachedFromLogicalTree(e); + DetachedFromLogicalTree?.Invoke(this, e); foreach (var child in LogicalChildren.OfType()) { From 2c61d17bc3efc967716f4620dcb4dae9189b17d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 13 Oct 2017 15:45:25 +0200 Subject: [PATCH 3/4] Always raise visual tree attach/detach events. Previously, if the user overrode `OnAttachedToVisualTree` or `OnDetachedFromVisualTree` and forgot to call the base implementation, the events wouldn't be raised. It's very important that these events get raised so moved the code to raise them to `OnAttachedToVisualTreeCore`/`OnDetachedFromVisualTreeCore`. --- src/Avalonia.Visuals/Visual.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index cfe6bce7e0..bc65d4f69f 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -329,6 +329,7 @@ namespace Avalonia } OnAttachedToVisualTree(e); + AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); if (VisualChildren != null) @@ -357,6 +358,7 @@ namespace Avalonia } OnDetachedFromVisualTree(e); + DetachedFromVisualTree?.Invoke(this, e); e.Root?.Renderer?.AddDirty(this); if (VisualChildren != null) @@ -374,7 +376,6 @@ namespace Avalonia /// The event args. protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - AttachedToVisualTree?.Invoke(this, e); } /// @@ -383,7 +384,6 @@ namespace Avalonia /// The event args. protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { - DetachedFromVisualTree?.Invoke(this, e); } /// From aa2980b8c833f50b2b895a93802d7b3b256d4af3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 13 Oct 2017 19:10:09 +0200 Subject: [PATCH 4/4] Don't return detached controls in HitTest. If a control has been detached from the visual tree but the scene hasn't yet been updated, don't return the control as part of a hit-test. --- src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index a85fd36763..a4af106a73 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph } } - if (node.HitTest(p)) + if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree) { yield return node.Visual; }