[Xaml] Set the TargetProperty on ServiceProvider (#847)
authorStephane Delcroix <stephane@delcroix.org>
Fri, 7 Apr 2017 07:48:17 +0000 (09:48 +0200)
committerGitHub <noreply@github.com>
Fri, 7 Apr 2017 07:48:17 +0000 (09:48 +0200)
Xamarin.Forms.Core.UnitTests/BaseTestFixture.cs
Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/TypeExtension.xaml.cs
Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/XamlServiceProvider.cs

index 69c3e32..23b7c2d 100644 (file)
@@ -11,6 +11,8 @@ namespace Xamarin.Forms.Core.UnitTests
                [SetUp]
                public virtual void Setup ()
                {
+                       Device.PlatformServices = new MockPlatformServices();
+
 #if !WINDOWS_PHONE
                        var culture = Environment.GetEnvironmentVariable ("UNIT_TEST_CULTURE");
                        
@@ -24,7 +26,7 @@ namespace Xamarin.Forms.Core.UnitTests
                [TearDown]
                public virtual void TearDown ()
                {
-                       
+                       Device.PlatformServices = null;
                }
        }
 }
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml
new file mode 100644 (file)
index 0000000..cab52a0
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
+       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+       xmlns:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests"
+       x:Class="Xamarin.Forms.Xaml.UnitTests.Bz53275"
+       ANonBindableProperty="{local:TargetProperty}">
+       <Label x:Name="label" Text="{local:TargetProperty}" />
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz53275.xaml.cs
new file mode 100644 (file)
index 0000000..ccef92d
--- /dev/null
@@ -0,0 +1,57 @@
+using System;
+using System.Reflection;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+       public class TargetPropertyExtension : IMarkupExtension
+       {
+               public object ProvideValue(IServiceProvider serviceProvider)
+               {
+                       var targetProperty = (serviceProvider?.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget)?.TargetProperty;
+                       return (targetProperty as BindableProperty)?.PropertyName ?? (targetProperty as PropertyInfo)?.Name;
+               }
+       }
+
+       public partial class Bz53275 : ContentPage
+       {
+               public Bz53275()
+               {
+                       InitializeComponent();
+               }
+
+               public Bz53275(bool useCompiledXaml)
+               {
+                       //this stub will be replaced at compile time
+               }
+
+               public string ANonBindableProperty { get; set; }
+
+               [TestFixture]
+               class Tests
+               {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
+                       [TestCase(true)]
+                       [TestCase(false)]
+                       public void TargetPropertyIsSetOnMarkups(bool useCompiledXaml)
+                       {
+                               var page = new Bz53275(useCompiledXaml);
+                               Assert.AreEqual("ANonBindableProperty", page.ANonBindableProperty);
+                               var l0 = page.label;
+                               Assert.AreEqual("Text", l0.Text);
+                       }
+               }
+       }
+}
\ No newline at end of file
index 6f9ef80..f1e0cd9 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Windows.Input;
 using Xamarin.Forms;
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
@@ -46,6 +47,18 @@ namespace Xamarin.Forms.Xaml.UnitTests
                [TestFixture]
                public class Tests
                {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
                        [TestCase(false)]
                        [TestCase(true)]
                        public void NestedMarkupExtensionInsideDataTemplate(bool useCompiledXaml)
index 24d2bbd..a2c9d49 100644 (file)
     <Compile Include="Issues\Bz41048.xaml.cs">
       <DependentUpon>Bz41048.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Issues\Bz53275.xaml.cs">
+      <DependentUpon>Bz53275.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" />
     <EmbeddedResource Include="Issues\Bz41048.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
+    <EmbeddedResource Include="Issues\Bz53275.xaml">
+      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+    </EmbeddedResource>
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
index 337b2ac..af7acaf 100644 (file)
@@ -77,7 +77,8 @@ namespace Xamarin.Forms.Xaml
                public void Visit(ElementNode node, INode parentNode)
                {
                        var propertyName = XmlName.Empty;
-                       if (TryGetPropertyName(node, parentNode, out propertyName) && propertyName == XmlName._CreateContent){
+
+                       if (TryGetPropertyName(node, parentNode, out propertyName) && propertyName == XmlName._CreateContent) {
                                var s0 = Values[parentNode];
                                if (s0 is ElementTemplate) {
                                        SetTemplate(s0 as ElementTemplate, node);
@@ -85,21 +86,7 @@ namespace Xamarin.Forms.Xaml
                                }
                        }
 
-                       var value = Values [node];
                        var parentElement = parentNode as IElementNode;
-                       var markupExtension = value as IMarkupExtension;
-                       var valueProvider = value as IValueProvider;
-
-                       if (markupExtension != null) {
-                               var serviceProvider = value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null ? new XamlServiceProvider(node, Context) : null;
-                               value = markupExtension.ProvideValue(serviceProvider);
-                       }
-
-                       if (valueProvider != null) {
-                               var serviceProvider = value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null ? new XamlServiceProvider(node, Context) : null;
-                               value = valueProvider.ProvideValue(serviceProvider);
-                       }
-
                        propertyName = XmlName.Empty;
 
                        //Simplify ListNodes with single elements
@@ -110,6 +97,15 @@ namespace Xamarin.Forms.Xaml
                                parentElement = parentNode as IElementNode;
                        }
 
+                       var value = Values[node];
+
+                       var markupExtension = value as IMarkupExtension;
+                       var valueProvider = value as IValueProvider;
+                       XamlServiceProvider serviceProvider = null;
+                       if (markupExtension != null || valueProvider != null)
+                               serviceProvider = value.GetType().GetTypeInfo().GetCustomAttribute<AcceptEmptyServiceProviderAttribute>() == null ? new XamlServiceProvider(node, Context) : null;
+
+
                        if (propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName)) {
                                if (Skips.Contains(propertyName))
                                        return;
@@ -117,8 +113,21 @@ namespace Xamarin.Forms.Xaml
                                        return;
 
                                var source = Values [parentNode];
+                               if (serviceProvider != null)
+                                       ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = GetTargetProperty(source, propertyName, Context, node);
+
+                               if (markupExtension != null)
+                                       value = markupExtension.ProvideValue(serviceProvider);
+                               else if (valueProvider != null)
+                                       value = valueProvider.ProvideValue(serviceProvider);
+
                                SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
                        } else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) {
+                               if (markupExtension != null)
+                                       value = markupExtension.ProvideValue(serviceProvider);
+                               else if (valueProvider != null)
+                                       value = valueProvider.ProvideValue(serviceProvider);
+
                                // Collection element, implicit content, or implicit collection element.
                                string contentProperty;
                                if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(Context.Types [parentElement].GetTypeInfo()) && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) {
@@ -140,6 +149,11 @@ namespace Xamarin.Forms.Xaml
                                } else
                                        throw new XamlParseException($"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute", node);
                        } else if (IsCollectionItem(node, parentNode) && parentNode is ListNode) {
+                               if (markupExtension != null)
+                                       value = markupExtension.ProvideValue(serviceProvider);
+                               else if (valueProvider != null)
+                                       value = valueProvider.ProvideValue(serviceProvider);
+
                                var parentList = (ListNode)parentNode;
                                var source = Values [parentNode.Parent];
 
@@ -269,6 +283,22 @@ namespace Xamarin.Forms.Xaml
                        return null;
                }
 
