Lazy creation of ResourceDictionaries (#1295)
authorStephane Delcroix <stephane@delcroix.org>
Wed, 22 Nov 2017 11:59:55 +0000 (12:59 +0100)
committerGitHub <noreply@github.com>
Wed, 22 Nov 2017 11:59:55 +0000 (12:59 +0100)
* [C,Xaml] allow implicit ResourceDictionaries

* [XamlC] implicit ResourceDictionaries

* [C] lazy Resources creation

* fix stupid docs

* [C] no need to trigger events on initial get

16 files changed:
Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
Xamarin.Forms.Core.UnitTests/NotifiedPropertiesTests.cs
Xamarin.Forms.Core/Application.cs
Xamarin.Forms.Core/IResourcesProvider.cs
Xamarin.Forms.Core/ResourcesExtensions.cs
Xamarin.Forms.Core/VisualElement.cs
Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml [deleted file]
Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml.cs [deleted file]
Xamarin.Forms.Xaml.UnitTests/SharedResourceDictionary.xaml.cs
Xamarin.Forms.Xaml.UnitTests/ViewExtensionsTest.cs
Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
docs/Xamarin.Forms.Core/Xamarin.Forms/Application.xml

index 22bbde7..f2f5945 100644 (file)
@@ -11,6 +11,9 @@ using Mono.Cecil.Rocks;
 using Xamarin.Forms.Internals;
 using Xamarin.Forms.Xaml;
 
+using static Mono.Cecil.Cil.Instruction;
+using static Mono.Cecil.Cil.OpCodes;
+
 namespace Xamarin.Forms.Build.Tasks
 {
        class SetPropertiesVisitor : IXamlNodeVisitor
@@ -125,41 +128,9 @@ namespace Xamarin.Forms.Build.Tasks
                                var parentVar = Context.Variables[(IElementNode)parentNode];
                                string contentProperty;
 
-                               // Implicit Style Resource in a ResourceDictionary
-                               if (IsResourceDictionary((IElementNode)parentNode)
-                                       && node.XmlType.Name == "Style"
-                                       && !node.Properties.ContainsKey(XmlName.xKey)) {
-                                       Context.IL.Emit(OpCodes.Ldloc, parentVar);
-                                       Context.IL.Emit(OpCodes.Ldloc, Context.Variables[node]);
-                                       Context.IL.Emit(OpCodes.Callvirt,
-                                               Module.ImportReference(
-                                                       Module.ImportReference(typeof(ResourceDictionary))
-                                                               .Resolve()
-                                                               .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 1)));
-                               }
-                               // Resource without a x:Key in a ResourceDictionary
-                               else if (IsResourceDictionary((IElementNode)parentNode)
-                                                && !node.Properties.ContainsKey(XmlName.xKey)) {
-                                       throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", node);
-                               }
-                               // Resource in a ResourceDictionary
-                               else if (IsResourceDictionary((IElementNode)parentNode)
-                                                && node.Properties.ContainsKey(XmlName.xKey)) {
-//                                     IL_0013: ldloc.0
-//                                     IL_0014:  ldstr "key"
-//                                     IL_0019:  ldstr "foo"
-//                                     IL_001e:  callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object)
-                                       Context.IL.Emit(OpCodes.Ldloc, parentVar);
-                                       Context.IL.Emit(OpCodes.Ldstr, (node.Properties[XmlName.xKey] as ValueNode).Value as string);
-                                       var varDef = Context.Variables[node];
-                                       Context.IL.Emit(OpCodes.Ldloc, varDef);
-                                       if (varDef.VariableType.IsValueType)
-                                               Context.IL.Emit(OpCodes.Box, Module.ImportReference(varDef.VariableType));
-                                       Context.IL.Emit(OpCodes.Callvirt,
-                                               Module.ImportReference(
-                                                       Module.ImportReference(typeof(ResourceDictionary))
-                                                               .Resolve()
-                                                               .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 2)));
+                               if (CanAddToResourceDictionary(parentVar.VariableType, node, node, Context)) {
+                                       Context.IL.Emit(Ldloc, parentVar);
+                                       Context.IL.Append(AddToResourceDictionary(node, node, Context));
                                }
                                // Collection element, implicit content, or implicit collection element.
                                else if (parentVar.VariableType.ImplementsInterface(Module.ImportReference(typeof (IEnumerable))) && parentVar.VariableType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).Any()) {
@@ -205,6 +176,10 @@ namespace Xamarin.Forms.Build.Tasks
                                TypeReference propertyType;
                                Context.IL.Append(GetPropertyValue(parent, parentList.XmlName, Context, node, out propertyType));
 
+                               if (CanAddToResourceDictionary(propertyType, node, node, Context)) {
+                                       Context.IL.Append(AddToResourceDictionary(node, node, Context));
+                                       return;
+                               } 
                                var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, Module).First();
                                var adderRef = Module.ImportReference(adderTuple.Item1);
                                adderRef = Module.ImportReference(adderRef.ResolveGenericParameters(adderTuple.Item2, Module));
