Skip to content

Commit

Permalink
fix(xbind): Support nested expression x:Bind events
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Mar 26, 2021
1 parent e0da76e commit af614a0
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@ public static bool Is(this INamedTypeSymbol symbol, INamedTypeSymbol other)
&& symbol.DeclaredAccessibility == Accessibility.Internal
);

public static IEnumerable<IMethodSymbol> GetMethods(this INamedTypeSymbol resolvedType)
public static IEnumerable<IMethodSymbol> GetMethods(this ITypeSymbol resolvedType)
=> resolvedType.GetMembers().OfType<IMethodSymbol>();

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

public static IEnumerable<IFieldSymbol> GetFields(this INamedTypeSymbol resolvedType)
public static IEnumerable<IFieldSymbol> GetFields(this ITypeSymbol resolvedType)
=> resolvedType.GetMembers().OfType<IFieldSymbol>();

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

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public NameScope(string @namespace, string className)

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

/// <summary>
/// List of action handlers for registering x:Bind events
/// </summary>
public List<BackingFieldDefinition> xBindEventsHandlers { get; } = new List<BackingFieldDefinition>();

/// <summary>
/// Lists the ElementStub builder holder variables used to pin references for implicit pinning platforms
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,12 @@ private void BuildBackingFields(IIndentedStringBuilder writer)
}
}

foreach (var xBindEventHandler in CurrentScope.xBindEventsHandlers)
{
// Create load-time subjects for ElementName references not in local scope
writer.AppendLineInvariant($"{FormatAccessibility(xBindEventHandler.Accessibility)} {GetGlobalizedTypeName(xBindEventHandler.Type)} {xBindEventHandler.Name};");
}

foreach (var remainingReference in CurrentScope.ReferencedElementNames)
{
// Create load-time subjects for ElementName references not in local scope
Expand Down Expand Up @@ -795,16 +801,10 @@ private void BuildCompiledBindingsInitializer(IndentedStringBuilder writer, stri
writer.AppendLineInvariant("Bindings.Update();");
}

for (var i = 0; i < CurrentScope.Components.Count; i++)
{
var component = CurrentScope.Components[i];

if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
{
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
}
}
BuildComponentResouceBindingUpdates(writer);
BuildxBindEventHandlerInitializers(writer);
}

writer.AppendLineInvariant(";");
}
}
Expand All @@ -820,21 +820,38 @@ private void BuildCompiledBindingsInitializerForTemplate(IIndentedStringBuilder
{
using (writer.BlockInvariant($"__fe.Loading += delegate"))
{
for (var i = 0; i < CurrentScope.Components.Count; i++)
{
var component = CurrentScope.Components[i];

if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
{
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
}
}
BuildComponentResouceBindingUpdates(writer);
BuildxBindEventHandlerInitializers(writer);
}
writer.AppendLineInvariant(";");
}
}
}

private void BuildxBindEventHandlerInitializers(IIndentedStringBuilder writer)
{
foreach (var xBindEventHandler in CurrentScope.xBindEventsHandlers)
{
writer.AppendLineInvariant($"{xBindEventHandler.Name}?.Invoke();");

// Only needs to happen once per visual tree creation
writer.AppendLineInvariant($"{xBindEventHandler.Name} = null;");
}
}

private void BuildComponentResouceBindingUpdates(IIndentedStringBuilder writer)
{
for (var i = 0; i < CurrentScope.Components.Count; i++)
{
var component = CurrentScope.Components[i];

if (HasMarkupExtensionNeedingComponent(component) && IsDependencyObject(component))
{
writer.AppendLineInvariant($"_component_{i}.UpdateResourceBindings();");
}
}
}

private void BuildComponentFields(IndentedStringBuilder writer)
{
for (var i = 0; i < CurrentScope.Components.Count; i++)
Expand Down Expand Up @@ -3069,8 +3086,40 @@ void writeEvent(string ownerPrefix)
// sanitizing member.Member.Name so that "ViewModel.SearchBreeds" becomes "ViewModel_SearchBreeds"
var sanitizedEventTarget = SanitizeResourceName(eventTarget);

(string target, string weakReference, INamedTypeSymbol sourceType) buildTargetContext()
(string target, string weakReference, IMethodSymbol targetMethod) buildTargetContext()
{
IMethodSymbol FindTargetMethodSymbol(INamedTypeSymbol sourceType)
{
if (eventTarget.Contains("."))
{
ITypeSymbol currentType = sourceType;

var parts = eventTarget.Split('.');

for (var i = 0; i < parts.Length-1; i++)
{
var next = currentType.GetAllMembersWithName(parts[i]).FirstOrDefault();

currentType = next switch
{
IFieldSymbol fs => fs.Type,
IPropertySymbol ps => ps.Type,
null => throw new InvalidOperationException($"Unable to find member {parts[i]} on type {currentType}"),
_ => throw new InvalidOperationException($"The field {next.Name} is not supported for x:Bind event binding")
};
}

var method = currentType.GetMethods().FirstOrDefault(m => m.Name == parts.Last())
?? throw new InvalidOperationException($"Failed to find {parts.Last()} on {currentType}");

return method;
}
else
{
return sourceType.GetMethods().FirstOrDefault(m => m.Name == eventTarget);
}
}

if (isInsideDataTemplate.isInside)
{
var dataTypeObject = FindMember(isInsideDataTemplate.xamlObject, "DataType", XamlConstants.XamlXmlNamespace)
Expand All @@ -3083,32 +3132,44 @@ void writeEvent(string ownerPrefix)
// Use of __rootInstance is required to get the top-level DataContext, as it may be changed
// in the current visual tree by the user.
$"(__rootInstance as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
dataTypeSymbol
FindTargetMethodSymbol(dataTypeSymbol)
);
}
else
{

return (
$"{member.Member.Name}_{sanitizedEventTarget}_That.Target as {_className.className}",
$"({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference",
FindType(_className.className)
FindTargetMethodSymbol(FindType(_className.className))
);
}
}