+               static object GetTargetProperty(object xamlelement, XmlName propertyName, HydratationContext context, IXmlLineInfo lineInfo)
+               {
+                       var localName = propertyName.LocalName;
+                       //If it's an attached BP, update elementType and propertyName
+                       var bpOwnerType = xamlelement.GetType();
+                       GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo);
+                       var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false);
+
+                       if (property != null)
+                               return property;
+                       
+                       var elementType = xamlelement.GetType();
+                       var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName);
+                       return propertyInfo;
+               }
+
                public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, object rootElement, INode node, HydratationContext context, IXmlLineInfo lineInfo)
                {
                        var localName = propertyName.LocalName;
@@ -397,6 +427,9 @@ namespace Xamarin.Forms.Xaml
                        if (property == null)
                                return false;
 
+                       if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
+                               ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = property;
+
                        Func<MemberInfo> minforetriever;
                        if (attached)
                                minforetriever = () => property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new [] { typeof(BindableObject) });
@@ -436,6 +469,9 @@ namespace Xamarin.Forms.Xaml
                        if (!IsVisibleFrom(setter, context.RootElement))
                                return false;
 
+                       if (serviceProvider != null && serviceProvider.IProvideValueTarget != null)
+                               ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = propertyInfo;
+
                        object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider);
                        if (convertedValue != null && !propertyInfo.PropertyType.IsInstanceOfType(convertedValue))
                                return false;
@@ -475,6 +511,7 @@ namespace Xamarin.Forms.Xaml
                        if (addMethod == null)
                                return false;
 
+                       ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = propertyInfo;
                        addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
                        return true;
                }
index 5d99f95..674795f 100644 (file)
@@ -103,7 +103,7 @@ namespace Xamarin.Forms.Xaml.Internals
 
                HydratationContext Context { get; }
                public object TargetObject { get; }
-               public object TargetProperty { get; } = null;
+               public object TargetProperty { get; internal set; } = null;
 
                IEnumerable<object> IProvideParentValues.ParentObjects
                {