diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs index 76d099ab1f74..c5559fb81f6c 100644 --- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs @@ -291,11 +291,19 @@ public static IEnumerable ProvideValue(VariableDefinitionReference if (!acceptEmptyServiceProvider && requireServiceAttribute == null) context.LoggingHelper.LogWarningOrError(BuildExceptionCode.UnattributedMarkupType, context.XamlFilePath, node.LineNumber, node.LinePosition, 0, 0, vardefref.VariableDefinition.VariableType); - if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.BindingExtension" - && bpRef != null //do not compile bindings if we're not gonna SetBinding - ) - foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition)) - yield return instruction; + if (bpRef is not null) // do not compile bindings if we're not gonna SetBinding + { + if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.BindingExtension") + { + foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "BindingExtension"))) + yield return instruction; + } + else if (vardefref.VariableDefinition.VariableType.FullName == "Microsoft.Maui.Controls.Xaml.TemplateBindingExtension") + { + foreach (var instruction in CompileBindingPath(node, context, vardefref.VariableDefinition, ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "TemplateBindingExtension"))) + yield return instruction; + } + } var markExt = markupExtension.ResolveCached(context.Cache); var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); @@ -383,7 +391,7 @@ public static IEnumerable ProvideValue(VariableDefinitionReference } //Once we get compiled IValueProvider, this will move to the BindingExpression - static IEnumerable CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt) + static IEnumerable CompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt, (string, string, string) bindingExtensionType) { //TODO support casting operators var module = context.Module; @@ -480,8 +488,6 @@ static IEnumerable CompileBindingPath(ElementNode node, ILContext c && !md.HasCustomAttributes(module.ImportReference(context.Cache, ("mscorlib", "System", "ObsoleteAttribute"))))); var ctorinforef = ctorInfo.MakeGeneric(typedBindingRef, funcRef, actionRef, tupleRef); - var bindingExtensionType = ("Microsoft.Maui.Controls.Xaml", "Microsoft.Maui.Controls.Xaml", "BindingExtension"); - foreach (var instruction in bindingExt.LoadAs(context.Cache, module.GetTypeDefinition(context.Cache, bindingExtensionType), module)) yield return instruction; foreach (var instruction in CompiledBindingGetGetter(tSourceRef, tPropertyRef, properties, node, context)) diff --git a/src/Controls/src/Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/TemplateBindingExtension.cs index 1a6782a216a7..22985b7c45cb 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/TemplateBindingExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/TemplateBindingExtension.cs @@ -1,5 +1,7 @@ using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using Microsoft.Maui.Controls.Internals; namespace Microsoft.Maui.Controls.Xaml { @@ -24,17 +26,33 @@ public TemplateBindingExtension() public string StringFormat { get; set; } + [EditorBrowsable(EditorBrowsableState.Never)] public TypedBindingBase TypedBinding { get; set; } + BindingBase IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) { - return new Binding + if (TypedBinding is null) { - Source = RelativeBindingSource.TemplatedParent, - Path = Path, - Mode = Mode, - Converter = Converter, - ConverterParameter = ConverterParameter, - StringFormat = StringFormat - }; +#pragma warning disable IL2026 // Using member 'Microsoft.Maui.Controls.Binding.Binding(String, BindingMode, IValueConverter, Object, String, Object)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Using bindings with string paths is not trim safe. Use expression-based binding instead. + // This code is only reachable in XamlC compiled code when there is a missing x:DataType and the binding could not be compiled. + // In that case, we produce a warning that the binding could not be compiled. + return new Binding + { + Source = RelativeBindingSource.TemplatedParent, + Path = Path, + Mode = Mode, + Converter = Converter, + ConverterParameter = ConverterParameter, + StringFormat = StringFormat + }; +#pragma warning restore IL2026 + } + + TypedBinding.Mode = Mode; + TypedBinding.Converter = Converter; + TypedBinding.ConverterParameter = ConverterParameter; + TypedBinding.StringFormat = StringFormat; + TypedBinding.Source = RelativeBindingSource.TemplatedParent; + return TypedBinding; } object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) diff --git a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-android/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-ios/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-tizen/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net-windows/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/net/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Shipped.txt b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Shipped.txt index 42e4a381cf22..044edea79c2c 100644 --- a/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Shipped.txt +++ b/src/Controls/src/Xaml/PublicAPI/netstandard/PublicAPI.Shipped.txt @@ -27,6 +27,8 @@ ~Microsoft.Maui.Controls.Xaml.BindingExtension.TargetNullValue.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase ~Microsoft.Maui.Controls.Xaml.BindingExtension.TypedBinding.set -> void +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.get -> Microsoft.Maui.Controls.Internals.TypedBindingBase +~Microsoft.Maui.Controls.Xaml.TemplateBindingExtension.TypedBinding.set -> void ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.get -> string ~Microsoft.Maui.Controls.Xaml.BindingExtension.UpdateSourceEventName.set -> void ~Microsoft.Maui.Controls.Xaml.DataTemplateExtension.ProvideValue(System.IServiceProvider serviceProvider) -> Microsoft.Maui.Controls.DataTemplate diff --git a/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml b/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml new file mode 100644 index 000000000000..f22f86643ac8 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml.cs b/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml.cs new file mode 100644 index 000000000000..89b9e4217761 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/TemplateBindingsCompiler.xaml.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Dispatching; +using Microsoft.Maui.UnitTests; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests +{ + public partial class TemplateBindingsCompiler : ContentPage + { + public TemplateBindingsCompiler() + { + InitializeComponent(); + } + + public TemplateBindingsCompiler(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + public class Tests + { + [SetUp] public void Setup() => DispatcherProvider.SetCurrent(new DispatcherProviderStub()); + [TearDown] public void TearDown() => DispatcherProvider.SetCurrent(null); + + [TestCase(false)] + [TestCase(true)] + public void Test(bool useCompiledXaml) + { + var page = new TemplateBindingsCompiler(useCompiledXaml); + var label = (Label)page.ContentView.GetTemplateChild("CardTitleLabel"); + Assert.AreEqual("The title", label?.Text); + + if (useCompiledXaml) + { + var binding = label.GetContext(Label.TextProperty).Bindings.GetValue(); + Assert.That(binding, Is.TypeOf>()); + } + } + } + } + + public class TemplateBindingCompilerTestCardView : ContentView + { + public static readonly BindableProperty CardTitleProperty = + BindableProperty.Create(nameof(CardTitle), typeof(string), typeof(TemplateBindingCompilerTestCardView), string.Empty); + + public string CardTitle + { + get => (string)GetValue(CardTitleProperty); + set => SetValue(CardTitleProperty, value); + } + } +} \ No newline at end of file