diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs index 1f527e9569..03ec32b9cf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -121,7 +121,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } else { - var clrProperty = targetType.GetAllProperties().FirstOrDefault(p => p.Name == propName.PropertyName); + var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName); if (clrProperty is null) { @@ -139,15 +139,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } IXamlProperty property = null; - for (var currentType = targetType; currentType != null; currentType = currentType.BaseType) + foreach (var currentType in TraverseTypeHierarchy(targetType)) { var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute"); if (defaultMemberAttribute != null) { - property = targetType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]); + property = currentType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]); break; } - }; + } if (property is null) { throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo); @@ -252,6 +252,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false, lineInfo, true).GetClrType(); } + + static IEnumerable GetAllDefinedProperties(IXamlType type) + { + foreach (var t in TraverseTypeHierarchy(type)) + { + foreach (var p in t.Properties) + { + yield return p; + } + } + } + + static IEnumerable TraverseTypeHierarchy(IXamlType type) + { + if (type.IsInterface) + { + yield return type; + foreach (var iface in type.Interfaces) + { + foreach (var h in TraverseTypeHierarchy(iface)) + { + yield return h; + } + } + } + else + { + for (var currentType = type; currentType != null; currentType = currentType.BaseType) + { + yield return currentType; + } + } + } } class ScopeRegistrationFinder : IXamlAstVisitor diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 37aa123cfc..4e574f2b9e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -43,6 +43,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void ResolvesClrPropertyBasedOnDataContextType_InterfaceInheritance() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext + { + StringProperty = "foobar" + }; + + window.DataContext = dataContext; + + Assert.Equal(dataContext.StringProperty, textBlock.Text); + } + } + [Fact] public void ResolvesPathPassedByProperty() { @@ -274,6 +301,36 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void ResolvesNonIntegerIndexerBindingFromParentInterfaceCorrectly() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + var dataContext = new TestDataContext(); + + dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "Initial Value"; + + window.DataContext = dataContext; + + Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text); + + dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "New Value"; + + Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text); + } + } + [Fact] public void InfersDataTemplateTypeFromDataTypeProperty() { @@ -584,7 +641,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } - public class TestDataContext + public interface INonIntegerIndexer + { + string this[string key] {get; set;} + } + + public interface INonIntegerIndexerDerived : INonIntegerIndexer + {} + + public interface IHasProperty + { + string StringProperty {get; set; } + } + + public interface IHasPropertyDerived : IHasProperty + {} + + public class TestDataContext : IHasPropertyDerived { public string StringProperty { get; set; } @@ -600,7 +673,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer(); - public class NonIntegerIndexer : NotifyingBase + public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty; + + public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived { private readonly Dictionary _storage = new Dictionary();