@@ -224,13 +199,6 @@ namespace Xamarin.Forms.Build.Tasks
                {
                }
 
-               bool IsResourceDictionary(IElementNode node)
-               {
-                       var parentVar = Context.Variables[(IElementNode)node];
-                       return parentVar.VariableType.FullName == "Xamarin.Forms.ResourceDictionary"
-                               || parentVar.VariableType.Resolve().BaseType?.FullName == "Xamarin.Forms.ResourceDictionary";
-               }
-
                public static bool TryGetPropertyName(INode node, INode parentNode, out XmlName name)
                {
                        name = default(XmlName);
@@ -1173,6 +1141,21 @@ namespace Xamarin.Forms.Build.Tasks
                        return true;
                }
 
+               static bool CanAddToResourceDictionary(TypeReference collectionType, IElementNode node, IXmlLineInfo lineInfo, ILContext context)
+               {
+                       if (   collectionType.FullName != "Xamarin.Forms.ResourceDictionary"
+                           && collectionType.Resolve().BaseType?.FullName != "Xamarin.Forms.ResourceDictionary")
+                               return false;
+
+                       if (node.Properties.ContainsKey(XmlName.xKey))
+                               return true;
+
+                       if (node.XmlType.Name == "Style")
+                               return true;
+
+                       throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", lineInfo);
+               }
+
                static IEnumerable<Instruction> Add(VariableDefinition parent, XmlName propertyName, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
                {
                        var module = context.Body.Method.Module;
@@ -1183,6 +1166,12 @@ namespace Xamarin.Forms.Build.Tasks
                        foreach (var instruction in GetPropertyValue(parent, propertyName, context, iXmlLineInfo, out propertyType))
                                yield return instruction;
 
+                       if (CanAddToResourceDictionary(propertyType, elementNode, iXmlLineInfo, context)) {
+                               foreach (var instruction in AddToResourceDictionary(elementNode, iXmlLineInfo, context))
+                                       yield return instruction;
+                               yield break;
+                       }
+
                        var adderTuple = propertyType.GetMethods(md => md.Name == "Add" && md.Parameters.Count == 1, module).FirstOrDefault();
                        var adderRef = module.ImportReference(adderTuple.Item1);
                        adderRef = module.ImportReference(adderRef.ResolveGenericParameters(adderTuple.Item2, module));
@@ -1199,6 +1188,37 @@ namespace Xamarin.Forms.Build.Tasks
                                yield return Instruction.Create(OpCodes.Pop);
                }
 
+               static IEnumerable<Instruction> AddToResourceDictionary(IElementNode node, IXmlLineInfo lineInfo, ILContext context)
+               {
+                       var module = context.Body.Method.Module;
+
+                       if (node.Properties.ContainsKey(XmlName.xKey)) {
+//                             IL_0014:  ldstr "key"
+//                             IL_0019:  ldstr "foo"
+//                             IL_001e:  callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ResourceDictionary::Add(string, object)
+                               yield return Create(Ldstr, (node.Properties[XmlName.xKey] as ValueNode).Value as string);
+                               var varDef = context.Variables[node];
+                               yield return Create(Ldloc, varDef);
+                               if (varDef.VariableType.IsValueType)
+                                       yield return Create(Box, module.ImportReference(varDef.VariableType));
+                               yield return Create(Callvirt,
+                                       module.ImportReference(
+                                               module.ImportReference(typeof(ResourceDictionary))
+                                                       .Resolve()
+                                                       .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 2)));
+                               yield break;
+                       }
+                       if (node.XmlType.Name == "Style") {
+                               yield return Create(Ldloc, context.Variables[node]);
+                               yield return Create(Callvirt,
+                                       module.ImportReference(
+                                               module.ImportReference(typeof(ResourceDictionary))
+                                                       .Resolve()
+                                                       .Methods.Single(md => md.Name == "Add" && md.Parameters.Count == 1)));
+                               yield break;
+                       }
+               }
+
                public static TypeReference GetParameterType(ParameterDefinition param)
                {
                        if (!param.ParameterType.IsGenericParameter)
index 1c81c45..88a3c7a 100644 (file)
@@ -56,7 +56,6 @@ namespace Xamarin.Forms.Core.UnitTests
 
 #pragma warning disable 0414
                static PropertyTestCase[] Properties = {
-                       new PropertyTestCase<View, ResourceDictionary> ("Resources", v => v.Resources, (v, o) => v.Resources = o, () => null, new ResourceDictionary ()),
                        new PropertyTestCase<View, bool> ("InputTransparent", v => v.InputTransparent, (v, o) => v.InputTransparent = o, () => false, true),
                        new PropertyTestCase<View, double> ("Scale", v => v.Scale, (v, o) => v.Scale = o, () => 1d, 2d),
                        new PropertyTestCase<View, double> ("Rotation", v => v.Rotation, (v, o) => v.Rotation = o, () => 0d, 90d),
index b3ca76d..95462f1 100644 (file)
@@ -21,7 +21,6 @@ namespace Xamarin.Forms
 
                Page _mainPage;
 
-               ResourceDictionary _resources;
                static SemaphoreSlim SaveSemaphore = new SemaphoreSlim(1, 1);
 
                protected Application()
@@ -134,9 +133,19 @@ namespace Xamarin.Forms
                        _appIndexProvider = provider;
                }
 
+               ResourceDictionary _resources;
+               bool IResourcesProvider.IsResourcesCreated => _resources != null;
+
                public ResourceDictionary Resources
                {
-                       get { return _resources; }
+                       get {
+                               if (_resources != null)
+                                       return _resources;
+
+                               _resources = new ResourceDictionary();
+                               ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged;
+                               return _resources;
+                       }
                        set
                        {
                                if (_resources == value)
@@ -208,7 +217,7 @@ namespace Xamarin.Forms
 
                internal override void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
                {
-                       if (Resources == null || Resources.Count == 0)
+                       if (!((IResourcesProvider)this).IsResourcesCreated || Resources.Count == 0)
                        {
                                base.OnParentResourcesChanged(values);
                                return;
index e06ef74..3285383 100644 (file)
@@ -1,7 +1,8 @@
 namespace Xamarin.Forms
 {
-       internal interface IResourcesProvider
+       interface IResourcesProvider
        {
+               bool IsResourcesCreated { get; }
                ResourceDictionary Resources { get; set; }
        }
 }
\ No newline at end of file
index 7d2f8d0..1962c3a 100644 (file)
@@ -11,7 +11,7 @@ namespace Xamarin.Forms
                        while (element != null)
                        {
                                var ve = element as IResourcesProvider;
-                               if (ve != null && ve.Resources != null)
+                               if (ve != null && ve.IsResourcesCreated)
                                {
                                        resources = resources ?? new Dictionary<string, object>();
                                        foreach (KeyValuePair<string, object> res in ve.Resources.MergedResources)
@@ -48,7 +48,7 @@ namespace Xamarin.Forms
                        while (element != null)
                        {
                                var ve = element as IResourcesProvider;
-                               if (ve != null && ve.Resources != null && ve.Resources.TryGetValue(key, out value))
+                               if (ve != null && ve.IsResourcesCreated && ve.Resources.TryGetValue(key, out value))
                                        return true;
                                var app = element as Application;
                                if (app != null && app.SystemResources != null && app.SystemResources.TryGetValue(key, out value))
@@ -57,7 +57,7 @@ namespace Xamarin.Forms
                        }
 
                        //Fallback for the XF previewer
-                       if (Application.Current != null && Application.Current.Resources != null && Application.Current.Resources.TryGetValue(key, out value))
+                       if (Application.Current != null && ((IResourcesProvider)Application.Current).IsResourcesCreated && Application.Current.Resources.TryGetValue(key, out value))
                                return true;
 
                        value = null;
index a6acc43..a66cb24 100644 (file)
@@ -141,7 +141,6 @@ namespace Xamarin.Forms
 
                double _mockY = -1;
 
-               ResourceDictionary _resources;
                LayoutConstraint _selfConstraint;
 
                internal VisualElement()
@@ -443,9 +442,18 @@ namespace Xamarin.Forms
                                BatchCommitted(this, new EventArg<VisualElement>(this));
                }
 
+               ResourceDictionary _resources;
+               bool IResourcesProvider.IsResourcesCreated => _resources != null;
+
                public ResourceDictionary Resources
                {
-                       get { return _resources; }
+                       get {
+                               if (_resources != null)
+                                       return _resources;
+                               _resources = new ResourceDictionary();
+                               ((IResourceDictionary)_resources).ValuesChanged += OnResourcesChanged;
+                               return _resources;
+                       }
                        set
                        {
                                if (_resources == value)
@@ -727,7 +735,7 @@ namespace Xamarin.Forms
                        if (values == null)
                                return;
 
-                       if (Resources == null || Resources.Count == 0)
+                       if (!((IResourcesProvider)this).IsResourcesCreated || Resources.Count == 0)
                        {
                                base.OnParentResourcesChanged(values);
                                return;
diff --git a/Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml b/Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml
new file mode 100644 (file)
index 0000000..55dbd22
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage 
+    xmlns="http://xamarin.com/schemas/2014/forms"
+    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+    x:Class="Xamarin.Forms.Xaml.UnitTests.ImplicitResourceDictionaries">
+    <ContentPage.Resources>
+        <Color x:Key="notpink">Purple</Color>
+        <Color x:Key="pink">Pink</Color>
+    </ContentPage.Resources>
+    <Label x:Name="label" TextColor="{StaticResource notpink}"/>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/ImplicitResourceDictionaries.xaml.cs
new file mode 100644 (file)
index 0000000..6c9f724
--- /dev/null
@@ -0,0 +1,43 @@
+using System;
+using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+       public partial class ImplicitResourceDictionaries : ContentPage
+       {
+               public ImplicitResourceDictionaries()
+               {
+                       InitializeComponent();
+               }
+
+               public ImplicitResourceDictionaries(bool useCompiledXaml)
+               {
+                       //this stub will be replaced at compile time
+               }
+
+               [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 ImplicitRDonContentViews(bool useCompiledXaml)
+                       {
+                               var layout = new ImplicitResourceDictionaries(useCompiledXaml);
+                               Assert.That(layout.label.TextColor, Is.EqualTo(Color.Purple));
+                       }
+               }
+       }
+}
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml
deleted file mode 100644 (file)
index 3bb6e20..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Xamarin.Forms.Xaml.UnitTests.Bz49307">
-       <Application.Resources>
-               <!-- Application resource dictionary -->
-               <Color x:Key="MyColor">#c2d1d3</Color>
-       </Application.Resources>
-</Application>
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz49307.xaml.cs
deleted file mode 100644 (file)
index 166706a..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Collections.Generic;
-using NUnit.Framework;
-using Xamarin.Forms;
-using Xamarin.Forms.Core.UnitTests;
-
-namespace Xamarin.Forms.Xaml.UnitTests
-{
-       public partial class Bz49307 : Application
-       {
-               public Bz49307()
-               {
-                       InitializeComponent();
-               }
-
-               public Bz49307(bool useCompiledXaml)
-               {
-                       //this stub will be replaced at compile time
-               }
-
-               [TestFixture]
-               class Tests
-               {
-                       [SetUp]
-                       public void Setup()
-                       {
-                               Device.PlatformServices = new MockPlatformServices();
-                       }
-
-                       [TearDown]
-                       public void TearDown()
-                       {
-                               Device.PlatformServices = null;
-                       }
-
-                       [TestCase(true)]
-                       [TestCase(false)]
-                       public void ThrowOnMissingDictionary(bool useCompiledXaml)
-                       {
-                               if (useCompiledXaml)
-                                       Assert.Throws<NullReferenceException>(() => new Bz49307(useCompiledXaml));
-                               else
-                                       Assert.Throws(new XamlParseExceptionConstraint(5, 4), () => new Bz49307(useCompiledXaml));
-                       }
-               }
-       }
-}
\ No newline at end of file
index aec2f4b..9e5c3f1 100644 (file)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using Xamarin.Forms;
 
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
@@ -22,6 +23,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 ResourcesDirectoriesCanBeXamlRoots (bool useCompiledXaml)
index 57c6e35..37afd89 100644 (file)
@@ -25,7 +25,7 @@ namespace Xamarin.Forms.Xaml.UnitTests
                public void TestResourceNotFound ()
                {
                        var view = new View ();
-                       var resource = view.Resources!= null ? view.Resources ["foo"] : null;
+                       var resource = ((IResourcesProvider)view).IsResourcesCreated ? view.Resources ["foo"] : null;
                        Assert.Null (resource);
                }
 
index 62d094d..636ec1c 100644 (file)
     <Compile Include="I8.xaml.cs">
       <DependentUpon>I8.xaml</DependentUpon>
     </Compile>
-    <Compile Include="Issues\Bz49307.xaml.cs">
-      <DependentUpon>Bz49307.xaml</DependentUpon>
-    </Compile>
     <Compile Include="Issues\Unreported008.xaml.cs">
       <DependentUpon>Unreported008.xaml</DependentUpon>
     </Compile>
     <Compile Include="Issues\Bz60575.xaml.cs">
       <DependentUpon>Bz60575.xaml</DependentUpon>
     </Compile>
-    <Compile Include="ResourceLoader.xaml.cs" >
+    <Compile Include="ResourceLoader.xaml.cs">
       <DependentUpon>ResourceLoader.xaml</DependentUpon>
     </Compile>
     <Compile Include="Issues\Bz60203.xaml.cs">
     <Compile Include="Issues\Bz55862.xaml.cs">
       <DependentUpon>Bz55862.xaml</DependentUpon>
     </Compile>
+    <Compile Include="ImplicitResourceDictionaries.xaml.cs">
+      <DependentUpon>ImplicitResourceDictionaries.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" />
     <EmbeddedResource Include="I8.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
-    <EmbeddedResource Include="Issues\Bz49307.xaml">
-      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
-    </EmbeddedResource>
     <EmbeddedResource Include="Issues\Unreported008.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
     <EmbeddedResource Include="Issues\Bz60575.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
-    <EmbeddedResource Include="ResourceLoader.xaml" >
+    <EmbeddedResource Include="ResourceLoader.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
     <EmbeddedResource Include="Issues\Bz60203.xaml">
     <EmbeddedResource Include="Issues\Bz55862.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
+    <EmbeddedResource Include="ImplicitResourceDictionaries.xaml">
+      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+    </EmbeddedResource>
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
index d539701..461a13f 100644 (file)
@@ -98,33 +98,29 @@ namespace Xamarin.Forms.Xaml
                                if (parentElement.SkipProperties.Contains(propertyName))
                                        return;
 
-                               var source = Values [parentNode];
+                               var source = Values[parentNode];
                                ProvideValue(ref value, node, source, propertyName);
                                SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
-                       } else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) {
+                       }
+                       else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode) {
                                var source = Values[parentNode];
                                ProvideValue(ref value, node, source, XmlName.Empty);
                                string contentProperty;
+                               Exception xpe = null;
+                               var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null;
+
+                               //ResourceDictionary
+                               if (xpe == null && TryAddToResourceDictionary(source as ResourceDictionary, value, xKey, node, out xpe))
+                                       return;
 
-                               // Implicit Style Resource in a ResourceDictionary
-                               if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[parentElement]) && value is Style &&
-                                               !node.Properties.ContainsKey(XmlName.xKey)) {
-                                       ((ResourceDictionary)source).Add(value as Style);
-                               }
-                               // Resource without a x:Key in a ResourceDictionary
-                               else if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[parentElement]) && !node.Properties.ContainsKey(XmlName.xKey))
-                                       throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", node);
-                               // Resource in a ResourceDictionary
-                               else if (typeof(ResourceDictionary).IsAssignableFrom(Context.Types[parentElement]) && node.Properties.ContainsKey(XmlName.xKey))
-                                       ((ResourceDictionary)source).Add((string)(((ValueNode)node.Properties[XmlName.xKey]).Value), value);
                                // Collection element, implicit content, or implicit collection element.
-                               else if (typeof(IEnumerable).IsAssignableFrom(Context.Types [parentElement]) && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) {
-                                       if (!(typeof(ResourceDictionary).IsAssignableFrom(Context.Types [parentElement]))) {
-                                               var addMethod =
-                                                       Context.Types [parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
-                                               addMethod.Invoke(source, new [] { value });
-                                       }
-                               } else if ((contentProperty = GetContentPropertyName(Context.Types [parentElement].GetTypeInfo())) != null) {
+                               if (xpe == null && typeof(IEnumerable).IsAssignableFrom(Context.Types[parentElement]) && Context.Types[parentElement].GetRuntimeMethods().Any(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) {
+                                       var addMethod =
+                                               Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
+                                       addMethod.Invoke(source, new[] { value });
+                                       return;
+                               }
+                               if (xpe == null && (contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo())) != null) {
                                        var name = new XmlName(node.NamespaceURI, contentProperty);
                                        if (Skips.Contains(name))
                                                return;
@@ -132,32 +128,44 @@ namespace Xamarin.Forms.Xaml
                                                return;
 
                                        SetPropertyValue(source, name, value, Context.RootElement, node, Context, node);
-                               } 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) {
-                               var source = Values [parentNode.Parent];
+                                       return;
+                               }
+                               xpe = xpe ?? new XamlParseException($"Can not set the content of {((IElementNode)parentNode).XmlType.Name} as it doesn't have a ContentPropertyAttribute", node);
+                               if (Context.ExceptionHandler != null)
+                                       Context.ExceptionHandler(xpe);
+                               else
+                                       throw xpe;
+                       }
+                       else if (IsCollectionItem(node, parentNode) && parentNode is ListNode) {
+                               var source = Values[parentNode.Parent];
                                ProvideValue(ref value, node, source, XmlName.Empty);
-
                                var parentList = (ListNode)parentNode;
-
                                if (Skips.Contains(parentList.XmlName))
                                        return;
+                               Exception xpe = null;
+                               var xKey = node.Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)node.Properties[XmlName.xKey]).Value as string : null;
 
                                object _;
                                var collection = GetPropertyValue(source, parentList.XmlName, Context, parentList, out _) as IEnumerable;
                                if (collection == null)
-                                       throw new XamlParseException($"Property {parentList.XmlName.LocalName} is null or is not IEnumerable", node);
+                                       xpe = new XamlParseException($"Property {parentList.XmlName.LocalName} is null or is not IEnumerable", node);
 
-                               MethodInfo addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
-                               if (addMethod == null)
-                                       throw new XamlParseException($"Value of {parentList.XmlName.LocalName} does not have a Add() method", node);
+                               if (xpe == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, node, out xpe))
+                                       return;
 