var targetContext = buildTargetContext();

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

//
// Generate a weak delegate, so the owner is not being held onto by the delegate. We can
// use the WeakReferenceProvider to get a self reference to avoid adding the cost of the
// creation of a WeakReference.
//
writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedEventTarget}_That = {targetContext.weakReference};");
var builderName = $"_{ownerPrefix}_{CurrentScope.xBindEventsHandlers.Count}_{member.Member.Name}_{sanitizedEventTarget}_Builder";

CurrentScope.xBindEventsHandlers.Add(
new BackingFieldDefinition("global::System.Action", builderName, Accessibility.Private)
);

using (writer.BlockInvariant($"{builderName} = () => "))
{
//
// Generate a weak delegate, so the owner is not being held onto by the delegate. We can
// use the WeakReferenceProvider to get a self reference to avoid adding the cost of the
// creation of a WeakReference.
//
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($"{closureName}.{member.Member.Name} += ({parms}) => ({targetContext.target})?.{eventTarget}({xBindParams});");
writer.AppendLineInvariant($";");
}
else
{
Expand All @@ -3123,7 +3184,7 @@ void writeEvent(string ownerPrefix)
//
writer.AppendLineInvariant($"var {member.Member.Name}_{sanitizedMemberValue}_That = ({eventSource} as global::Uno.UI.DataBinding.IWeakReferenceProvider).WeakReference;");

writer.AppendLineInvariant($"{closureName}.{member.Member.Name} += ({parms}) => ({member.Member.Name}_{sanitizedMemberValue}_That.Target as {_className.className})?.{member.Value}({parms});");
writer.AppendLineInvariant($"/* second level */ {closureName}.{member.Member.Name} += ({parms}) => ({member.Member.Name}_{sanitizedMemberValue}_That.Target as {_className.className})?.{member.Value}({parms});");
}
}
else
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Page x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Event_Nested"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid>
<CheckBox x:Name="myCheckBox"
x:FieldModifier="public"
Checked="{x:Bind ViewModel.OnCheckedRaised}"
Unchecked="{x:Bind ViewModel.OnUncheckedRaised}"/>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Binding_Event_Nested : Page
{
public Binding_Event_Nested()
{
this.InitializeComponent();
}

public Binding_Event_Nested_ViewModel ViewModel { get; } = new Binding_Event_Nested_ViewModel();
}

public class Binding_Event_Nested_ViewModel
{
public int CheckedRaised { get; private set; }
public int UncheckedRaised { get; private set; }

public void OnCheckedRaised()
{
CheckedRaised++;
}

public void OnUncheckedRaised(object sender, RoutedEventArgs args)
{
UncheckedRaised++;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Page x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_Event_Nested_DataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<ContentControl x:Name="root"
x:FieldModifier="public">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="local:Binding_Event_Nested_DataTemplate_Model">
<Grid>
<CheckBox x:Name="myCheckBox"
x:FieldModifier="public"
Checked="{x:Bind ViewModel.OnCheckedRaised}"
Unchecked="{x:Bind ViewModel.OnUncheckedRaised}"/>
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Binding_Event_Nested_DataTemplate : Page
{
public Binding_Event_Nested_DataTemplate()
{
this.InitializeComponent();
}
}

public class Binding_Event_Nested_DataTemplate_Model
{
public Binding_Event_Nested_DataTemplate_ViewModel ViewModel { get; } = new Binding_Event_Nested_DataTemplate_ViewModel();
}

public class Binding_Event_Nested_DataTemplate_ViewModel
{
public int CheckedRaised { get; private set; }
public int UncheckedRaised { get; private set; }

internal void OnCheckedRaised()
{
CheckedRaised++;
}

internal void OnUncheckedRaised(object sender, RoutedEventArgs args)
{
UncheckedRaised++;
}
}
}
Loading

0 comments on commit af614a0

Please sign in to comment.