Skip to content

Commit af614a0

Browse files
committed
fix(xbind): Support nested expression x:Bind events
1 parent e0da76e commit af614a0

File tree

8 files changed

+316
-34
lines changed

8 files changed

+316
-34
lines changed

src/SourceGenerators/Uno.UI.SourceGenerators/Helpers/SymbolExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,16 @@ public static bool IsLocallyPublic(this ISymbol symbol, IModuleSymbol currentSym
144144
&& symbol.DeclaredAccessibility == Accessibility.Internal
145145
);
146146

147-
public static IEnumerable<IMethodSymbol> GetMethods(this INamedTypeSymbol resolvedType)
147+
public static IEnumerable<IMethodSymbol> GetMethods(this ITypeSymbol resolvedType)
148148
=> resolvedType.GetMembers().OfType<IMethodSymbol>();
149149

150-
public static IEnumerable<IMethodSymbol> GetMethodsWithName(this INamedTypeSymbol resolvedType, string name)
150+
public static IEnumerable<IMethodSymbol> GetMethodsWithName(this ITypeSymbol resolvedType, string name)
151151
=> resolvedType.GetMembers(name).OfType<IMethodSymbol>();
152152

153-
public static IEnumerable<IFieldSymbol> GetFields(this INamedTypeSymbol resolvedType)
153+
public static IEnumerable<IFieldSymbol> GetFields(this ITypeSymbol resolvedType)
154154
=> resolvedType.GetMembers().OfType<IFieldSymbol>();
155155

156-
public static IEnumerable<IFieldSymbol> GetFieldsWithName(this INamedTypeSymbol resolvedType, string name)
156+
public static IEnumerable<IFieldSymbol> GetFieldsWithName(this ITypeSymbol resolvedType, string name)
157157
=> resolvedType.GetMembers(name).OfType<IFieldSymbol>();
158158

159159
/// <summary>

src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/NameScope.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ public NameScope(string @namespace, string className)
2020

2121
public List<BackingFieldDefinition> BackingFields { get; } = new List<BackingFieldDefinition>();
2222

23+
/// <summary>
24+
/// List of action handlers for registering x:Bind events
25+
/// </summary>
26+
public List<BackingFieldDefinition> xBindEventsHandlers { get; } = new List<BackingFieldDefinition>();
27+
2328
/// <summary>
2429
/// Lists the ElementStub builder holder variables used to pin references for implicit pinning platforms
2530
/// </summary>

src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,12 @@ private void BuildBackingFields(IIndentedStringBuilder writer)
674674
}
675675
}
676676

677+
foreach (var xBindEventHandler in CurrentScope.xBindEventsHandlers)
678+
{
679+
// Create load-time subjects for ElementName references not in local scope
680+
writer.AppendLineInvariant($"{FormatAccessibility(xBindEventHandler.Accessibility)} {GetGlobalizedTypeName(xBindEventHandler.Type)} {xBindEventHandler.Name};");
681+
}
682+
677683
foreach (var remainingReference in CurrentScope.ReferencedElementNames)
678684
{
679685
// Create load-time subjects for ElementName references not in local scope
@@ -795,16 +801,10 @@ private void BuildCompiledBindingsInitializer(IndentedStringBuilder writer, stri
795801
writer.AppendLineInvariant("Bindings.Update();");
796802
}
797803

798-
for (var i = 0; i < CurrentScope.Components.Count; i++)
799-
{
800-
var component = CurrentScope.Components[i];
801-
802-
if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
803-
{
804-
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
805-
}
806-
}
804+
BuildComponentResouceBindingUpdates(writer);
805+
BuildxBindEventHandlerInitializers(writer);
807806
}
807+
808808
writer.AppendLineInvariant(";");
809809
}
810810
}
@@ -820,21 +820,38 @@ private void BuildCompiledBindingsInitializerForTemplate(IIndentedStringBuilder
820820
{
821821
using (writer.BlockInvariant($"__fe.Loading += delegate"))
822822
{
823-
for (var i = 0; i < CurrentScope.Components.Count; i++)
824-
{
825-
var component = CurrentScope.Components[i];
826-
827-
if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
828-
{
829-
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
830-
}
831-
}
823+
BuildComponentResouceBindingUpdates(writer);
824+
BuildxBindEventHandlerInitializers(writer);
832825
}
833826
writer.AppendLineInvariant(";");
834827
}
835828
}
836829
}
837830

