Browse Source

Merge pull request #5052 from donandren/issues/compiledbindingsetsource

Add Support for CompiledBinding Source
pull/5155/head
Dan Walmsley 6 years ago
committed by GitHub
parent
commit
75a947fc2a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  2. 100
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  3. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
  4. 10
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  5. 121
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

23
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@ -72,16 +72,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
v.Property is AvaloniaSyntheticCompiledBindingProperty prop
&& prop.Name == SyntheticCompiledBindingPropertyName.ElementName);
var sourceProperty = syntheticCompiledBindingProperties
.FirstOrDefault(v =>
v.Property is AvaloniaSyntheticCompiledBindingProperty prop
&& prop.Name == SyntheticCompiledBindingPropertyName.Source);
var relativeSourceProperty = syntheticCompiledBindingProperties
.FirstOrDefault(v =>
v.Property is AvaloniaSyntheticCompiledBindingProperty prop
&& prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource);
var sourceProperty = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(v =>
v.Property is XamlAstClrProperty prop
&& prop.Name == "Source");
if (elementNameProperty?.Values[0] is XamlAstTextNode elementName)
{
convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text };
@ -91,14 +91,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
}
if (sourceProperty?.Values[0] != null)
if (sourceProperty != null && convertedNode != null)
{
if (convertedNode != null)
{
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]);
throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
}
if (GetRelativeSourceObjectFromAssignment(
@ -223,10 +218,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
binding.Children.Remove(elementNameProperty);
}
if (sourceProperty != null)
{
binding.Children.Remove(sourceProperty);
}
if (relativeSourceProperty != null)
{
binding.Children.Remove(relativeSourceProperty);

100
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
@ -15,14 +13,102 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
if (node is XamlAstConstructableObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
IXamlType startType;
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
IXamlType startType = null;
var sourceProperty = binding.Children.OfType<XamlPropertyAssignmentNode>().FirstOrDefault(c => c.Property.Name == "Source");
if ((sourceProperty?.Values.Count ?? 0) == 1)
{
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
var sourceValue = sourceProperty.Values[0];
switch (sourceValue)
{
case XamlAstTextNode textNode:
startType = textNode.Type?.GetClrType();
break;
case XamlMarkupExtensionNode extension:
startType = extension.Type?.GetClrType();
//let's try to infer StaticResource type from parent resources in xaml
if (extension.Value.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.MarkupExtensions.StaticResourceExtension" &&
extension.Value is XamlAstConstructableObjectNode cn &&
cn.Arguments.Count == 1 && cn.Arguments[0] is XamlAstTextNode keyNode)
{
bool matchProperty(IXamlAstNode node, IXamlType styledElementType, string propertyName)
{
return (node is XamlPropertyAssignmentNode p &&
p.Property.DeclaringType == styledElementType && p.Property.Name == propertyName)
||
(node is XamlManipulationGroupNode m && m.Children.Count > 0 &&
m.Children[0] is XamlPropertyAssignmentNode pm &&
pm.Property.DeclaringType == styledElementType && pm.Property.Name == propertyName);
}
string getResourceValue_xKey(XamlPropertyAssignmentNode node)
=> node.Values.Count == 2 && node.Values[0] is XamlAstTextNode t ? t.Text : "";
IXamlType getResourceValue_Type(XamlPropertyAssignmentNode node, IXamlType xamlType)
=> node.Values.Count == 2 ? node.Values[1].Type.GetClrType() : xamlType;
IEnumerable<XamlPropertyAssignmentNode> getResourceValues(IXamlAstNode node)
{
if (node is XamlPropertyAssignmentNode propertyNode)
{
if (propertyNode.Values.Count == 1 &&
propertyNode.Values[0] is XamlAstConstructableObjectNode obj &&
obj.Type.GetClrType().FullName == "Avalonia.Controls.ResourceDictionary")
{
foreach (var r in obj.Children.SelectMany(c => getResourceValues(c)))
{
yield return r;
}
}
else
{
yield return propertyNode;
}
}
else if (node is XamlManipulationGroupNode m)
{
foreach (var r in m.Children.OfType<XamlPropertyAssignmentNode>())
{
yield return r;
}
}
}
string key = keyNode.Text;
var styledElement = context.GetAvaloniaTypes().StyledElement;
var resource = context.ParentNodes()
.OfType<XamlAstConstructableObjectNode>()
.Where(o => styledElement.IsAssignableFrom(o.Type.GetClrType()))
.Select(o => o.Children.FirstOrDefault(p => matchProperty(p, styledElement, "Resources")))
.Where(r => r != null)
.SelectMany(r => getResourceValues(r))
.FirstOrDefault(r => getResourceValue_xKey(r) == key);
if (resource != null)
{
startType = getResourceValue_Type(resource, startType);
}
}
break;
case XamlStaticExtensionNode staticExtension:
startType = staticExtension.Type?.GetClrType();
break;
}
}
startType = parentDataContextNode.DataContextType;
if (startType == null)
{
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
{
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
}
startType = parentDataContextNode.DataContextType;
}
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType);
}

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs

@ -25,11 +25,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return new AvaloniaSyntheticCompiledBindingProperty(node,
SyntheticCompiledBindingPropertyName.RelativeSource);
}
else if (prop.Name == "Source")
{
return new AvaloniaSyntheticCompiledBindingProperty(node,
SyntheticCompiledBindingPropertyName.Source);
}
}
return node;
@ -39,8 +34,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
enum SyntheticCompiledBindingPropertyName
{
ElementName,
RelativeSource,
Source
RelativeSource
}
class AvaloniaSyntheticCompiledBindingProperty : XamlAstNode, IXamlAstPropertyReference

