From b0f7871eecd9380f81a111de001b32a4cdd821fa Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 30 May 2022 19:14:37 +0300 Subject: [PATCH 1/7] Don't allow bindings to private methods. --- .../Data/Converters/DefaultValueConverter.cs | 9 +++++- .../Data/BindingTests_Method.cs | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Markup.UnitTests/Data/BindingTests_Method.cs diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index 11d50afe93..e531cfd7be 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -33,7 +33,14 @@ namespace Avalonia.Data.Converters if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) { - return new MethodToCommandConverter(d); + if (d.Method.Attributes.HasFlag(System.Reflection.MethodAttributes.Private) == false) + { + return new MethodToCommandConverter(d); + } + else + { + return new BindingNotification(new InvalidCastException("You can't bind to private methods!"), BindingErrorType.Error); + } } if (TypeUtilities.TryConvert(targetType, value, culture, out var result)) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Method.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Method.cs new file mode 100644 index 0000000000..e613a178d5 --- /dev/null +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Method.cs @@ -0,0 +1,32 @@ +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; +using Xunit; + +namespace Avalonia.Markup.UnitTests.Data +{ + public class BindingTests_Method + { + [Fact] + public void Binding_To_Private_Methods_Shouldnt_Work() + { + var vm = new TestClass(); + var target = new Button + { + DataContext = vm, + [!Button.CommandProperty] = new Binding("MyMethod"), + }; + target.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); + + Assert.False(vm.IsSet); + } + + + class TestClass + { + public bool IsSet { get; set; } + private void MyMethod() => IsSet = true; + } + } +} From 83b5338a2855b7a0e1643c810895ebd3f765d1bc Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 30 May 2022 19:51:36 +0300 Subject: [PATCH 2/7] Check for method being Private correctly. --- src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index e531cfd7be..c4f4362537 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -33,7 +33,7 @@ namespace Avalonia.Data.Converters if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) { - if (d.Method.Attributes.HasFlag(System.Reflection.MethodAttributes.Private) == false) + if (d.Method.IsPrivate == false) { return new MethodToCommandConverter(d); } From 131c81ad22f91d9d54fdfb1412aa6947f391764b Mon Sep 17 00:00:00 2001 From: Todd Date: Tue, 31 May 2022 11:51:54 -0700 Subject: [PATCH 3/7] replace mouse and touch events to pointer event to fix issue #8200 --- .../Avalonia.Web.Blazor/AvaloniaView.razor | 10 +- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 103 +++++++++--------- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor index 3dd98f8cd3..3fe4c299cf 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor @@ -1,14 +1,12 @@ 
+ onkeyup="@OnKeyUp" + onpointerdown="@OnPointerDown" + onpointerup="@OnPointerUp" + onpointermove="@OnPointerMove"> diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index be58d9d49c..766aeb6d19 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -56,90 +56,91 @@ namespace Avalonia.Web.Blazor { return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); } - - private void OnTouchStart(TouchEventArgs e) + + private void OnTouchCancel(TouchEventArgs e) { foreach (var touch in e.ChangedTouches) { - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(touch.ClientX, touch.ClientY), + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(touch.ClientX, touch.ClientY), GetModifiers(e), touch.Identifier); } } - private void OnTouchEnd(TouchEventArgs e) + private void OnTouchMove(TouchEventArgs e) { foreach (var touch in e.ChangedTouches) { - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(touch.ClientX, touch.ClientY), + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(touch.ClientX, touch.ClientY), GetModifiers(e), touch.Identifier); } } - private void OnTouchCancel(TouchEventArgs e) + private void OnPointerMove(Microsoft.AspNetCore.Components.Web.PointerEventArgs e) { - foreach (var touch in e.ChangedTouches) + if (e.PointerType == "mouse") { - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchCancel, new Point(touch.ClientX, touch.ClientY), - GetModifiers(e), touch.Identifier); + _topLevelImpl.RawMouseEvent(RawPointerEventType.Move, new Point(e.ClientX, e.ClientY), GetModifiers(e)); } } - private void OnTouchMove(TouchEventArgs e) + private void OnPointerUp(Microsoft.AspNetCore.Components.Web.PointerEventArgs e) { - foreach (var touch in e.ChangedTouches) + if (e.PointerType == "touch") { - _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchUpdate, new Point(touch.ClientX, touch.ClientY), - GetModifiers(e), touch.Identifier); + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(e.ClientX, e.ClientY), + GetModifiers(e), e.PointerId); } - } - - private void OnMouseMove(MouseEventArgs e) - { - _topLevelImpl.RawMouseEvent(RawPointerEventType.Move, new Point(e.ClientX, e.ClientY), GetModifiers(e)); - } + else if (e.PointerType == "mouse") + { + RawPointerEventType type = default; - private void OnMouseUp(MouseEventArgs e) - { - RawPointerEventType type = default; + switch (e.Button) + { + case 0: + type = RawPointerEventType.LeftButtonUp; + break; - switch (e.Button) - { - case 0: - type = RawPointerEventType.LeftButtonUp; - break; + case 1: + type = RawPointerEventType.MiddleButtonUp; + break; - case 1: - type = RawPointerEventType.MiddleButtonUp; - break; + case 2: + type = RawPointerEventType.RightButtonUp; + break; + } - case 2: - type = RawPointerEventType.RightButtonUp; - break; + _topLevelImpl.RawMouseEvent(type, new Point(e.ClientX, e.ClientY), GetModifiers(e)); } - - _topLevelImpl.RawMouseEvent(type, new Point(e.ClientX, e.ClientY), GetModifiers(e)); } - private void OnMouseDown(MouseEventArgs e) + private void OnPointerDown(Microsoft.AspNetCore.Components.Web.PointerEventArgs e) { - RawPointerEventType type = default; - - switch (e.Button) + if (e.PointerType == "touch") + { + _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(e.ClientX, e.ClientY), + GetModifiers(e), e.PointerId); + } + else if (e.PointerType == "mouse") { - case 0: - type = RawPointerEventType.LeftButtonDown; - break; + RawPointerEventType type = default; + + switch (e.Button) + { + case 0: + type = RawPointerEventType.LeftButtonDown; + break; - case 1: - type = RawPointerEventType.MiddleButtonDown; - break; + case 1: + type = RawPointerEventType.MiddleButtonDown; + break; - case 2: - type = RawPointerEventType.RightButtonDown; - break; - } + case 2: + type = RawPointerEventType.RightButtonDown; + break; + } - _topLevelImpl.RawMouseEvent(type, new Point(e.ClientX, e.ClientY), GetModifiers(e)); + _topLevelImpl.RawMouseEvent(type, new Point(e.ClientX, e.ClientY), GetModifiers(e)); + } } private void OnWheel(WheelEventArgs e) @@ -189,7 +190,7 @@ namespace Avalonia.Web.Blazor return modifiers; } - private static RawInputModifiers GetModifiers(MouseEventArgs e) + private static RawInputModifiers GetModifiers(Microsoft.AspNetCore.Components.Web.PointerEventArgs e) { var modifiers = RawInputModifiers.None; From 53f6b62b4394a29311778d26a025af8e6931aab9 Mon Sep 17 00:00:00 2001 From: Oxc3 <61174136+Oxc3@users.noreply.github.com> Date: Tue, 31 May 2022 13:13:16 -0700 Subject: [PATCH 4/7] Update AvaloniaView.razor.cs modified pointer up, down, move to handle touch device specifically and all other devices as mouse event --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 766aeb6d19..1f411d0cee 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -56,7 +56,7 @@ namespace Avalonia.Web.Blazor { return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet"); } - + private void OnTouchCancel(TouchEventArgs e) { foreach (var touch in e.ChangedTouches) @@ -77,7 +77,7 @@ namespace Avalonia.Web.Blazor private void OnPointerMove(Microsoft.AspNetCore.Components.Web.PointerEventArgs e) { - if (e.PointerType == "mouse") + if (e.PointerType != "touch") { _topLevelImpl.RawMouseEvent(RawPointerEventType.Move, new Point(e.ClientX, e.ClientY), GetModifiers(e)); } @@ -90,7 +90,7 @@ namespace Avalonia.Web.Blazor _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchEnd, new Point(e.ClientX, e.ClientY), GetModifiers(e), e.PointerId); } - else if (e.PointerType == "mouse") + else { RawPointerEventType type = default; @@ -120,7 +120,7 @@ namespace Avalonia.Web.Blazor _topLevelImpl.RawTouchEvent(RawPointerEventType.TouchBegin, new Point(e.ClientX, e.ClientY), GetModifiers(e), e.PointerId); } - else if (e.PointerType == "mouse") + else { RawPointerEventType type = default; From 6dbb828b60042677b5fc2a92d810f2fabbf0d965 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 31 May 2022 21:58:00 -0400 Subject: [PATCH 5/7] Reset popup parent on flyout hidden --- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index d024f86b32..dfbd3f9a36 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -175,7 +175,8 @@ namespace Avalonia.Controls.Primitives IsOpen = false; Popup.IsOpen = false; - + ((ISetLogicalParent)Popup).SetParent(null); + // Ensure this isn't active _transientDisposable?.Dispose(); _transientDisposable = null; From 5ab8b06dce55f3c1294540e959182faad839bccf Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 31 May 2022 21:58:19 -0400 Subject: [PATCH 6/7] Reset popup parent on context menu target detached --- src/Avalonia.Controls/ContextMenu.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 2b122d4174..7b35e35278 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -450,6 +450,11 @@ namespace Avalonia.Controls if (sender is Control control && control.ContextMenu is ContextMenu contextMenu) { + if (contextMenu._popup?.Parent == control) + { + ((ISetLogicalParent)contextMenu._popup).SetParent(null); + } + contextMenu.Close(); } } From bc6d5ec87d125936ed4892dae020bf4de82c668e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 31 May 2022 22:17:33 -0400 Subject: [PATCH 7/7] Add tests --- .../ContextMenuTests.cs | 21 ++++++++++++++++ .../FlyoutTests.cs | 25 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index ba01f3db40..b63cbd286e 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -446,6 +446,27 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Should_Reset_Popup_Parent_On_Target_Detached() + { + using (Application()) + { + var userControl = new UserControl(); + var window = PreparedWindow(userControl); + window.Show(); + + var menu = new ContextMenu(); + userControl.ContextMenu = menu; + menu.Open(); + + var popup = Assert.IsType(menu.Parent); + Assert.NotNull(popup.Parent); + + window.Content = null; + Assert.Null(popup.Parent); + } + } + [Fact] public void Context_Menu_In_Resources_Can_Be_Shared() { diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs index c2dd8cf01a..8b77074960 100644 --- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs @@ -432,6 +432,26 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Should_Reset_Popup_Parent_On_Target_Detached() + { + using (CreateServicesWithFocus()) + { + var userControl = new UserControl(); + var window = PreparedWindow(userControl); + window.Show(); + + var flyout = new TestFlyout(); + flyout.ShowAt(userControl); + + var popup = Assert.IsType(flyout.Popup); + Assert.NotNull(popup.Parent); + + window.Content = null; + Assert.Null(popup.Parent); + } + } + [Fact] public void ContextFlyout_Can_Be_Set_In_Styles() { @@ -549,5 +569,10 @@ namespace Avalonia.Controls.UnitTests new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), KeyModifiers.None); } + + public class TestFlyout : Flyout + { + public new Popup Popup => base.Popup; + } } }