diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs index eea32f2ba5c2..6a22389bb467 100644 --- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs @@ -382,14 +382,26 @@ static IEnumerable CompileBindingPath(ElementNode node, ILContext c INode dataTypeNode = null; IElementNode n = node; + + // Special handling for BindingContext={Binding ...} + // The order of checks is: + // - x:DataType on the binding itself + // - SKIP looking for x:DataType on the parent + // - continue looking for x:DataType on the parent's parent... + IElementNode skipNode = null; + if (IsBindingContextBinding(node)) + { + skipNode = GetParent(node); + } + while (n != null) { - if (n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode)) + if (n != skipNode && n.Properties.TryGetValue(XmlName.xDataType, out dataTypeNode)) + { break; - if (n.Parent is ListNode listNode) - n = listNode.Parent as IElementNode; - else - n = n.Parent as IElementNode; + } + + n = GetParent(n); } if (dataTypeNode is null) @@ -486,6 +498,25 @@ static IEnumerable CompileBindingPath(ElementNode node, ILContext c yield return Create(Ldnull); yield return Create(Newobj, module.ImportReference(ctorinforef)); yield return Create(Callvirt, module.ImportPropertySetterReference(context.Cache, bindingExtensionType, propertyName: "TypedBinding")); + + static IElementNode GetParent(IElementNode node) + { + return node switch + { + { Parent: ListNode { Parent: IElementNode parentNode } } => parentNode, + { Parent: IElementNode parentNode } => parentNode, + _ => null, + }; + } + + static bool IsBindingContextBinding(ElementNode node) + { + // looking for BindingContext="{Binding ...}" + return GetParent(node) is IElementNode parentNode + && ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out var propertyName) + && propertyName.NamespaceURI == "" + && propertyName.LocalName == nameof(BindableObject.BindingContext); + } } static IList<(PropertyDefinition property, TypeReference propDeclTypeRef, string indexArg)> ParsePath(ILContext context, string path, TypeReference tSourceRef, IXmlLineInfo lineInfo, ModuleDefinition module) diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.cs new file mode 100644 index 000000000000..2f968d27eb7b --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Dispatching; + +using Microsoft.Maui.UnitTests; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class Maui21434 +{ + public Maui21434() + { + InitializeComponent(); + } + + public Maui21434(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Test + { + [SetUp] + public void Setup() + { + Application.SetCurrentApplication(new MockApplication()); + DispatcherProvider.SetCurrent(new DispatcherProviderStub()); + } + + [TearDown] public void TearDown() => AppInfo.SetCurrent(null); + + [Test] + public void BindingsDoNotResolveStaticProperties([Values(false, true)] bool useCompiledXaml) + { + var page = new Maui21434(useCompiledXaml); + Assert.That(page.ParentTextLabel?.Text, Is.EqualTo("ParentText")); + Assert.That(page.ChildTextLabel?.Text, Is.EqualTo("ChildText")); + } + } +} + +public class ParentViewModel21434 +{ + public string Text => "ParentText"; + public ChildViewModel21434 Child { get; } = new(); +} + +public class ChildViewModel21434 +{ + public string Text => "ChildText"; +} diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.xaml new file mode 100644 index 000000000000..df0c2a6b91be --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21434.xaml @@ -0,0 +1,15 @@ + + + + + + + + +