diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index e2ee4dd67f7..f267182f6f1 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -177,7 +177,8 @@
-
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
index 6e7a0473784..1765a848a6a 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
@@ -197,8 +197,13 @@ private static XamlIlBindingPathNode TransformBindingPath(AstTransformationConte
if (avaloniaPropertyFieldMaybe != null)
{
- nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
- XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
+ var isDataContextProperty = avaloniaPropertyFieldMaybe.Name == "DataContextProperty" && Equals(avaloniaPropertyFieldMaybe.DeclaringType, context.GetAvaloniaTypes().StyledElement);
+ var propertyType = isDataContextProperty
+ ? (nodes.LastOrDefault() as IXamlIlBindingPathNodeWithDataContextType)?.DataContextType
+ : null;
+ propertyType ??= XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo);
+
+ nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, propertyType));
}
else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty)
{
@@ -297,34 +302,55 @@ private static XamlIlBindingPathNode TransformBindingPath(AstTransformationConte
nodes.Add(new TemplatedParentPathElementNode(templatedParentType.GetClrType()));
break;
case BindingExpressionGrammar.AncestorNode ancestor:
- if (ancestor.Namespace is null && ancestor.TypeName is null)
+ var styledElement = context.GetAvaloniaTypes().StyledElement;
+ var ancestorTypeFilter = !(ancestor.Namespace is null && ancestor.TypeName is null) ? GetType(ancestor.Namespace, ancestor.TypeName) : null;
+
+ var ancestorNode = context
+ .ParentNodes()
+ .OfType()
+ .Where(x => styledElement.IsAssignableFrom(x.Type.GetClrType()))
+ .Skip(1)
+ .Where(x => ancestorTypeFilter is not null
+ ? ancestorTypeFilter.IsAssignableFrom(x.Type.GetClrType()) : true)
+ .ElementAtOrDefault(ancestor.Level);
+
+ IXamlType? dataContextType = null;
+ if (ancestorNode is not null)
{
- var styledElementType = context.GetAvaloniaTypes().StyledElement;
- var ancestorType = context
- .ParentNodes()
- .OfType()
- .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
- .Skip(1)
- .ElementAtOrDefault(ancestor.Level)
- ?.Type.GetClrType();
-
- if (ancestorType is null)
+ var isSkipping = true;
+ foreach (var node in context.ParentNodes())
{
- throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
+ if (node == ancestorNode)
+ isSkipping = false;
+ if (node is AvaloniaNameScopeRegistrationXamlIlNode)
+ break;
+ if (!isSkipping && node is AvaloniaXamlIlDataContextTypeMetadataNode metadataNode)
+ {
+ dataContextType = metadataNode.DataContextType;
+ break;
+ }
}
-
- nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
}
- else
+
+ // We need actual ancestor for a correct DataContextType,
+ // but since in current design bindings do a double-work by enumerating the tree,
+ // we want to keep original ancestor type filter, if it was present.
+ var bindingAncestorType = ancestorTypeFilter is not null
+ ? ancestorTypeFilter
+ : ancestorNode?.Type.GetClrType();
+
+ if (bindingAncestorType is null)
{
- nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level));
+ throw new XamlX.XamlTransformException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
}
+
+ nodes.Add(new FindAncestorPathElementNode(bindingAncestorType, ancestor.Level, dataContextType));
break;
case BindingExpressionGrammar.NameNode elementName:
- IXamlType? elementType = null;
+ IXamlType? elementType = null, dataType = null;
foreach (var deferredContent in context.ParentNodes().OfType())
{
- elementType = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name);
+ (elementType, dataType) = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name) ?? default;
if (!(elementType is null))
{
break;
@@ -332,14 +358,14 @@ private static XamlIlBindingPathNode TransformBindingPath(AstTransformationConte
}
if (elementType is null)
{
- elementType = ScopeRegistrationFinder.GetTargetType(context.ParentNodes().Last(), elementName.Name);
+ (elementType, dataType) = ScopeRegistrationFinder.GetTargetType(context.ParentNodes().Last(), elementName.Name) ?? default;
}
if (elementType is null)
{
throw new XamlX.XamlTransformException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
}
- nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
+ nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType, dataType));
break;
case BindingExpressionGrammar.TypeCastNode typeCastNode:
var castType = GetType(typeCastNode.Namespace, typeCastNode.TypeName);
@@ -420,8 +446,9 @@ private ScopeRegistrationFinder(string name)
string Name { get; }
IXamlType? TargetType { get; set; }
+ IXamlType? DataContextType { get; set; }
- public static IXamlType? GetTargetType(IXamlAstNode namescopeRoot, string name)
+ public static (IXamlType Target, IXamlType? DataContextType)? GetTargetType(IXamlAstNode namescopeRoot, string name)
{
// If we start from the nested scope - skip it.
if (namescopeRoot is NestedScopeMetadataNode scope)
@@ -431,7 +458,7 @@ private ScopeRegistrationFinder(string name)
var finder = new ScopeRegistrationFinder(name);
namescopeRoot.Visit(finder);
- return finder.TargetType;
+ return finder.TargetType is not null ? (finder.TargetType, DataType: finder.DataContextType) : null;
}
void IXamlAstVisitor.Pop()
@@ -455,12 +482,20 @@ void IXamlAstVisitor.Push(IXamlAstNode node)
IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
{
// Ignore name registrations, if we are inside of the nested namescope.
- if (_childScopesStack.Count == 0 && node is AvaloniaNameScopeRegistrationXamlIlNode registration)
+ if (_childScopesStack.Count == 0)
{
- if (registration.Name is XamlAstTextNode text && text.Text == Name)
+ if (node is AvaloniaNameScopeRegistrationXamlIlNode registration
+ && registration.Name is XamlAstTextNode text && text.Text == Name)
{
TargetType = registration.TargetType;
}
+ // We are visiting nodes top to bottom.
+ // If we have already found target type by its name,
+ // it means all next nodes will be below, and not applicable for data context inheritance.
+ else if (TargetType is null && node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextTypeMetadata)
+ {
+ DataContextType = dataContextTypeMetadata.DataContextType;
+ }
}
return node;
}
@@ -473,6 +508,11 @@ interface IXamlIlBindingPathElementNode
void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen);
}
+ interface IXamlIlBindingPathNodeWithDataContextType
+ {
+ IXamlType? DataContextType { get; }
+ }
+
class XamlIlNotPathElementNode : IXamlIlBindingPathElementNode
{
public XamlIlNotPathElementNode(IXamlType boolType)
@@ -533,17 +573,19 @@ public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
}
}
- class FindAncestorPathElementNode : IXamlIlBindingPathElementNode
+ class FindAncestorPathElementNode : IXamlIlBindingPathElementNode, IXamlIlBindingPathNodeWithDataContextType
{
private readonly int _level;
- public FindAncestorPathElementNode(IXamlType ancestorType, int level)
+ public FindAncestorPathElementNode(IXamlType ancestorType, int level, IXamlType? dataContextType)
{
Type = ancestorType;
_level = level;
+ DataContextType = dataContextType;
}
public IXamlType Type { get; }
+ public IXamlType? DataContextType { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
@@ -573,17 +615,19 @@ public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
}
}
- class ElementNamePathElementNode : IXamlIlBindingPathElementNode
+ class ElementNamePathElementNode : IXamlIlBindingPathElementNode, IXamlIlBindingPathNodeWithDataContextType
{
private readonly string _name;
- public ElementNamePathElementNode(string name, IXamlType elementType)
+ public ElementNamePathElementNode(string name, IXamlType elementType, IXamlType? dataType)
{
_name = name;
Type = elementType;
+ DataContextType = dataType;
}
public IXamlType Type { get; }
+ public IXamlType? DataContextType { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 5e0813c9e6d..5f5c2f9fe52 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -1282,6 +1282,28 @@ public void SupportParentInPath()
}
}
+ [Fact]
+ public void SupportsParentInPathWithTypeAndLevelFilter()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ Assert.Equal("p1", textBlock.Text);
+ }
+ }
+
[Fact]
public void SupportConverterWithParameter()
{
@@ -2175,6 +2197,140 @@ public void Can_Bind_Brush_To_Hex_String()
}
}
+ [Fact]
+ public void ResolvesElementNameDataContextTypeBasedOnContext()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ var dataContext = new TestDataContext
+ {
+ StringProperty = "foobar"
+ };
+
+ window.DataContext = dataContext;
+
+ Assert.Equal(dataContext.StringProperty, textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void ResolvesElementNameDataContextTypeBasedOnContextShortSyntax()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ var dataContext = new TestDataContext
+ {
+ StringProperty = "foobar"
+ };
+
+ window.DataContext = dataContext;
+
+ Assert.Equal(dataContext.StringProperty, textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void TypeCastWorksWithElementNameDataContext()
+ {
+ // By default, DataContext will infer DataType from the XAML context, which will be local:TestDataContext here.
+ // But developer should be able to re-define this type via type casing, if they know better.
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ var panelDataContext = new Button { Tag = "foo" };
+ ((Panel)window.Content!).DataContext = panelDataContext;
+
+ Assert.Equal(panelDataContext.Tag, textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void ResolvesParentDataContextTypeBasedOnContext()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ var dataContext = new TestDataContext
+ {
+ StringProperty = "foobar"
+ };
+
+ window.DataContext = dataContext;
+
+ Assert.Equal(dataContext.StringProperty, textBlock.Text);
+ }
+ }
+
+ [Fact]
+ public void ResolvesParentDataContextTypeBasedOnContextShortSyntax()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
+
+
+
+
+");
+ var textBlock = window.GetControl("textBlock");
+
+ var dataContext = new TestDataContext
+ {
+ StringProperty = "foobar"
+ };
+
+ window.DataContext = dataContext;
+
+ Assert.Equal(dataContext.StringProperty, textBlock.Text);
+ }
+ }
+
static void Throws(string type, Action cb)
{
try