10
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -32,6 +32,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Mode = Mode,
Priority = Priority,
StringFormat = StringFormat,
Source = Source,
DefaultAnchor = new WeakReference(GetDefaultAnchor(provider))
};
}
@ -52,6 +53,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
{
if (Source != null)
{
return CreateSourceObserver(
Source,
Path.BuildExpression(enableDataValidation));
}
if (Path.RawSource != null)
{
return CreateSourceObserver(
@ -77,5 +85,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
[ConstructorArgument("path")]
public CompiledBindingPath Path { get; set; }
public object Source { get; set; }
}
}

121
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Markup.Data;
using Avalonia.Media;
using Avalonia.UnitTests;
using XamlX;
using Xunit;
@ -536,7 +537,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
[Fact]
public void ResolvesSourceBindingLongForm()
public void Binds_To_Source()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@ -559,6 +560,124 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void Binds_To_Source_StaticResource()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:CompileBindings='True'>
<Window.Resources>
<local:TestDataContext x:Key='dataKey' StringProperty='foobar'/>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding StringProperty, Source={StaticResource dataKey}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void Binds_To_Source_StaticResource1()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:CompileBindings='True'>
<Window.Resources>
<local:TestDataContext x:Key='dataKey' StringProperty='foobar'/>
<x:String x:Key='otherObjectKey'>test</x:String>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding StringProperty, Source={StaticResource dataKey}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void Binds_To_Source_StaticResource_In_ResourceDictionary()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext' x:CompileBindings='True'>
<Window.Resources>
<ResourceDictionary>
<local:TestDataContext x:Key='dataKey' StringProperty='foobar'/>
</ResourceDictionary>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding StringProperty, Source={StaticResource dataKey}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void Binds_To_Source_StaticResource_In_ResourceDictionary1()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext' x:CompileBindings='True'>
<Window.Resources>
<ResourceDictionary>
<local:TestDataContext x:Key='dataKey' StringProperty='foobar'/>
<x:String x:Key='otherObjectKey'>test</x:String>
</ResourceDictionary>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding StringProperty, Source={StaticResource dataKey}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void Binds_To_Source_xStatic()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:CompileBindings='True'>
<ContentControl Name='contentControl' Content='{Binding Color, Source={x:Static Brushes.Red}}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
Assert.Equal(Brushes.Red.Color, contentControl.Content);
}
}
[Fact]
public void CompilesBindingWhenRequested()
{

Loading…
Cancel
Save