-                               addMethod.Invoke(collection, new [] { Values [node] });
+                               MethodInfo addMethod;
+                               if (xpe == null && (addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) != null) {
+                                       addMethod.Invoke(collection, new[] { Values[node] });
+                                       return;
+                               }
+                               xpe = xpe ?? new XamlParseException($"Value of {parentList.XmlName.LocalName} does not have a Add() method", node);
+                               if (Context.ExceptionHandler != null)
+                                       Context.ExceptionHandler(xpe);
+                               else
+                                       throw xpe;
                        }
                }
 
-
-
                public void Visit(RootNode node, INode parentNode)
                {
                }
@@ -290,6 +298,8 @@ namespace Xamarin.Forms.Xaml
                        var localName = propertyName.LocalName;
                        var serviceProvider = new XamlServiceProvider(node, context);
                        Exception xpe = null;
+                       var xKey = node is IElementNode && ((IElementNode)node).Properties.ContainsKey(XmlName.xKey) ? ((ValueNode)((IElementNode)node).Properties[XmlName.xKey]).Value as string : null;
+
 
                        //If it's an attached BP, update elementType and propertyName
                        var bpOwnerType = xamlelement.GetType();
@@ -317,7 +327,7 @@ namespace Xamarin.Forms.Xaml
                                return;
 
                        //If it's an already initialized property, add to it
