Skip to content

Commit

Permalink
feat(xBind): Enhanced support for x:Bind casting expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Oct 20, 2022
1 parent 46c1796 commit 4521b63
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 24 deletions.
17 changes: 15 additions & 2 deletions doc/articles/features/windows-ui-xaml-xbind.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,21 @@ Uno supports the [`x:Bind`](https://docs.microsoft.com/en-us/windows/uwp/xaml-pl
```

- Type casts
```xml
<TextBox FontFamily="{x:Bind (FontFamily)MyComboBox.SelectedValue}" />
- ```xml
<TextBox FontFamily="{x:Bind (FontFamily)MyComboBox.SelectedValue}" />
```
- ```xml
<TextBox Text="{x:Bind (x:String)MyObject}" />
```
- ```xml
<TextBox Text="{x:Bind MyFunction((x:String)MyObject, (x:String)MyObject)}" />
```
- ```xml
<TextBox Tag="{x:Bind ((x:String)MyObject).Length}" />
```
where this methods is available in the code behind:
```csharp
public void MyFunction(string p1, string p2) { }
```

- [Pathless casting](https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-bind-markup-extension#pathless-casting)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class Given_XBindRewriter
[DataRow("ctx", "MyNameSpace.Static2.MyFunction(MyProperty)", "MyNameSpace.Static2.MyFunction(ctx.MyProperty)")]
[DataRow("ctx", "MyFunction(MyProperty)", "ctx.MyFunction(ctx.MyProperty)")]
[DataRow("ctx", "", "ctx")]

// Type Casts
[DataRow("ctx", "(FontFamily)Value", "(FontFamily)ctx.Value")]
[DataRow("ctx", "(FontFamily)a.Value", "(FontFamily)ctx.a.Value")]
[DataRow("ctx", "(global::System.Int32)a.Value", "(global::System.Int32)ctx.a.Value")]

Expand All @@ -41,7 +44,9 @@ public class Given_XBindRewriter
[DataRow("ctx", "System.String.Format('{0:X8}', AdornerCanvas.(MyNamespace.FrameworkElementExtensions.ActualWidth))", "System.String.Format('{0:X8}', MyNamespace.FrameworkElementExtensions.GetActualWidth(ctx.AdornerCanvas))")]

// Not supported https://github.com/unoplatform/uno/issues/5061
// [DataRow("ctx", "MyFunction((global::System.Int32)MyProperty)", "ctx.MyFunction((global::System.Int32)ctx.MyProperty)")]
[DataRow("ctx", "MyFunction((global::System.Int32)MyProperty)", "ctx.MyFunction((global::System.Int32)ctx.MyProperty)")]
[DataRow("ctx", "MyFunction((global::System.Int32)MyProperty.Inner)", "ctx.MyFunction((global::System.Int32)ctx.MyProperty.Inner)")]
[DataRow("ctx", "((MyClass)MyProperty).Test", "((MyClass)ctx.MyProperty).Test")]

// Main class (without context)
[DataRow("", "MyProperty.A", "MyProperty.A")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,13 @@ private string ContextBuilder
var isPathLessCast = Helpers.IsPathLessCast(node);
var isAttachedPropertySyntax = Helpers.IsAttachedPropertySyntax(node);
var isInsideAttachedPropertySyntax = Helpers.IsInsideAttachedPropertySyntax(node);
var isParenthesizedExpression = node.Expression is ParenthesizedExpressionSyntax;

if (e != null && isValidParent && !_isStaticMember(node.Expression.ToFullString()) && !isParentMemberStatic)
if (e!= null
&& isValidParent
&& !_isStaticMember(node.Expression.ToFullString())
&& !isParentMemberStatic
&& !isParenthesizedExpression)
{
if (isPathLessCast.result)
{
Expand Down Expand Up @@ -145,9 +150,15 @@ private string ContextBuilder
public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
{
var isInsideCast = Helpers.IsInsideCast(node);
var isValidParent = !Helpers.IsInsideMethod(node).result
&& !Helpers.IsInsideMemberAccessExpression(node).result
&& !isInsideCast.result;
var isValidParent =
(
!Helpers.IsInsideMethod(node).result
&& !Helpers.IsInsideMemberAccessExpression(node).result
&& !isInsideCast.result
)
|| Helpers.IsInsideCastWithParentheses(node).result
|| Helpers.IsInsideCastAsArrowClause(node).result
|| Helpers.IsInsideCastAsMethodArgument(node).result;

if (isValidParent && !_isStaticMember(node.ToFullString()))
{
Expand Down Expand Up @@ -231,22 +242,7 @@ internal static ExpressionSyntax ParseMethodBody(string body)
.Expression;

internal static (bool result, MemberAccessExpressionSyntax? memberAccess) IsInsideMemberAccessExpression(SyntaxNode node)
{
var currentNode = node.Parent;

do
{
if (currentNode is MemberAccessExpressionSyntax memberAccess)
{
return (true, memberAccess);
}

currentNode = currentNode?.Parent;
}
while (currentNode != null);

return (false, null);
}
=> IsInside(node, n => n as MemberAccessExpressionSyntax);

internal static (bool result, InvocationExpressionSyntax? expression) IsInsideMethod(SyntaxNode node)
{
Expand All @@ -270,12 +266,36 @@ internal static (bool result, InvocationExpressionSyntax? expression) IsInsideMe
}

internal static (bool result, CastExpressionSyntax? expression) IsInsideCast(SyntaxNode node)
=> IsInside(node, n => n as CastExpressionSyntax);

internal static (bool result, CastExpressionSyntax? expression) IsInsideCastWithParentheses(SyntaxNode node)
=> IsInside(
node,
n => n is CastExpressionSyntax cast
&& cast.Parent is ParenthesizedExpressionSyntax
&& cast.Expression == node ? cast : null);

internal static (bool result, CastExpressionSyntax? expression) IsInsideCastAsMethodArgument(SyntaxNode node)
=> IsInside(
node,
n => n is CastExpressionSyntax cast
&& cast.Parent is ArgumentSyntax
&& cast.Expression == node ? cast : null);

internal static (bool result, CastExpressionSyntax? expression) IsInsideCastAsArrowClause(SyntaxNode node)
=> IsInside(
node,
n => n is CastExpressionSyntax cast
&& cast.Parent is ArrowExpressionClauseSyntax
&& cast.Expression == node ? cast : null);

internal static (bool result, T? expression) IsInside<T>(SyntaxNode node, Func<SyntaxNode?, T?> predicate) where T: SyntaxNode
{
var currentNode = node.Parent;

do
{
if (currentNode is CastExpressionSyntax cast)
if (predicate(currentNode) is { } cast)
{
return (true, cast);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<UserControl
x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_TypeCast"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls">
<Grid>
<TextBlock x:Name="tb01"
x:FieldModifier="public"
Text="{x:Bind (x:String)MyObject}"/>
<TextBlock x:Name="tb02"
x:FieldModifier="public"
Text="{x:Bind MyMethod((x:String)MyObject)}"/>
<TextBlock x:Name="tb03"
x:FieldModifier="public"
Text="{x:Bind MyMethod2((x:String)MyObject, (x:String)MyObject)}" />
<TextBlock x:Name="tb04"
x:FieldModifier="public"
Tag="{x:Bind ((x:String)MyObject).Length}" />
</Grid>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
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 User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236

namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
{
public sealed partial class Binding_TypeCast : UserControl
{
public Binding_TypeCast()
{
this.InitializeComponent();
}

public object MyObject => "42";

public string MyMethod(string myString)
=> myString;

public string MyMethod2(string myString, string myString2)
=> myString + myString2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<UserControl
x:Class="Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls.Binding_TypeCast_DataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls">
<ContentControl x:Name="root"
x:FieldModifier="public">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="local:Binding_TypeCast_DataTemplate_Data">
<StackPanel>
<TextBlock x:Name="tb01"
x:FieldModifier="public"
Text="{x:Bind (x:String)MyObject}" />
<TextBlock x:Name="tb02"
x:FieldModifier="public"
Text="{x:Bind MyMethod((x:String)MyObject)}" />
<TextBlock x:Name="tb03"
x:FieldModifier="public"
Text="{x:Bind MyMethod2((x:String)MyObject, (x:String)MyObject)}" />
<TextBlock x:Name="tb04"
x:FieldModifier="public"
Tag="{x:Bind ((x:String)MyObject).Length}" />

</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
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 User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236

namespace Uno.UI.Tests.Windows_UI_Xaml_Data.xBindTests.Controls
{
public sealed partial class Binding_TypeCast_DataTemplate : UserControl
{
public Binding_TypeCast_DataTemplate()
{
this.InitializeComponent();
}
}

public partial class Binding_TypeCast_DataTemplate_Data
{
public object MyObject => "42";

public string MyMethod(string myString)
=> myString;

public string MyMethod2(string myString, string myString2)
=> myString + myString2;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,42 @@ public async Task When_NullableRecordStruct()
Assert.AreEqual("42", SUT.tb1.Text);
}

[TestMethod]
public async Task When_TypeCast()
{
var SUT = new Binding_TypeCast();

SUT.ForceLoaded();

Assert.AreEqual("42", SUT.tb01.Text);
Assert.AreEqual("42", SUT.tb02.Text);
Assert.AreEqual("4242", SUT.tb03.Text);
Assert.AreEqual(2, SUT.tb04.Tag);
}

[TestMethod]
public async Task When_TypeCast_DataTemplate()
{
var SUT = new Binding_TypeCast_DataTemplate();

var root = SUT.FindName("root") as FrameworkElement;
var dc = new Binding_TypeCast_DataTemplate_Data();
root.DataContext = dc;

SUT.ForceLoaded();
root.ForceLoaded();

var tb01 = SUT.FindName("tb01") as TextBlock;
var tb02 = SUT.FindName("tb02") as TextBlock;
var tb03 = SUT.FindName("tb03") as TextBlock;
var tb04 = SUT.FindName("tb04") as TextBlock;

Assert.AreEqual("42", tb01.Text);
Assert.AreEqual("42", tb02.Text);
Assert.AreEqual("4242", tb03.Text);
Assert.AreEqual(2, tb04.Tag);
}

[TestMethod]
public async Task When_PathLessCasting()
{
Expand Down

0 comments on commit 4521b63

Please sign in to comment.