831+
private void BuildxBindEventHandlerInitializers(IIndentedStringBuilder writer)
832+
{
833+
foreach (var xBindEventHandler in CurrentScope.xBindEventsHandlers)
834+
{
835+
writer.AppendLineInvariant($"{xBindEventHandler.Name}?.Invoke();");
836+
837+
// Only needs to happen once per visual tree creation
838+
writer.AppendLineInvariant($"{xBindEventHandler.Name} = null;");
839+
}
840+
}
841+
842+
private void BuildComponentResouceBindingUpdates(IIndentedStringBuilder writer)
843+
{
844+
for (var i = 0; i < CurrentScope.Components.Count; i++)
845+
{
846+
var component = CurrentScope.Components[i];
847+
848+
if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
849+
{
850+
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
851+
}
852+
}
853+
}
854+
838855
private void BuildComponentFields(IndentedStringBuilder writer)
839856
{
840857
for (var i = 0; i < CurrentScope.Components.Count; i++)
@@ -3069,8 +3086,40 @@ void writeEvent(string ownerPrefix)
30693086
// sanitizing member.Member.Name so that "ViewModel.SearchBreeds" becomes "ViewModel_SearchBreeds"
30703087
var sanitizedEventTarget = SanitizeResourceName(eventTarget);
30713088

3072-
(string target, string weakReference, INamedTypeSymbol sourceType) buildTargetContext()
3089+
(string target, string weakReference, IMethodSymbol targetMethod) buildTargetContext()
30733090
{
3091+
IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol sourceType)
3092+
{
3093+
if (eventTarget.Contains("."))
3094+
{
3095+
ITypeSymbol currentType = sourceType;
3096+
3097+
var parts = eventTarget.Split('.');
3098+
3099+
for (var i = 0; i < parts.Length-1; i++)
3100+
{
3101+
var next = currentType.GetAllMembersWithName(parts[i]).FirstOrDefault();
3102+
3103+
currentType = next switch
3104+
{
3105+
IFieldSymbol fs => fs.Type,
3106+
IPropertySymbol ps => ps.Type,
3107+
null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"),
3108+
_ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding")
3109+
};
3110+
}
3111+
3112+
var method = currentType.GetMethods().FirstOrDefault(m => m.Name == parts.Last())
3113+
?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}");
3114+
3115+
return method;
3116+
}
3117+
else
3118+
{
3119+
return sourceType.GetMethods().FirstOrDefault(m => m.Name == eventTarget);
3120+
}
3121+
}
3122+
30743123
if (isInsideDataTemplate.isInside)
30753124
{
30763125
var dataTypeObject = FindMember(isInsideDataTemplate.xamlObject, "DataType", XamlConstants.XamlXmlNamespace)
@@ -3083,32 +3132,44 @@ void writeEvent(string ownerPrefix)
30833132
// Use of __rootInstance is required to get the top-level DataContext, as it may be changed
30843133
// in the current visual tree by the user.
30853134
$"(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
3086-
dataTypeSymbol
3135+
FindTargetMethodSymbol(dataTypeSymbol)
30873136
);
30883137
}
30893138
else
30903139
{
3140+
30913141
return (
30923142
$"{member.Member.Name}_{sanitizedEventTarget}_That.Target as {_className.className}",
30933143
$"({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
3094-
FindType(_className.className)
3144+
FindTargetMethodSymbol(FindType(_className.className))
30953145
);
30963146
}
30973147
}
30983148

30993149
var targetContext = buildTargetContext();
31003150

3101-
var targetMethodHasParamters = targetContext.sourceType.GetMethods().FirstOrDefault(m => m.Name == eventTarget)?.Parameters.Any() ?? false;
3151+
var targetMethodHasParamters = targetContext.targetMethod?.Parameters.Any() ?? false;
31023152
var xBindParams = targetMethodHasParamters ? parms : "";
31033153

3104-
//
3105-
// Generate a weak delegate, so the owner is not being held onto by the delegate. We can
3106-
// use the WeakReferenceProvider to get a self reference to avoid adding the cost of the
3107-
// creation of a WeakReference.
3108-
//
3109-
writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedEventTarget}_That = {targetContext.weakReference};");
3154+
var builderName = $"_{ownerPrefix}_{CurrentScope.xBindEventsHandlers.Count}_{member.Member.Name}_{sanitizedEventTarget}_Builder";
3155+
3156+
CurrentScope.xBindEventsHandlers.Add(
3157+
new BackingFieldDefinition("global::System.Action", builderName, Accessibility.Private)
3158+
);
3159+
3160+
using (writer.BlockInvariant($"{builderName} = () => "))
3161+
{
3162+
//
3163+
// Generate a weak delegate, so the owner is not being held onto by the delegate. We can
3164+
// use the WeakReferenceProvider to get a self reference to avoid adding the cost of the
3165+
// creation of a WeakReference.
3166+
//
3167+
writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedEventTarget}_That = {targetContext.weakReference};");
3168+
3169+
writer.AppendLineInvariant($"/* first level targetMethod:{targetContext.targetMethod} */ {closureName}.{member.Member.Name} += ({parms}) => ({targetContext.target})?.{eventTarget}({xBindParams});");
3170+
}
31103171