-                       if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, lineInfo, serviceProvider, context, out xpe))
+                       if (xpe == null && TryAddToProperty(xamlelement, propertyName, value, xKey, lineInfo, serviceProvider, context, out xpe))
                                return;
 
                        xpe = xpe ?? new XamlParseException($"Cannot assign property \"{localName}\": Property does not exists, or is not assignable, or mismatching type between value and property", lineInfo);
@@ -553,7 +563,7 @@ namespace Xamarin.Forms.Xaml
                        return false;
                }
 
-               static bool TryAddToProperty(object element, XmlName propertyName, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception)
+               static bool TryAddToProperty(object element, XmlName propertyName, object value, string xKey, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, HydrationContext context, out Exception exception)
                {
                        exception = null;
 
@@ -562,15 +572,41 @@ namespace Xamarin.Forms.Xaml
                        if (collection == null)
                                return false;
 
+                       if (exception == null && TryAddToResourceDictionary(collection as ResourceDictionary, value, xKey, lineInfo, out exception))
+                               return true;
+
+                       if (exception != null)
+                               return false;
+
                        var addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
                        if (addMethod == null)
                                return false;
 
-                       ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = targetProperty;
+                       if (serviceProvider != null)
+                               ((XamlValueTargetProvider)serviceProvider.IProvideValueTarget).TargetProperty = targetProperty;
+
                        addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
                        return true;
                }
 
