From 5e5e7e22ae02eca741409cfc552c646efad82b21 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 13 Oct 2019 13:26:56 -0700 Subject: [PATCH] Support synthetic Source property. --- .../CompiledBindingExtension.cs | 7 +++ .../CompiledBindings/CompiledBindingPath.cs | 14 +++++- .../AvaloniaXamlIlBindingPathParser.cs | 48 ++++++++++++++++++- ...ransformSyntheticCompiledBindingMembers.cs | 8 +++- .../XamlIlBindingPathHelper.cs | 43 +++++++++++++++++ .../CompiledBindingExtensionTests.cs | 25 ++++++++++ 6 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 5c3628fd22..aab733cb43 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -50,6 +50,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) { + if (Path.RawSource != null) + { + return CreateSourceObserver( + Path.RawSource, + Path.BuildExpression(enableDataValidation)); + } + if (Path.SourceMode == SourceMode.Data) { return CreateDataContextObserver( diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index aea83829ce..d627fe3cd3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -14,9 +14,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CompiledBindingPath() { } - internal CompiledBindingPath(IEnumerable bindingPath) + internal CompiledBindingPath(IEnumerable bindingPath, object rawSource) { _elements = new List(bindingPath); + RawSource = rawSource; } public ExpressionNode BuildExpression(bool enableValidation) @@ -63,10 +64,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings } internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data; + + internal object RawSource { get; } } public class CompiledBindingPathBuilder { + private object _rawSource; private List _elements = new List(); public CompiledBindingPathBuilder Not() @@ -122,7 +126,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } - public CompiledBindingPath Build() => new CompiledBindingPath(_elements); + public CompiledBindingPathBuilder SetRawSource(object rawSource) + { + _rawSource = rawSource; + return this; + } + + public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource); } public interface ICompiledBindingPathElement diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs index ee8fc35deb..daf3f57936 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs @@ -70,6 +70,11 @@ 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 @@ -84,6 +89,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlIlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]); } + if (sourceProperty?.Values[0] != null) + { + if (convertedNode != null) + { + throw new XamlIlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding); + } + + convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]); + } + if (GetRelativeSourceObjectFromAssignment( context, relativeSourceProperty, @@ -91,7 +106,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { if (convertedNode != null) { - throw new XamlIlParseException("Both ElementName and RelativeSource specified as a binding source. Only one property is allowed.", binding); + throw new XamlIlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding); } var mode = relativeSourceObject.Children @@ -206,6 +221,10 @@ 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); @@ -263,6 +282,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlIlAstTypeReference Type { get; } public IList Path { get; } + + public override void VisitChildren(IXamlIlAstVisitor visitor) + { + for (int i = 0; i < Path.Count; i++) + { + if (Path[i] is IXamlIlAstNode ast) + { + Path[i] = (BindingExpressionGrammar.INode)ast.Visit(visitor); + } + } + } } class VisualAncestorBindingExpressionNode : BindingExpressionGrammar.INode @@ -281,4 +311,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { public IXamlIlType Type { get; set; } } + + class RawSourceBindingExpressionNode : XamlIlAstNode, BindingExpressionGrammar.INode + { + public RawSourceBindingExpressionNode(IXamlIlAstValueNode rawSource) + : base(rawSource) + { + RawSource = rawSource; + } + + public IXamlIlAstValueNode RawSource { get; private set; } + + public override void VisitChildren(IXamlIlAstVisitor visitor) + { + RawSource = (IXamlIlAstValueNode)RawSource.Visit(visitor); + } + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs index 1354224e24..8e8f628da3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs @@ -25,6 +25,11 @@ 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; @@ -34,7 +39,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers enum SyntheticCompiledBindingPropertyName { ElementName, - RelativeSource + RelativeSource, + Source } class AvaloniaSyntheticCompiledBindingProperty : XamlIlAstNode, IXamlIlAstPropertyReference diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs index 24ef62f368..5d8c3bea7e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -235,6 +235,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType)); break; + case RawSourceBindingExpressionNode rawSource: + nodes.Add(new RawSourcePathElementNode(rawSource.RawSource)); + break; } } @@ -563,6 +566,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public IXamlIlType Type => _arrayType.ArrayElementType; } + class RawSourcePathElementNode : XamlIlAstNode, IXamlIlBindingPathElementNode + { + private readonly IXamlIlAstValueNode _rawSource; + + public RawSourcePathElementNode(IXamlIlAstValueNode rawSource) + :base(rawSource) + { + _rawSource = rawSource; + + } + + public IXamlIlType Type => _rawSource.Type.GetClrType(); + + public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) + { + context.Emit(_rawSource, codeGen, Type); + codeGen + .EmitCall(context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "SetRawSource")); + } + } + class XamlIlBindingPathNode : XamlIlAstNode, IXamlIlBindingPathNode, IXamlIlAstEmitableNode { private readonly List _transformElements; @@ -603,6 +628,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions codeGen.EmitCall(types.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Build")); return XamlIlNodeEmitResult.Type(0, types.CompiledBindingPath); } + + public override void VisitChildren(IXamlIlAstVisitor visitor) + { + for (int i = 0; i < _transformElements.Count; i++) + { + if (_transformElements[i] is IXamlIlAstNode ast) + { + _transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); + } + } + for (int i = 0; i < _elements.Count; i++) + { + if (_elements[i] is IXamlIlAstNode ast) + { + _elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); + } + } + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 69f86d46a6..43aad921ff 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -494,6 +494,31 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal("test", target.Text); } } + + [Fact] + public void ResolvesSourceBindingLongForm() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var target = window.FindControl("text"); + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + target.ApplyTemplate(); + + Assert.Equal("Test".Length.ToString(), target.Text); + } + } } public class TestDataContext