From 1e30a7dad50dee8c9bd9b2d36bdb55aaaabf9cec Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Fri, 11 Mar 2022 16:56:56 -0500 Subject: [PATCH] feat: Add support x:Bind to static event handlers --- .../XamlGenerator/XamlFileGenerator.cs | 36 +++++++++++-- .../Controls/Binding_Static_Event.xaml | 15 ++++++ .../Controls/Binding_Static_Event.xaml.cs | 49 ++++++++++++++++++ .../Binding_Static_Event_DataTemplate.xaml | 22 ++++++++ .../Binding_Static_Event_DataTemplate.xaml.cs | 50 +++++++++++++++++++ .../xBindTests/Given_xBind_Binding.cs | 50 +++++++++++++++++++ 6 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml.cs create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml create mode 100644 src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml.cs diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index aa456955301a..19e9b91ce309 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -838,7 +838,7 @@ private void BuildBackingFields(IIndentedStringBuilder writer) } } - private static readonly char[] ResourceInvalidCharacters = new[] { '.', '-' }; + private static readonly char[] ResourceInvalidCharacters = new[] { '.', '-', ':' }; private static string? SanitizeResourceName(string? name) { @@ -3535,6 +3535,17 @@ void writeEvent(string? ownerPrefix) CurrentScope.XBindExpressions.Add(bind); var eventTarget = XBindExpressionParser.RestoreSinglePath(bind.Members.First().Value?.ToString()); + + if(eventTarget == null) + { + throw new InvalidOperationException("x:Bind event path cannot by empty"); + } + + var parts = eventTarget.Split('.').ToList(); + var isStaticTarget = parts.FirstOrDefault()?.Contains(":") ?? false; + + eventTarget = RewriteNamespaces(eventTarget); + // x:Bind to second-level method generates invalid code // sanitizing member.Member.Name so that "ViewModel.SearchBreeds" becomes "ViewModel_SearchBreeds" var sanitizedEventTarget = SanitizeResourceName(eventTarget); @@ -3547,11 +3558,17 @@ IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType) { ITypeSymbol? currentType = sourceType; - var parts = eventTarget.Split('.'); + if (isStaticTarget) + { + // First part is a type for static method binding and should + // overide the original source type + currentType = GetType(RewriteNamespaces(parts[0])); + parts.RemoveAt(0); + } - for (var i = 0; i < parts.Length - 1; i++) + for (var i = 0; i < parts.Count - 1; i++) { - var next = currentType.GetAllMembersWithName(parts[i]).FirstOrDefault(); + var next = currentType.GetAllMembersWithName(RewriteNamespaces(parts[i])).FirstOrDefault(); currentType = next switch { @@ -3623,7 +3640,16 @@ IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol? sourceType) // writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedEventTarget}_That = {targetContext.weakReference};"); - writer.AppendLineInvariant($"/* first level targetMethod:{targetContext.targetMethod} */ {closureName}.{member.Member.Name} += ({parms}) => ({targetContext.target})?.{eventTarget}({xBindParams});"); + writer.AppendLineInvariant($"/* first level targetMethod:{targetContext.targetMethod} */ {closureName}.{member.Member.Name} += ({parms}) => "); + + if (isStaticTarget) + { + writer.AppendLineInvariant($"{eventTarget}({xBindParams});"); + } + else + { + writer.AppendLineInvariant($"({targetContext.target})?.{eventTarget}({xBindParams});"); + } } writer.AppendLineInvariant($";"); diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml new file mode 100644 index 000000000000..243b3767fe5b --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml.cs new file mode 100644 index 000000000000..83f1b191e726 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event.xaml.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class Binding_Static_Event : Page + { + public Binding_Static_Event() + { + this.InitializeComponent(); + } + + } + + public static class Binding_Static_Event_Class + { + + public static int CheckedRaised { get; private set; } + public static int UncheckedRaised { get; private set; } + + public static void OnCheckedRaised() + { + CheckedRaised++; + } + + public static void OnUncheckedRaised(object sender, RoutedEventArgs args) + { + UncheckedRaised++; + } + } +} diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml new file mode 100644 index 000000000000..729755041de6 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml.cs new file mode 100644 index 000000000000..646f14c9f791 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Controls/Binding_Static_Event_DataTemplate.xaml.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class Binding_Static_Event_DataTemplate : Page + { + public Binding_Static_Event_DataTemplate() + { + this.InitializeComponent(); + } + + } + + public class Binding_Static_Event_DataTemplate_Model { } + + public static class Binding_Static_Event_DataTemplate_Model_Class + { + public static int CheckedRaised { get; private set; } + public static int UncheckedRaised { get; private set; } + + internal static void OnCheckedRaised() + { + CheckedRaised++; + } + + internal static void OnUncheckedRaised(object sender, RoutedEventArgs args) + { + UncheckedRaised++; + } + } +} diff --git a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs index e0de8b3f804f..f3f0ead15fba 100644 --- a/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs +++ b/src/Uno.UI.Tests/Windows_UI_Xaml_Data/xBindTests/Given_xBind_Binding.cs @@ -736,6 +736,29 @@ public void When_Event() Assert.AreEqual(1, SUT.UncheckedRaised); } + [TestMethod] + public void When_Static_Event() + { + var SUT = new Binding_Static_Event(); + + SUT.ForceLoaded(); + + var checkBox = SUT.FindName("myCheckBox") as CheckBox; + + Assert.AreEqual(0, Binding_Static_Event_Class.CheckedRaised); + Assert.AreEqual(0, Binding_Static_Event_Class.UncheckedRaised); + + checkBox.IsChecked = true; + + Assert.AreEqual(1, Binding_Static_Event_Class.CheckedRaised); + Assert.AreEqual(0, Binding_Static_Event_Class.UncheckedRaised); + + checkBox.IsChecked = false; + + Assert.AreEqual(1, Binding_Static_Event_Class.CheckedRaised); + Assert.AreEqual(1, Binding_Static_Event_Class.UncheckedRaised); + } + [TestMethod] public void When_Event_Nested() { @@ -786,6 +809,33 @@ public void When_Event_DataTemplate() Assert.AreEqual(1, dc.UncheckedRaised); } + + [TestMethod] + public void When_Static_Event_DataTemplate() + { + var SUT = new Binding_Static_Event_DataTemplate(); + + SUT.ForceLoaded(); + + var root = SUT.FindName("root") as FrameworkElement; + root.DataContext = new object(); + + var checkBox = SUT.FindName("myCheckBox") as CheckBox; + + Assert.AreEqual(0, Binding_Static_Event_DataTemplate_Model_Class.CheckedRaised); + Assert.AreEqual(0, Binding_Static_Event_DataTemplate_Model_Class.UncheckedRaised); + + checkBox.IsChecked = true; + + Assert.AreEqual(1, Binding_Static_Event_DataTemplate_Model_Class.CheckedRaised); + Assert.AreEqual(0, Binding_Static_Event_DataTemplate_Model_Class.UncheckedRaised); + + checkBox.IsChecked = false; + + Assert.AreEqual(1, Binding_Static_Event_DataTemplate_Model_Class.CheckedRaised); + Assert.AreEqual(1, Binding_Static_Event_DataTemplate_Model_Class.UncheckedRaised); + } + [TestMethod] public void When_Event_Nested_DataTemplate() {