Browse Source

Merge pull request #7201 from jkoritzinsky/relativesource-compiledbinding

Support RelativeSource-based bindings without requiring an x:DataType property.
release/0.10.11
Nikita Tsukanov 4 years ago
committed by Dan Walmsley
parent
commit
0a94431153
  1. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  2. 22
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  3. 101
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  4. 24
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

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

@ -99,7 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
if (startType == null)
Func<IXamlType> startTypeResolver = startType is not null ? () => startType : () =>
{
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
@ -107,10 +107,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
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;
}
return parentDataContextNode.DataContextType;
};
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType);
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType());
}
return node;

22
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using XamlX;
@ -11,7 +12,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
{
private const string AvaloniaNs = "https://github.com/avaloniaui";
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlDataContextTypeMetadataNode)
@ -120,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentItemsDataContext != null)
{
itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, parentItemsDataContext.DataContextType);
itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, () => parentItemsDataContext.DataContextType, parentObject.Type.GetClrType());
}
}
}
@ -153,16 +153,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
else if (obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{
IXamlType startType;
var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
if (parentDataContextNode is null)
Func<IXamlType> startTypeResolver = () =>
{
throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
}
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.", obj);
}
startType = parentDataContextNode.DataContextType;
return parentDataContextNode.DataContextType;
};
var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startType);
var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver, on.Type.GetClrType());
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
}

101
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -20,7 +20,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
static class XamlIlBindingPathHelper
{
public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, IXamlType startType)
public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, Func<IXamlType> startTypeResolver, IXamlType selfType)
{
IXamlType bindingResultType = null;
if (binding.Arguments.Count > 0 && binding.Arguments[0] is ParsedBindingPathNode bindingPath)
@ -28,7 +28,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var transformed = TransformBindingPath(
context,
bindingPath,
startType,
startTypeResolver,
selfType,
bindingPath.Path);
bindingResultType = transformed.BindingResultType;
@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (bindingPathAssignment is null)
{
return startType;
return startTypeResolver();
}
if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode)
@ -49,7 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var transformed = TransformBindingPath(
context,
bindingPathNode,
startType,
startTypeResolver,
selfType,
bindingPathNode.Path);
bindingResultType = transformed.BindingResultType;
@ -64,13 +66,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return bindingResultType;
}
private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, IXamlType startType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
{
List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
foreach (var astNode in bindingExpression)
{
var targetType = nodes.Count == 0 ? startType : nodes[nodes.Count - 1].Type;
var targetTypeResolver = nodes.Count == 0 ? startTypeResolver : () => nodes[nodes.Count - 1].Type;
switch (astNode)
{
case BindingExpressionGrammar.EmptyExpressionNode _:
@ -79,59 +81,66 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
break;
case BindingExpressionGrammar.StreamNode _:
IXamlType observableType;
if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
{
observableType = targetType;
}
else
{
observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false);
}
IXamlType targetType = targetTypeResolver();
IXamlType observableType;
if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
{
observableType = targetType;
}
else
{
observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false);
}
if (observableType != null)
{
nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0]));
break;
}
bool foundTask = false;
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
{
if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1")))
if (observableType != null)
{
foundTask = true;
nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0]));
break;
}
bool foundTask = false;
for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
{
if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1")))
{
foundTask = true;
nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
break;
}
}
if (foundTask)
{
break;
}
throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
}
if (foundTask)
{
break;
}
throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
case BindingExpressionGrammar.PropertyNameNode propName:
var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);
if (avaloniaPropertyFieldMaybe != null)
{
nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
}
else
{
var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName);
IXamlType targetType = targetTypeResolver();
var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);
if (clrProperty is null)
if (avaloniaPropertyFieldMaybe != null)
{
nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
}
else
{
throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName);
if (clrProperty is null)
{
throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
}
nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
break;
}
break;
case BindingExpressionGrammar.IndexerNode indexer:
{
IXamlType targetType = targetTypeResolver();
if (targetType.IsArray)
{
nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo));
@ -183,7 +192,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo)));
break;
case BindingExpressionGrammar.SelfNode _:
nodes.Add(new SelfPathElementNode(targetType));
nodes.Add(new SelfPathElementNode(selfType));
break;
case VisualAncestorBindingExpressionNode visualAncestor:
nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level));

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

@ -731,8 +731,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
Title='foo'
x:DataType='local:TestDataContext'>
Title='foo'>
<ContentControl Content='{CompiledBinding $parent.Title}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
@ -1042,6 +1041,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void Binds_To_Self_Without_DataType()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock Name='textBlock' Text='{CompiledBinding $self.Name}' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.Equal(textBlock.Name, textBlock.Text);
}
}
void Throws(string type, Action cb)
{
try

Loading…
Cancel
Save