+               static bool TryAddToResourceDictionary(ResourceDictionary resourceDictionary, object value, string xKey, IXmlLineInfo lineInfo, out Exception exception)
+               {
+                       exception = null;
+
+                       if (resourceDictionary == null)
+                               return false;
+
+                       if (xKey != null)
+                               resourceDictionary.Add(xKey, value);
+                       else if (value is Style)
+                               resourceDictionary.Add((Style)value);
+                       else {
+                               exception = new XamlParseException("resources in ResourceDictionary require a x:Key attribute", lineInfo);
+                               return false;
+                       }
+                       return true;
+               }
+
                void SetTemplate(ElementTemplate dt, INode node)
                {
 #pragma warning disable 0612
index 926d956..b8840b0 100644 (file)
@@ -28,8 +28,8 @@ namespace Xamarin.Forms.Xaml
                        object resource = null;
 
                        foreach (var p in valueProvider.ParentObjects) {
-                               var ve = p as VisualElement;
-                               var resDict = ve?.Resources ?? p as ResourceDictionary;
+                               var irp = p as IResourcesProvider;
+                               var resDict = irp != null && irp.IsResourcesCreated ? irp.Resources : p as ResourceDictionary;
                                if (resDict == null)
                                        continue;
                                if (resDict.TryGetValue(Key, out resource))
@@ -82,7 +82,7 @@ namespace Xamarin.Forms.Xaml
                internal object GetApplicationLevelResource(string key, IXmlLineInfo xmlLineInfo)
                {
                        object resource;
-                       if (Application.Current == null || Application.Current.Resources == null || !Application.Current.Resources.TryGetValue(Key, out resource))
+                       if (Application.Current == null || !((IResourcesProvider)Application.Current).IsResourcesCreated || !Application.Current.Resources.TryGetValue(Key, out resource))
                                throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
                        return resource;
                }
index 57a9636..f763d45 100644 (file)
           <AttributeName>System.Diagnostics.DebuggerStepThrough</AttributeName>
         </Attribute>
         <Attribute>
-          <AttributeName>System.Runtime.CompilerServices.AsyncStateMachine(typeof(Xamarin.Forms.Application/&lt;SavePropertiesAsync&gt;d__52))</AttributeName>
+          <AttributeName>System.Runtime.CompilerServices.AsyncStateMachine(typeof(Xamarin.Forms.Application/&lt;SavePropertiesAsync&gt;d__54))</AttributeName>
         </Attribute>
       </Attributes>
       <ReturnValue>