3111-
writer.AppendLineInvariant($"{closureName}.{member.Member.Name} += ({parms}) => ({targetContext.target})?.{eventTarget}({xBindParams});");
3172+
writer.AppendLineInvariant($";");
31123173
}
31133174
else
31143175
{
@@ -3123,7 +3184,7 @@ void writeEvent(string ownerPrefix)
31233184
//
31243185
writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedMemberValue}_That = ({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference;");
31253186

3126-
writer.AppendLineInvariant($"{closureName}.{member.Member.Name} += ({parms}) => ({member.Member.Name}_{sanitizedMemberValue}_That.Target as {_className.className})?.{member.Value}({parms});");
3187+
writer.AppendLineInvariant($"/* second level */ {closureName}.{member.Member.Name} += ({parms}) => ({member.Member.Name}_{sanitizedMemberValue}_That.Target as {_className.className})?.{member.Value}({parms});");
31273188
}
31283189
}
31293190
else
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Page x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Event_Nested"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
mc:Ignorable="d">
8+
9+
<Grid>
10+
<CheckBox x:Name="myCheckBox"
11+
x:FieldModifier="public"
12+
Checked="{x:Bind ViewModel.OnCheckedRaised}"
13+
Unchecked="{x:Bind ViewModel.OnUncheckedRaised}"/>
14+
</Grid>
15+
</Page>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Runtime.InteropServices.WindowsRuntime;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.UI.Xaml;
10+
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Controls.Primitives;
12+
using Windows.UI.Xaml.Data;
13+
using Windows.UI.Xaml.Input;
14+
using Windows.UI.Xaml.Media;
15+
using Windows.UI.Xaml.Navigation;
16+
17+
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
18+
19+
namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
20+
{
21+
/// <summary>
22+
/// An empty page that can be used on its own or navigated to within a Frame.
23+
/// </summary>
24+
public sealed partial class Binding_Event_Nested : Page
25+
{
26+
public Binding_Event_Nested()
27+
{
28+
this.InitializeComponent();
29+
}
30+
31+
public Binding_Event_Nested_ViewModel ViewModel { get; } = new Binding_Event_Nested_ViewModel();
32+
}
33+
34+
public class Binding_Event_Nested_ViewModel
35+
{
36+
public int CheckedRaised { get; private set; }
37+
public int UncheckedRaised { get; private set; }
38+
39+
public void OnCheckedRaised()
40+
{
41+
CheckedRaised++;
42+
}
43+
44+
public void OnUncheckedRaised(object sender, RoutedEventArgs args)
45+
{
46+
UncheckedRaised++;
47+
}
48+
}
49+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Page x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Event_Nested_DataTemplate"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7+
mc:Ignorable="d">
8+
9+
<ContentControl x:Name="root"
10+
x:FieldModifier="public">
11+
<ContentControl.ContentTemplate>
12+
<DataTemplate x:DataType="local:Binding_Event_Nested_DataTemplate_Model">
13+
<Grid>
14+
<CheckBox x:Name="myCheckBox"
15+
x:FieldModifier="public"
16+
Checked="{x:Bind ViewModel.OnCheckedRaised}"
17+
Unchecked="{x:Bind ViewModel.OnUncheckedRaised}"/>
18+
</Grid>
19+
</DataTemplate>
20+
</ContentControl.ContentTemplate>
21+
</ContentControl>
22+
</Page>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Runtime.InteropServices.WindowsRuntime;
7+
using Windows.Foundation;
8+
using Windows.Foundation.Collections;
9+
using Windows.UI.Xaml;
10+
using Windows.UI.Xaml.Controls;
11+
using Windows.UI.Xaml.Controls.Primitives;
12+
using Windows.UI.Xaml.Data;
13+
using Windows.UI.Xaml.Input;
14+
using Windows.UI.Xaml.Media;
15+
using Windows.UI.Xaml.Navigation;
16+
17+
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
18+
19+
namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
20+
{
21+
/// <summary>
22+
/// An empty page that can be used on its own or navigated to within a Frame.
23+
/// </summary>
24+
public sealed partial class Binding_Event_Nested_DataTemplate : Page
25+
{
26+
public Binding_Event_Nested_DataTemplate()
27+
{
28+
this.InitializeComponent();
29+
}
30+
}
31+
32+
public class Binding_Event_Nested_DataTemplate_Model
33+
{
34+
public Binding_Event_Nested_DataTemplate_ViewModel ViewModel { get; } = new Binding_Event_Nested_DataTemplate_ViewModel();
35+
}
36+
37+
public class Binding_Event_Nested_DataTemplate_ViewModel
38+
{
39+
public int CheckedRaised { get; private set; }
40+
public int UncheckedRaised { get; private set; }
41+
42+
internal void OnCheckedRaised()
43+
{
44+
CheckedRaised++;
45+
}
46+
47+
internal void OnUncheckedRaised(object sender, RoutedEventArgs args)
48+
{
49+
UncheckedRaised++;
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)