diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 3e4204d79d..2f3f8c4daa 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -18,6 +18,7 @@ + @@ -57,6 +58,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 3324dd7319..aea83829ce 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -37,6 +37,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case ArrayElementPathElement arr: node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType)); break; + case VisualAncestorPathElement visualAncestor: + node = new FindVisualAncestorNode(visualAncestor.AncestorType, visualAncestor.Level); + break; case AncestorPathElement ancestor: node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level); break; @@ -101,6 +104,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings _elements.Add(new AncestorPathElement(ancestorType, level)); return this; } + public CompiledBindingPathBuilder VisualAncestor(Type ancestorType, int level) + { + _elements.Add(new VisualAncestorPathElement(ancestorType, level)); + return this; + } public CompiledBindingPathBuilder ElementName(INameScope nameScope, string name) { @@ -177,6 +185,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public int Level { get; } } + internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement + { + public VisualAncestorPathElement(Type ancestorType, int level) + { + AncestorType = ancestorType; + Level = level; + } + + public Type AncestorType { get; } + public int Level { get; } + } + internal class ElementNameElement : ICompiledBindingPathElement, IControlSourceBindingPathElement { public ElementNameElement(INameScope nameScope, string name) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs new file mode 100644 index 0000000000..5820f47fbb --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs @@ -0,0 +1,52 @@ +using System; +using Avalonia.Data.Core; +using Avalonia.VisualTree; + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + class FindVisualAncestorNode : ExpressionNode + { + private readonly int _level; + private readonly Type _ancestorType; + private IDisposable _subscription; + + public FindVisualAncestorNode(Type ancestorType, int level) + { + _level = level; + _ancestorType = ancestorType; + } + + public override string Description + { + get + { + if (_ancestorType == null) + { + return $"$visualparent[{_level}]"; + } + else + { + return $"$visualparent[{_ancestorType.Name}, {_level}]"; + } + } + } + + protected override void StartListeningCore(WeakReference reference) + { + if (reference.TryGetTarget(out object target) && target is IVisual visual) + { + _subscription = VisualLocator.Track(visual, _level, _ancestorType).Subscribe(ValueChanged); + } + else + { + _subscription = null; + } + } + + protected override void StopListeningCore() + { + _subscription?.Dispose(); + _subscription = null; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 396196cfbc..2b09934057 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -37,26 +37,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions // Targeted - InsertBefore(new AvaloniaXamlIlTransformInstanceAttachedProperties()); + InsertBefore( + new AvaloniaXamlIlTransformInstanceAttachedProperties(), + new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers()); InsertAfter(new AvaloniaXamlIlAvaloniaPropertyResolver()); InsertBefore( + new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlSelectorTransformer(), + new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlPropertyPathTransformer(), new AvaloniaXamlIlSetterTransformer(), - new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlTransitionsTypeMetadataTransformer() ); // After everything else - InsertBefore( - new AvaloniaXamlIlBindingPathParser() - ); - InsertBefore( new AddNameScopeRegistration(), new AvaloniaXamlIlDataContextTypeTransformer(), 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 9a34261813..ee8fc35deb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs @@ -4,8 +4,10 @@ using System.Linq; using System.Text; using Avalonia.Markup.Parsers; using Avalonia.Utilities; +using XamlIl; using XamlIl.Ast; using XamlIl.Transform; +using XamlIl.Transform.Transformers; using XamlIl.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers @@ -16,10 +18,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { if (node is XamlIlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) { + var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding); + if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlIlAstTextNode bindingPathText) { var reader = new CharacterReader(bindingPathText.Text.AsSpan()); var (nodes, _) = BindingExpressionGrammar.Parse(ref reader); + + if (convertedNode != null) + { + nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); + } + binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes); } else @@ -31,6 +41,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { var reader = new CharacterReader(pathValue.Text.AsSpan()); var (nodes, _) = BindingExpressionGrammar.Parse(ref reader); + + if (convertedNode != null) + { + nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); + } + bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes); } } @@ -38,6 +54,201 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } + + private static BindingExpressionGrammar.INode ConvertLongFormPropertiesToBindingExpressionNode( + XamlIlAstTransformationContext context, + XamlIlAstObjectNode binding) + { + BindingExpressionGrammar.INode convertedNode = null; + + var syntheticCompiledBindingProperties = binding.Children.OfType() + .Where(v => v.Property is AvaloniaSyntheticCompiledBindingProperty) + .ToList(); + + var elementNameProperty = syntheticCompiledBindingProperties + .FirstOrDefault(v => + v.Property is AvaloniaSyntheticCompiledBindingProperty prop + && prop.Name == SyntheticCompiledBindingPropertyName.ElementName); + + var relativeSourceProperty = syntheticCompiledBindingProperties + .FirstOrDefault(v => + v.Property is AvaloniaSyntheticCompiledBindingProperty prop + && prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource); + + if (elementNameProperty?.Values[0] is XamlIlAstTextNode elementName) + { + convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text }; + } + else if (elementNameProperty != null) + { + throw new XamlIlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]); + } + + if (GetRelativeSourceObjectFromAssignment( + context, + relativeSourceProperty, + out var relativeSourceObject)) + { + if (convertedNode != null) + { + throw new XamlIlParseException("Both ElementName and RelativeSource specified as a binding source. Only one property is allowed.", binding); + } + + var mode = relativeSourceObject.Children + .OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode") + ?.Values[0] is XamlIlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null; + if (relativeSourceObject.Arguments.Count == 0 && mode == null) + { + mode = "FindAncestor"; + } + + if (mode == "FindAncestor") + { + var ancestorLevel = relativeSourceObject.Children + .OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "FindAncestor") + ?.Values[0] is XamlIlAstTextNode ancestorLevelText ? int.Parse(ancestorLevelText.Text) - 1 : 0; + + var treeType = relativeSourceObject.Children + .OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree") + ?.Values[0] is XamlIlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual"; + + var ancestorTypeName = relativeSourceObject.Children + .OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType") + ?.Values[0] as XamlIlAstTextNode; + + IXamlIlType ancestorType = null; + if (ancestorTypeName is null) + { + if (treeType == "Visual") + { + throw new XamlIlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject); + } + else if (treeType == "Logical") + { + var styledElementType = context.GetAvaloniaTypes().StyledElement; + ancestorType = context + .ParentNodes() + .OfType() + .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType())) + .ElementAtOrDefault(ancestorLevel) + ?.Type.GetClrType(); + + if (ancestorType is null) + { + throw new XamlIlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject); + } + } + } + else + { + ancestorType = XamlIlTypeReferenceResolver.ResolveType( + context, + ancestorTypeName.Text, + false, + ancestorTypeName, + true).GetClrType(); + } + + if (treeType == "Visual") + { + convertedNode = new VisualAncestorBindingExpressionNode + { + Type = ancestorType, + Level = ancestorLevel + }; + } + else if (treeType == "Logical") + { + convertedNode = new LogicalAncestorBindingExpressionNode + { + Type = ancestorType, + Level = ancestorLevel + }; + } + else + { + throw new XamlIlParseException($"Unknown tree type '{treeType}'.", binding); + } + } + else if (mode == "DataContext") + { + convertedNode = null; + } + else if (mode == "Self") + { + convertedNode = new BindingExpressionGrammar.SelfNode(); + } + else if (mode == "TemplatedParent") + { + var parentType = context.ParentNodes().OfType() + .FirstOrDefault(x => + x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate) + ?.TargetType.GetClrType(); + + if (parentType is null) + { + throw new XamlIlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding); + } + + convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType }; + } + else + { + throw new XamlIlParseException($"Unknown RelativeSource mode '{mode}'.", binding); + } + } + + if (elementNameProperty != null) + { + binding.Children.Remove(elementNameProperty); + } + if (relativeSourceProperty != null) + { + binding.Children.Remove(relativeSourceProperty); + } + + return convertedNode; + } + + private static bool GetRelativeSourceObjectFromAssignment( + XamlIlAstTransformationContext context, + XamlIlAstXamlPropertyValueNode relativeSourceProperty, + out XamlIlAstObjectNode relativeSourceObject) + { + relativeSourceObject = null; + if (relativeSourceProperty is null) + { + return false; + } + + if (relativeSourceProperty.Values[0] is XamlIlMarkupExtensionNode me) + { + if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource) + { + throw new XamlIlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me); + } + + relativeSourceObject = (XamlIlAstObjectNode)me.Value; + return true; + } + + if (relativeSourceProperty.Values[0] is XamlIlAstObjectNode on) + { + if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource) + { + throw new XamlIlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on); + } + + relativeSourceObject = on; + return true; + } + + return false; + } } class ParsedBindingPathNode : XamlIlAstNode, IXamlIlAstValueNode @@ -53,4 +264,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IList Path { get; } } + + class VisualAncestorBindingExpressionNode : BindingExpressionGrammar.INode + { + public IXamlIlType Type { get; set; } + public int Level { get; set; } + } + + class LogicalAncestorBindingExpressionNode : BindingExpressionGrammar.INode + { + public IXamlIlType Type { get; set; } + public int Level { get; set; } + } + + class TemplatedParentBindingExpressionNode : BindingExpressionGrammar.INode + { + public IXamlIlType Type { get; set; } + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs new file mode 100644 index 0000000000..1354224e24 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; +using XamlIl; +using XamlIl.Ast; +using XamlIl.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlTransformSyntheticCompiledBindingMembers : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (node is XamlIlAstNamePropertyReference prop + && prop.TargetType is XamlIlAstClrTypeReference targetRef + && targetRef.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) + { + if (prop.Name == "ElementName") + { + return new AvaloniaSyntheticCompiledBindingProperty(node, + SyntheticCompiledBindingPropertyName.ElementName); + } + else if (prop.Name == "RelativeSource") + { + return new AvaloniaSyntheticCompiledBindingProperty(node, + SyntheticCompiledBindingPropertyName.RelativeSource); + } + } + + return node; + } + } + + enum SyntheticCompiledBindingPropertyName + { + ElementName, + RelativeSource + } + + class AvaloniaSyntheticCompiledBindingProperty : XamlIlAstNode, IXamlIlAstPropertyReference + { + public SyntheticCompiledBindingPropertyName Name { get; } + + public AvaloniaSyntheticCompiledBindingProperty( + IXamlIlLineInfo lineInfo, + SyntheticCompiledBindingPropertyName name) + : base(lineInfo) + { + Name = name; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index d4ec9fbea3..829be2cb1a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -39,6 +39,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlIlType IItemsPresenterHost { get; } public IXamlIlType BindingExtension { get; } + public IXamlIlType RelativeSource { get; } + public AvaloniaXamlIlWellKnownTypes(XamlIlTransformerConfiguration cfg) { XamlIlTypes = cfg.WellKnownTypes; @@ -91,6 +93,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost"); BindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.BindingExtension"); + RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs index 5d90f17117..24ef62f368 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -118,6 +118,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions else { var clrProperty = targetType.GetAllProperties().FirstOrDefault(p => p.Name == propName.PropertyName); + + if (clrProperty is null) + { + throw new XamlIlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); + } nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty)); } break; @@ -176,8 +181,38 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions case BindingExpressionGrammar.SelfNode _: nodes.Add(new SelfPathElementNode(targetType)); break; + case VisualAncestorBindingExpressionNode visualAncestor: + nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level)); + break; + case TemplatedParentBindingExpressionNode templatedParent: + var templatedParentField = context.GetAvaloniaTypes().StyledElement.GetAllFields() + .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == "TemplatedParentProperty"); + nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode( + templatedParentField, + templatedParent.Type)); + break; case BindingExpressionGrammar.AncestorNode ancestor: - nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level)); + if (ancestor.Namespace is null && ancestor.TypeName is null) + { + var styledElementType = context.GetAvaloniaTypes().StyledElement; + var ancestorType = context + .ParentNodes() + .OfType() + .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType())) + .ElementAtOrDefault(ancestor.Level) + ?.Type.GetClrType(); + + if (ancestorType is null) + { + throw new XamlIlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo); + } + + nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level)); + } + else + { + nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level)); + } break; case BindingExpressionGrammar.NameNode elementName: IXamlIlType elementType = null; @@ -351,6 +386,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + class FindVisualAncestorPathElementNode : IXamlIlBindingPathElementNode + { + private readonly int _level; + + public FindVisualAncestorPathElementNode(IXamlIlType ancestorType, int level) + { + Type = ancestorType; + _level = level; + } + + public IXamlIlType Type { get; } + + public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen) + { + codeGen.Ldtype(Type) + .Ldc_I4(_level) + .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "VisualAncestor")); + } + } + class ElementNamePathElementNode : IXamlIlBindingPathElementNode { private readonly string _name; @@ -390,7 +445,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { codeGen.Ldsfld(_field); context.Configuration.GetExtra() - .EmitLoadInpcPropertyAccessorFactory(context, codeGen); + .EmitLoadAvaloniaPropertyAccessorFactory(context, codeGen); codeGen.EmitCall(context.GetAvaloniaTypes() .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property")); } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index a58068cc5b..cbf2fa201a 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -345,6 +345,8 @@ namespace Avalonia.Markup.Parsers public interface INode {} + public interface ITransformNode {} + public class EmptyExpressionNode : INode { } public class PropertyNameNode : INode @@ -364,7 +366,7 @@ namespace Avalonia.Markup.Parsers public IList Arguments { get; set; } } - public class NotNode : INode {} + public class NotNode : INode, ITransformNode {} public class StreamNode : INode {} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index ac4ff7036a..69f86d46a6 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -439,6 +439,61 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(dataContext.StringProperty, textBlock.Text); } } + + [Fact] + public void ResolvesElementNameBindingFromLongForm() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("text2"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal(dataContext.StringProperty, textBlock.Text); + } + } + + [Fact] + public void ResolvesRelativeSourceBindingLongForm() + { + 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", target.Text); + } + } } public class TestDataContext