[Xaml] support native views and native bindings (#266)
authorStephane Delcroix <stephane@delcroix.org>
Thu, 8 Sep 2016 18:45:43 +0000 (20:45 +0200)
committerJason Smith <jason.smith@xamarin.com>
Thu, 8 Sep 2016 18:45:43 +0000 (11:45 -0700)
Allows including Native views directly in xaml.
Support for ios, android, UWP

29 files changed:
Xamarin.Forms.Build.Tasks/XamlGTask.cs
Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs
Xamarin.Forms.Controls/CoreGallery.cs
Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml [new file with mode: 0644]
Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj
Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs
Xamarin.Forms.Core/INativeBindingService.cs [new file with mode: 0644]
Xamarin.Forms.Core/INativeValueConverterService.cs [new file with mode: 0644]
Xamarin.Forms.Core/Xamarin.Forms.Core.csproj
Xamarin.Forms.Core/XamlParseException.cs
Xamarin.Forms.Platform.Android/NativeBindingservice.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/NativeValueConverterService.cs [new file with mode: 0644]
Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj
Xamarin.Forms.Platform.UAP/NativeBindingService.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/NativeValueConverterService.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
Xamarin.Forms.Platform.iOS/NativeBindingService.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs [new file with mode: 0644]
Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj
Xamarin.Forms.Xaml.UnitTests/Issues/BPNotResolvedOnSubClass.xaml.cs
Xamarin.Forms.Xaml.UnitTests/Issues/Issue1497.cs
Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/TypeConversionExtensions.cs
Xamarin.Forms.Xaml/XamlParser.cs
Xamarin.Forms.Xaml/XmlnsHelper.cs

index 72375d6..79a49c4 100644 (file)
@@ -90,8 +90,8 @@ namespace Xamarin.Forms.Build.Tasks
                                return;
                        }
 
-                       string rootAsm;
-                       XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm);
+                       string rootAsm, targetPlatform;
+                       XmlnsHelper.ParseXmlns(rootClass.Value, out rootType, out rootNs, out rootAsm, out targetPlatform);
                        namesAndTypes = GetNamesAndTypes(root, nsmgr);
 
                        var typeArguments = root.Attributes["TypeArguments", "http://schemas.microsoft.com/winfx/2009/xaml"];
index e081063..5246041 100644 (file)
@@ -36,8 +36,9 @@ namespace Xamarin.Forms.Build.Tasks
                                string ns;
                                string typename;
                                string asmstring;
+                               string targetPlatform;
 
-                               XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring);
+                               XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring, out targetPlatform);
                                asmstring = asmstring ?? module.Assembly.Name.Name;
                                lookupAssemblies.Add(new Tuple<string, string>(asmstring, ns));
                        }
index 06f214b..a774340 100644 (file)
@@ -222,6 +222,7 @@ namespace Xamarin.Forms.Controls
                        var pages = new List<Page> {
                                new PlatformSpecificsGallery() {Title = "Platform Specifics"},
                                new NativeBindingGalleryPage {Title = "Native Binding Controls Gallery"},
+                               new XamlNativeViews {Title = "Xaml Native Views Gallery"},
                                new AppLinkPageGallery {Title = "App Link Page Gallery"},
                                new NestedNativeControlGalleryPage {Title = "Nested Native Controls Gallery"},
                                new CellForceUpdateSizeGalleryPage {Title = "Cell Force Update Size Gallery"},
diff --git a/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml b/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml
new file mode 100644 (file)
index 0000000..7a1fb85
--- /dev/null
@@ -0,0 +1,14 @@
+<?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:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
+                        xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
+                        xmlns:formsandroid="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.Platform.Android;targetPlatform=Android"
+                        xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
+       x:Class="Xamarin.Forms.Controls.XamlNativeViews">
+       <ContentPage.Content>
+               <ios:UILabel Text="{Binding NativeText}" View.HorizontalOptions="Start"/>
+               <androidWidget:TextView Text="{Binding NativeText}" x:Arguments="{x:Static formsandroid:Forms.Context}" />
+    <win:TextBlock Text="Foo"/>
+       </ContentPage.Content>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml.cs
new file mode 100644 (file)
index 0000000..1e40fdb
--- /dev/null
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Controls
+{
+       public partial class XamlNativeViews : ContentPage
+       {
+               public XamlNativeViews()
+               {
+                       InitializeComponent();
+                       BindingContext = new VM { NativeText = "Text set to Native view using native binding" };
+               }
+       }
+
+       public class VM
+       { 
+               public string NativeText { get; set; }
+       }
+}
\ No newline at end of file
index 3b02adb..7ad923e 100644 (file)
     <Compile Include="ControlGalleryPages\AutomationIDGallery.cs" />
     <Compile Include="GalleryPages\AppLinkPageGallery.cs" />
     <Compile Include="ControlGalleryPages\NativeBindingGalleryPage.cs" />
+    <Compile Include="GalleryPages\XamlNativeViews.xaml.cs">
+      <DependentUpon>XamlNativeViews.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
   <Import Project="..\.nuspec\Xamarin.Forms.targets" />
     <EmbeddedResource Include="GalleryPages\StyleXamlGallery.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
+    <EmbeddedResource Include="GalleryPages\XamlNativeViews.xaml">
+      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+    </EmbeddedResource>
   </ItemGroup>
   <Import Project="..\Xamarin.Forms.Controls.Issues\Xamarin.Forms.Controls.Issues.Shared\Xamarin.Forms.Controls.Issues.Shared.projitems" Label="Shared" />
   <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
     </CreateItem>
     <Copy SourceFiles="@(ConfigFile)" DestinationFiles="controlgallery.config" Condition="!Exists('controlgallery.config')" />
   </Target>
-</Project>
\ No newline at end of file
+</Project>
index e7c7f9a..66577f6 100644 (file)
@@ -52,7 +52,6 @@ namespace Xamarin.Forms.Core.UnitTests
                        NativeView.SetBindingContext(BindingContext, nv => nv.SubViews);
                        base.OnBindingContextChanged();
                }
-
        }
 
        public class MockNativeColor
diff --git a/Xamarin.Forms.Core/INativeBindingService.cs b/Xamarin.Forms.Core/INativeBindingService.cs
new file mode 100644 (file)
index 0000000..d926dae
--- /dev/null
@@ -0,0 +1,10 @@
+namespace Xamarin.Forms.Xaml
+{
+
+       interface INativeBindingService
+       {
+               bool TrySetBinding(object target, string propertyName, BindingBase binding);
+               bool TrySetBinding(object target, BindableProperty property, BindingBase binding);
+               bool TrySetValue(object target, BindableProperty property, object value);
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Core/INativeValueConverterService.cs b/Xamarin.Forms.Core/INativeValueConverterService.cs
new file mode 100644 (file)
index 0000000..4309be9
--- /dev/null
@@ -0,0 +1,9 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+       interface INativeValueConverterService
+       {
+               bool ConvertTo(object value, Type toType, out object nativeValue);
+       }
+}
\ No newline at end of file
index 9e96152..a07b88c 100644 (file)
     <Compile Include="ListStringTypeConverter.cs" />
     <Compile Include="PoppedToRootEventArgs.cs" />
     <Compile Include="NativeBindingHelpers.cs" />
+    <Compile Include="INativeValueConverterService.cs" />
+    <Compile Include="INativeBindingService.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
   <ItemGroup>
     </PostBuildEvent>
   </PropertyGroup>
   <ItemGroup />
-</Project>
\ No newline at end of file
+</Project>
index 42e8b61..d953da0 100644 (file)
@@ -7,7 +7,7 @@ namespace Xamarin.Forms.Xaml
        {
                readonly string _unformattedMessage;
 
-               public XamlParseException(string message, IXmlLineInfo xmlInfo) : base(FormatMessage(message, xmlInfo))
+               public XamlParseException(string message, IXmlLineInfo xmlInfo, Exception innerException = null) : base(FormatMessage(message, xmlInfo), innerException)
                {
                        _unformattedMessage = message;
                        XmlInfo = xmlInfo;
diff --git a/Xamarin.Forms.Platform.Android/NativeBindingservice.cs b/Xamarin.Forms.Platform.Android/NativeBindingservice.cs
new file mode 100644 (file)
index 0000000..3dd4fe3
--- /dev/null
@@ -0,0 +1,39 @@
+using System;
+using AView = Android.Views.View;
+
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.Android.NativeBindingService))]
+
+namespace Xamarin.Forms.Platform.Android
+{
+       class NativeBindingService : Xaml.INativeBindingService
+       {
+               public bool TrySetBinding(object target, string propertyName, BindingBase binding)
+               {
+                       var view = target as AView;
+                       if (view == null)
+                               return false;
+                       if (target.GetType().GetProperty(propertyName)?.GetMethod == null)
+                               return false;
+                       view.SetBinding(propertyName, binding);
+                       return true;
+               }
+
+               public bool TrySetBinding(object target, BindableProperty property, BindingBase binding)
+               {
+                       var view = target as AView;
+                       if (view == null)
+                               return false;
+                       view.SetBinding(property, binding);
+                       return true;
+               }
+
+               public bool TrySetValue(object target, BindableProperty property, object value)
+               {
+                       var view = target as AView;
+                       if (view == null)
+                               return false;
+                       view.SetValue(property, value);
+                       return true;
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.Android/NativeValueConverterService.cs b/Xamarin.Forms.Platform.Android/NativeValueConverterService.cs
new file mode 100644 (file)
index 0000000..ff6faa9
--- /dev/null
@@ -0,0 +1,19 @@
+using System;
+using AView = Android.Views.View;
+
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.Android.NativeValueConverterService))]
+namespace Xamarin.Forms.Platform.Android
+{
+       class NativeValueConverterService : Xaml.INativeValueConverterService
+       {
+               public bool ConvertTo(object value, Type toType, out object nativeValue)
+               {
+                       nativeValue = null;
+                       if (typeof(AView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) {
+                               nativeValue = ((AView)value).ToView();
+                               return true;
+                       }
+                       return false;
+               }
+       }
+}
\ No newline at end of file
index 0017971..2852b85 100644 (file)
     <Compile Include="AndroidAppIndexProvider.cs" />
     <Compile Include="Renderers\FormsSeekBar.cs" />
     <Compile Include="Extensions\NativeBindingExtensions.cs" />
+    <Compile Include="NativeValueConverterService.cs" />
+    <Compile Include="NativeBindingservice.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
diff --git a/Xamarin.Forms.Platform.UAP/NativeBindingService.cs b/Xamarin.Forms.Platform.UAP/NativeBindingService.cs
new file mode 100644 (file)
index 0000000..e999af2
--- /dev/null
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml;
+
+#if WINDOWS_UWP
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.UWP.NativeBindingService))]
+namespace Xamarin.Forms.Platform.UWP
+#else
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.WinRT.NativeBindingService))]
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+    public class NativeBindingService : Xaml.INativeBindingService
+    {
+        public bool TrySetBinding(object target, string propertyName, BindingBase binding)
+        {
+            var view = target as FrameworkElement;
+            if (view == null)
+                return false;
+            if (target.GetType().GetProperty(propertyName)?.GetMethod == null)
+                return false;
+            view.SetBinding(propertyName, binding);
+            return true;
+        }
+
+        public bool TrySetBinding(object target, BindableProperty property, BindingBase binding)
+        {
+            var view = target as FrameworkElement;
+            if (view == null)
+                return false;
+            view.SetBinding(property, binding);
+            return true;
+        }
+
+        public bool TrySetValue(object target, BindableProperty property, object value)
+        {
+            var view = target as FrameworkElement;
+            if (view == null)
+                return false;
+            view.SetValue(property, value);
+            return true;
+        }
+    }
+}
diff --git a/Xamarin.Forms.Platform.UAP/NativeValueConverterService.cs b/Xamarin.Forms.Platform.UAP/NativeValueConverterService.cs
new file mode 100644 (file)
index 0000000..1f0ef68
--- /dev/null
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml;
+
+#if WINDOWS_UWP
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.UWP.NativeValueConverterService))]
+namespace Xamarin.Forms.Platform.UWP
+#else
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.WinRT.NativeValueConverterService))]
+namespace Xamarin.Forms.Platform.WinRT
+#endif
+{
+    public class NativeValueConverterService : Xaml.INativeValueConverterService
+    {
+        public bool ConvertTo(object value, Type toType, out object nativeValue)
+        {
+            nativeValue = null;
+            if (typeof(FrameworkElement).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View)))
+            {
+                nativeValue = ((FrameworkElement)value).ToView();
+                return true;
+            }
+            return false;
+        }
+    }
+}
index 81ec3ff..4f38306 100644 (file)
       <Link>LayoutExtensions.cs</Link>
     </Compile>
     <Compile Include="IToolBarForegroundBinder.cs" />
+    <Compile Include="NativeBindingService.cs" />
+    <Compile Include="NativeValueConverterService.cs" />
     <Compile Include="SearchBarRenderer.cs" />
     <Compile Include="..\Xamarin.Forms.Platform.WinRT\TextAlignmentToHorizontalAlignmentConverter.cs">
       <Link>TextAlignmentToHorizontalAlignmentConverter.cs</Link>
diff --git a/Xamarin.Forms.Platform.iOS/NativeBindingService.cs b/Xamarin.Forms.Platform.iOS/NativeBindingService.cs
new file mode 100644 (file)
index 0000000..3756e2f
--- /dev/null
@@ -0,0 +1,39 @@
+using System;
+using UIKit;
+
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.iOS.NativeBindingService))]
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       class NativeBindingService : Xaml.INativeBindingService
+       {
+               public bool TrySetBinding(object target, string propertyName, BindingBase binding)
+               {
+                       var view = target as UIView;
+                       if (view == null)
+                               return false;
+                       if (target.GetType().GetProperty(propertyName)?.GetMethod == null)
+                               return false;
+                       view.SetBinding(propertyName, binding);
+                       return true;
+               }
+
+               public bool TrySetBinding(object target, BindableProperty property, BindingBase binding)
+               {
+                       var view = target as UIView;
+                       if (view == null)
+                               return false;
+                       view.SetBinding(property, binding);
+                       return true;
+               }
+
+               public bool TrySetValue(object target, BindableProperty property, object value)
+               {
+                       var view = target as UIView;
+                       if (view == null)
+                               return false;
+                       view.SetValue(property, value);
+                       return true;
+               }
+       }
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs b/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs
new file mode 100644 (file)
index 0000000..9e20e04
--- /dev/null
@@ -0,0 +1,20 @@
+using System;
+using UIKit;
+
+[assembly: Xamarin.Forms.Dependency(typeof(Xamarin.Forms.Platform.iOS.NativeValueConverterService))]
+
+namespace Xamarin.Forms.Platform.iOS
+{
+       class NativeValueConverterService : Xaml.INativeValueConverterService
+       {
+               public bool ConvertTo(object value, Type toType, out object nativeValue)
+               {
+                       nativeValue = null;
+                       if (typeof(UIView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) {
+                               nativeValue = ((UIView)value).ToView();
+                               return true;
+                       }
+                       return false;
+               }
+       }
+}
\ No newline at end of file
index 8fde4c8..e31aa3a 100644 (file)
     <Compile Include="IOSAppLinks.cs" />
     <Compile Include="NativeViewPropertyListener.cs" />
     <Compile Include="Extensions\LayoutExtensions.cs" />
+    <Compile Include="NativeValueConverterService.cs" />
+    <Compile Include="NativeBindingService.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Resources\StringResources.ar.resx" />
       <Link>Properties\GlobalAssemblyInfo.cs</Link>
     </Compile>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index 65bb73f..c691394 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 
 using Xamarin.Forms;
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
@@ -29,6 +30,18 @@ namespace Xamarin.Forms.Xaml.UnitTests
                [TestFixture]
                class Tests
                {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
                        [TestCase(true)]
                        [TestCase(false)]
                        public void CorrectlyResolveBPOnSubClasses (bool useCompiledXaml)
index 3813f73..b35339d 100644 (file)
@@ -1,11 +1,24 @@
 using System;
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
        [TestFixture]
        public class Issue1497
        {
+               [SetUp]
+               public void Setup()
+               {
+                       Device.PlatformServices = new MockPlatformServices();
+               }
+
+               [TearDown]
+               public void TearDown()
+               {
+                       Device.PlatformServices = null;
+               }
+
                [Test]
                public void BPCollectionsWithSingleElement ()
                {
@@ -23,5 +36,4 @@ namespace Xamarin.Forms.Xaml.UnitTests
                        Assert.True (grid.ColumnDefinitions [0].Width.IsStar);
                }
        }
-}
-
+}
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml b/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml
new file mode 100644 (file)
index 0000000..a35f8e5
--- /dev/null
@@ -0,0 +1,13 @@
+<?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:ios="clr-namespace:Xamarin.Forms.Xaml.UnitTests;targetPlatform=iOS"
+               xmlns:android="clr-namespace:Xamarin.Forms.Xaml.UnitTests;targetPlatform=Android"
+               x:Class="Xamarin.Forms.Xaml.UnitTests.NativeViewsAndBindings">
+       <StackLayout>
+               <ContentView x:Name="view0">
+                       <ios:MockUIView Foo="foo" Bar="42" Baz="{Binding Baz}" View.HorizontalOptions="End" View.VerticalOptions="{Binding VerticalOption}" />
+                       <android:MockAndroidView Foo="foo" Bar="42" Baz="{Binding Baz}" View.HorizontalOptions="End" View.VerticalOptions="{Binding VerticalOption}" />
+               </ContentView>
+       </StackLayout>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml.cs
new file mode 100644 (file)
index 0000000..ce02d63
--- /dev/null
@@ -0,0 +1,293 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using NUnit.Framework;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+       public abstract class MockNativeView
+       { 
+               public string Foo { get; set; }
+               public int Bar { get; set; }
+               public string Baz { get; set; }
+       }
+
+       public class MockUIView : MockNativeView
+       {
+               public IList<MockUIView> SubViews { get; set; }
+       }
+
+       class MockUIViewWrapper : View
+       {
+               public MockUIView NativeView { get; }
+
+               public MockUIViewWrapper(MockUIView nativeView)
+               {
+                       NativeView = nativeView;
+                       nativeView.TransferbindablePropertiesToWrapper(this);
+               }
+
+               protected override void OnBindingContextChanged()
+               {
+                       NativeView.SetBindingContext(BindingContext, nv => nv.SubViews);
+                       base.OnBindingContextChanged();
+               }
+       }
+
+       public class MockAndroidView : MockNativeView
+       {
+               public IList<MockAndroidView> SubViews { get; set; }
+       }
+
+       class MockAndroidViewWrapper : View
+       {
+               public MockAndroidView NativeView { get; }
+
+               public MockAndroidViewWrapper(MockAndroidView nativeView)
+               {
+                       NativeView = nativeView;
+                       nativeView.TransferbindablePropertiesToWrapper(this);
+               }
+
+               protected override void OnBindingContextChanged()
+               {
+                       NativeView.SetBindingContext(BindingContext, nv => nv.SubViews);
+                       base.OnBindingContextChanged();
+               }
+       }
+
+       public static class MockNativeViewExtensions
+       {
+               public static View ToView(this MockUIView nativeView)
+               {
+                       return new MockUIViewWrapper(nativeView);
+               }
+
+               public static void SetBinding(this MockUIView target, string targetProperty, BindingBase binding, string updateSourceEventName = null)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding, updateSourceEventName);
+               }
+
+               internal static void SetBinding(this MockUIView target, string targetProperty, BindingBase binding, INotifyPropertyChanged propertyChanged)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding, propertyChanged);
+               }
+
+               public static void SetBinding(this MockUIView target, BindableProperty targetProperty, BindingBase binding)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding);
+               }
+
+               public static void SetValue(this MockUIView target, BindableProperty targetProperty, object value)
+               {
+                       NativeBindingHelpers.SetValue(target, targetProperty, value);
+               }
+
+               public static void SetBindingContext(this MockUIView target, object bindingContext, Func<MockUIView, IEnumerable<MockUIView>> getChild = null)
+               {
+                       NativeBindingHelpers.SetBindingContext(target, bindingContext, getChild);
+               }
+
+               internal static void TransferbindablePropertiesToWrapper(this MockUIView target, MockUIViewWrapper wrapper)
+               {
+                       NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+               }
+
+               public static View ToView(this MockAndroidView nativeView)
+               {
+                       return new MockAndroidViewWrapper(nativeView);
+               }
+
+               public static void SetBinding(this MockAndroidView target, string targetProperty, BindingBase binding, string updateSourceEventName = null)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding, updateSourceEventName);
+               }
+
+               internal static void SetBinding(this MockAndroidView target, string targetProperty, BindingBase binding, INotifyPropertyChanged propertyChanged)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding, propertyChanged);
+               }
+
+               public static void SetBinding(this MockAndroidView target, BindableProperty targetProperty, BindingBase binding)
+               {
+                       NativeBindingHelpers.SetBinding(target, targetProperty, binding);
+               }
+
+               public static void SetValue(this MockAndroidView target, BindableProperty targetProperty, object value)
+               {
+                       NativeBindingHelpers.SetValue(target, targetProperty, value);
+               }
+
+               public static void SetBindingContext(this MockAndroidView target, object bindingContext, Func<MockAndroidView, IEnumerable<MockAndroidView>> getChild = null)
+               {
+                       NativeBindingHelpers.SetBindingContext(target, bindingContext, getChild);
+               }
+
+               internal static void TransferbindablePropertiesToWrapper(this MockAndroidView target, MockAndroidViewWrapper wrapper)
+               {
+                       NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper);
+               }
+       }
+
+       public class MockIosNativeValueConverterService : INativeValueConverterService
+       {
+               public bool ConvertTo(object value, Type toType, out object nativeValue)
+               {
+                       nativeValue = null;
+                       if (typeof(MockUIView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) {
+                               nativeValue = ((MockUIView)value).ToView();
+                               return true;
+                       }
+                       return false;
+               }
+       }
+
+       public class MockAndroidNativeValueConverterService : INativeValueConverterService
+       {
+               public bool ConvertTo(object value, Type toType, out object nativeValue)
+               {
+                       nativeValue = null;
+                       if (typeof(MockAndroidView).IsInstanceOfType(value) && toType.IsAssignableFrom(typeof(View))) {
+                               nativeValue = ((MockAndroidView)value).ToView();
+                               return true;
+                       }
+                       return false;
+               }
+       }
+
+       public class MockIosNativeBindingService : INativeBindingService
+       {
+               public bool TrySetBinding(object target, string propertyName, BindingBase binding)
+               {
+                       var view = target as MockUIView;
+                       if (view == null)
+                               return false;
+                       if (target.GetType().GetProperty(propertyName)?.GetMethod == null)
+                               return false;
+                       view.SetBinding(propertyName, binding);
+                       return true;
+               }
+
+               public bool TrySetBinding(object target, BindableProperty property, BindingBase binding)
+               {
+                       var view = target as MockUIView;
+                       if (view == null)
+                               return false;
+                       view.SetBinding(property, binding);
+                       return true;
+               }
+
+               public bool TrySetValue(object target, BindableProperty property, object value)
+               {
+                       var view = target as MockUIView;
+                       if (view == null)
+                               return false;
+                       view.SetValue(property, value);
+                       return true;
+               }
+       }
+
+       public class MockAndroidNativeBindingService : INativeBindingService
+       {
+               public bool TrySetBinding(object target, string propertyName, BindingBase binding)
+               {
+                       var view = target as MockAndroidView;
+                       if (view == null)
+                               return false;
+                       view.SetBinding(propertyName, binding);
+                       return true;
+               }
+
+               public bool TrySetBinding(object target, BindableProperty property, BindingBase binding)
+               {
+                       var view = target as MockAndroidView;
+                       if (view == null)
+                               return false;
+                       view.SetBinding(property, binding);
+                       return true;
+               }
+
+               public bool TrySetValue(object target, BindableProperty property, object value)
+               {
+                       var view = target as MockAndroidView;
+                       if (view == null)
+                               return false;
+                       view.SetValue(property, value);
+                       return true;
+               }
+       }
+
+       public partial class NativeViewsAndBindings : ContentPage
+       {
+               public NativeViewsAndBindings()
+               {
+                       InitializeComponent();
+               }
+
+               public NativeViewsAndBindings(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;
+                       }
+
+                       void SetUpPlatform(TargetPlatform platform)
+                       {
+                               Device.OS = platform;
+                               if (platform == TargetPlatform.iOS) {
+                                       DependencyService.Register<INativeValueConverterService, MockIosNativeValueConverterService>();
+                                       DependencyService.Register<INativeBindingService, MockIosNativeBindingService>();
+                               } else if (platform == TargetPlatform.Android) {
+                                       DependencyService.Register<INativeValueConverterService, MockAndroidNativeValueConverterService>();
+                                       DependencyService.Register<INativeBindingService, MockAndroidNativeBindingService>();
+                               }
+                       }
+
+                       [TestCase(false, TargetPlatform.iOS)]
+                       [TestCase(false, TargetPlatform.Android)]
+                       //[TestCase(true)]
+                       public void NativeInContentView(bool useCompiledXaml, TargetPlatform platform)
+                       {
+                               SetUpPlatform(platform);
+                               var layout = new NativeViewsAndBindings(useCompiledXaml);
+                               layout.BindingContext = new {
+                                       Baz = "Bound Value",
+                                       VerticalOption=LayoutOptions.EndAndExpand
+                               };
+                               var view = layout.view0;
+                               Assert.NotNull(view.Content);
+                               MockNativeView nativeView = null;
+                               if (platform == TargetPlatform.iOS) {
+                                       Assert.That(view.Content, Is.TypeOf<MockUIViewWrapper>());
+                                       Assert.That(((MockUIViewWrapper)view.Content).NativeView, Is.TypeOf<MockUIView>());
+                                       nativeView = ((MockUIViewWrapper)view.Content).NativeView;
+                               } else if (platform == TargetPlatform.Android) {
+                                       Assert.That(view.Content, Is.TypeOf<MockAndroidViewWrapper>());
+                                       Assert.That(((MockAndroidViewWrapper)view.Content).NativeView, Is.TypeOf<MockAndroidView>());
+                                       nativeView = ((MockAndroidViewWrapper)view.Content).NativeView;
+                               }
+
+                               Assert.AreEqual("foo", nativeView.Foo);
+                               Assert.AreEqual(42, nativeView.Bar);
+                               Assert.AreEqual("Bound Value", nativeView.Baz);
+                               Assert.AreEqual(LayoutOptions.End, view.Content.GetValue(View.HorizontalOptionsProperty));
+                               Assert.AreEqual(LayoutOptions.EndAndExpand, view.Content.GetValue(View.VerticalOptionsProperty));
+                       }
+               }
+       }
+}
\ No newline at end of file
index 86e2eeb..26bf6d4 100644 (file)
     <Compile Include="TypeExtension.xaml.cs">
       <DependentUpon>TypeExtension.xaml</DependentUpon>
     </Compile>
+    <Compile Include="NativeViewsAndBindings.xaml.cs">
+      <DependentUpon>NativeViewsAndBindings.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" />
     <EmbeddedResource Include="TypeExtension.xaml">
       <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
     </EmbeddedResource>
+    <EmbeddedResource Include="NativeViewsAndBindings.xaml">
+      <Generator>MSBuild:UpdateDesignTimeXaml</Generator>
+    </EmbeddedResource>
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
index 327a412..36f5b7f 100644 (file)
@@ -7,6 +7,8 @@ using System.Xml;
 using Xamarin.Forms.Internals;
 using Xamarin.Forms.Xaml.Internals;
 
+using static System.String;
+
 namespace Xamarin.Forms.Xaml
 {
        internal class ApplyPropertiesVisitor : IXamlNodeVisitor
@@ -26,20 +28,17 @@ namespace Xamarin.Forms.Xaml
                        StopOnResourceDictionary = stopOnResourceDictionary;
                }
 
-               Dictionary<INode, object> Values
-               {
+               Dictionary<INode, object> Values {
                        get { return Context.Values; }
                }
 
                HydratationContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
+               public bool VisitChildrenFirst {
                        get { return true; }
                }
 
-               public bool StopOnDataTemplate
-               {
+               public bool StopOnDataTemplate {
                        get { return true; }
                }
 
@@ -48,12 +47,11 @@ namespace Xamarin.Forms.Xaml
                public void Visit(ValueNode node, INode parentNode)
                {
                        var parentElement = parentNode as IElementNode;
-                       var value = Values[node];
-                       var source = Values[parentNode];
+                       var value = Values [node];
+                       var source = Values [parentNode];
 
                        XmlName propertyName;
-                       if (TryGetPropertyName(node, parentNode, out propertyName))
-                       {
+                       if (TryGetPropertyName(node, parentNode, out propertyName)) {
                                if (Skips.Contains(propertyName))
                                        return;
                                if (parentElement.SkipProperties.Contains(propertyName))
@@ -62,13 +60,10 @@ namespace Xamarin.Forms.Xaml
                                        propertyName.LocalName == "Ignorable")
                                        return;
                                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) {
                                // Collection element, implicit content, or implicit collection element.
-                               var contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo());
-                               if (contentProperty != null)
-                               {
+                               var contentProperty = GetContentPropertyName(Context.Types [parentElement].GetTypeInfo());
+                               if (contentProperty != null) {
                                        var name = new XmlName(((ElementNode)parentNode).NamespaceURI, contentProperty);
                                        if (Skips.Contains(name))
                                                return;
@@ -85,19 +80,17 @@ namespace Xamarin.Forms.Xaml
 
                public void Visit(ElementNode node, INode parentNode)
                {
-                       var value = Values[node];
+                       var value = Values [node];
                        var parentElement = parentNode as IElementNode;
                        var markupExtension = value as IMarkupExtension;
                        var valueProvider = value as IValueProvider;
 
-                       if (markupExtension != null)
-                       {
+                       if (markupExtension != null) {
                                var serviceProvider = new XamlServiceProvider(node, Context);
                                value = markupExtension.ProvideValue(serviceProvider);
                        }
 
-                       if (valueProvider != null)
-                       {
+                       if (valueProvider != null) {
                                var serviceProvider = new XamlServiceProvider(node, Context);
                                value = valueProvider.ProvideValue(serviceProvider);
                        }
@@ -124,37 +117,29 @@ namespace Xamarin.Forms.Xaml
                                        SetTemplate(source as ElementTemplate, node);
                                else
                                        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) {
                                // Collection element, implicit content, or implicit collection element.
                                string contentProperty;
-                               if (typeof (IEnumerable).GetTypeInfo().IsAssignableFrom(Context.Types[parentElement].GetTypeInfo()))
-                               {
-                                       var source = Values[parentNode];
-                                       if (!(typeof (ResourceDictionary).IsAssignableFrom(Context.Types[parentElement])))
-                                       {
+                               if (typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(Context.Types [parentElement].GetTypeInfo())) {
+                                       var source = Values [parentNode];
+                                       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 });
+                                                       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)
-                               {
+                               } else if ((contentProperty = GetContentPropertyName(Context.Types [parentElement].GetTypeInfo())) != null) {
                                        var name = new XmlName(node.NamespaceURI, contentProperty);
                                        if (Skips.Contains(name))
                                                return;
                                        if (parentElement.SkipProperties.Contains(propertyName))
                                                return;
 
-                                       var source = Values[parentNode];
+                                       var source = Values [parentNode];
                                        SetPropertyValue(source, name, value, Context.RootElement, node, Context, node);
                                }
-                       }
-                       else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
-                       {
+                       } else if (IsCollectionItem(node, parentNode) && parentNode is ListNode) {
                                var parentList = (ListNode)parentNode;
-                               var source = Values[parentNode.Parent];
+                               var source = Values [parentNode.Parent];
 
                                if (Skips.Contains(parentList.XmlName))
                                        return;
@@ -165,15 +150,11 @@ namespace Xamarin.Forms.Xaml
                                GetRealNameAndType(ref elementType, parentList.XmlName.NamespaceURI, ref localname, Context, node);
 
                                PropertyInfo propertyInfo = null;
-                               try
-                               {
+                               try {
                                        propertyInfo = elementType.GetRuntimeProperty(localname);
-                               }
-                               catch (AmbiguousMatchException)
-                               {
+                               } catch (AmbiguousMatchException) {
                                        // Get most derived instance of property
-                                       foreach (var property in elementType.GetRuntimeProperties().Where(prop => prop.Name == localname))
-                                       {
+                                       foreach (var property in elementType.GetRuntimeProperties().Where(prop => prop.Name == localname)) {
                                                if (propertyInfo == null || propertyInfo.DeclaringType.IsAssignableFrom(property.DeclaringType))
                                                        propertyInfo = property;
                                        }
@@ -184,7 +165,7 @@ namespace Xamarin.Forms.Xaml
                                if (!propertyInfo.CanRead || (getter = propertyInfo.GetMethod) == null)
                                        throw new XamlParseException(string.Format("Property {0} does not have an accessible getter", localname), node);
                                IEnumerable collection;
-                               if ((collection = getter.Invoke(source, new object[] { }) as IEnumerable) == null)
+                               if ((collection = getter.Invoke(source, new object [] { }) as IEnumerable) == null)
                                        throw new XamlParseException(string.Format("Property {0} is null or is not IEnumerable", localname), node);
                                MethodInfo addMethod;
                                if (
@@ -192,7 +173,7 @@ namespace Xamarin.Forms.Xaml
                                                collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) == null)
                                        throw new XamlParseException(string.Format("Value of {0} does not have a Add() method", localname), node);
 
-                               addMethod.Invoke(collection, new[] { Values[node] });
+                               addMethod.Invoke(collection, new [] { Values [node] });
                        }
                }
 
@@ -210,8 +191,7 @@ namespace Xamarin.Forms.Xaml
                        var parentElement = parentNode as IElementNode;
                        if (parentElement == null)
                                return false;
-                       foreach (var kvp in parentElement.Properties)
-                       {
+                       foreach (var kvp in parentElement.Properties) {
                                if (kvp.Value != node)
                                        continue;
                                name = kvp.Key;
@@ -230,8 +210,7 @@ namespace Xamarin.Forms.Xaml
 
                internal static string GetContentPropertyName(TypeInfo typeInfo)
                {
-                       while (typeInfo != null)
-                       {
+                       while (typeInfo != null) {
                                var propName = GetContentPropertyName(typeInfo.CustomAttributes);
                                if (propName != null)
                                        return propName;
@@ -246,8 +225,8 @@ namespace Xamarin.Forms.Xaml
                                attributes.FirstOrDefault(cad => ContentPropertyAttribute.ContentPropertyTypes.Contains(cad.AttributeType.FullName));
                        if (contentAttribute == null || contentAttribute.ConstructorArguments.Count != 1)
                                return null;
-                       if (contentAttribute.ConstructorArguments[0].ArgumentType == typeof (string))
-                               return (string)contentAttribute.ConstructorArguments[0].Value;
+                       if (contentAttribute.ConstructorArguments [0].ArgumentType == typeof(string))
+                               return (string)contentAttribute.ConstructorArguments [0].Value;
                        return null;
                }
 
@@ -255,8 +234,7 @@ namespace Xamarin.Forms.Xaml
                        HydratationContext context, IXmlLineInfo lineInfo)
                {
                        var dotIdx = localname.IndexOf('.');
-                       if (dotIdx > 0)
-                       {
+                       if (dotIdx > 0) {
                                var typename = localname.Substring(0, dotIdx);
                                localname = localname.Substring(dotIdx + 1);
                                XamlParseException xpe;
@@ -276,8 +254,7 @@ namespace Xamarin.Forms.Xaml
                                elementType.GetFields().FirstOrDefault(fi => fi.Name == localName + "Property" && fi.IsStatic && fi.IsPublic);
 
                        Exception exception = null;
-                       if (exception == null && bindableFieldInfo == null)
-                       {
+                       if (exception == null && bindableFieldInfo == null) {
                                exception =
                                        new XamlParseException(
                                                string.Format("BindableProperty {0} not found on {1}", localName + "Property", elementType.Name), lineInfo);
@@ -290,148 +267,204 @@ namespace Xamarin.Forms.Xaml
                        return null;
                }
 
-               public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, object rootElement,
-                       INode node, HydratationContext context, IXmlLineInfo lineInfo)
+               public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, object rootElement, INode node, HydratationContext context, IXmlLineInfo lineInfo)
                {
-                       var elementType = xamlelement.GetType();
-                       var localname = propertyName.LocalName;
-
+                       var localName = propertyName.LocalName;
                        var serviceProvider = new XamlServiceProvider(node, context);
+                       Exception xpe = null;
 
                        //If it's an attached BP, update elementType and propertyName
-                       var attached = GetRealNameAndType(ref elementType, propertyName.NamespaceURI, ref localname, context, lineInfo);
+                       var bpOwnerType = xamlelement.GetType();
+                       var attached = GetRealNameAndType(ref bpOwnerType, propertyName.NamespaceURI, ref localName, context, lineInfo);
+                       var property = GetBindableProperty(bpOwnerType, localName, lineInfo, false);
 
                        //If the target is an event, connect
-                       var eventInfo = elementType.GetRuntimeEvent(localname);
-                       if (eventInfo != null && value is string)
-                       {
-                               var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
-                               if (methodInfo == null) {
-                                       var xpe = new XamlParseException (string.Format ("No method {0} found on type {1}", value, rootElement.GetType ()), lineInfo);
-                                       if (context.DoNotThrowOnExceptions) {
-                                               System.Diagnostics.Debug.WriteLine (xpe.Message);
-                                               return;
-                                       } else
-                                               throw xpe;
-                               }
-                               try
-                               {
-                                       eventInfo.AddEventHandler(xamlelement, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
-                               }
-                               catch (ArgumentException)
-                               {
-                                       var xpe = new XamlParseException (string.Format ("Method {0} does not have the correct signature", value), lineInfo);
-                                       if (context.DoNotThrowOnExceptions)
-                                               System.Diagnostics.Debug.WriteLine (xpe.Message);
-                                       else
-                                               throw xpe;
-                               }
+                       if (xpe == null && TryConnectEvent(xamlelement, localName, value, rootElement, lineInfo, out xpe))
+                               return;
 
+                       //If Value is DynamicResource and it's a BP, SetDynamicResource
+                       if (xpe == null && TrySetDynamicResource(xamlelement, property, value, lineInfo, out xpe))
                                return;
-                       }
 
-                       var property = GetBindableProperty(elementType, localname, lineInfo, false);
+                       //If value is BindingBase, SetBinding
+                       if (xpe == null && TrySetBinding(xamlelement, property, localName, value, lineInfo, out xpe))
+                               return;
 
-                       //If Value is DynamicResource and it's a BP, SetDynamicResource
-                       if (value is DynamicResource && property != null)
-                       {
-                               if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
-                                       throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
-                               ((BindableObject)xamlelement).SetDynamicResource(property, ((DynamicResource)value).Key);
+                       //If it's a BindableProberty, SetValue
+                       if (xpe == null && TrySetValue(xamlelement, property, attached, value, lineInfo, serviceProvider, out xpe))
                                return;
-                       }
 
-                       //If value is BindingBase, and target is a BindableProperty, SetBinding
-                       if (value is BindingBase && property != null)
-                       {
-                               if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
-                                       throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
+                       //If we can assign that value to a normal property, let's do it
+                       if (xpe == null && TrySetProperty(xamlelement, localName, value, lineInfo, serviceProvider, out xpe))
+                               return;
 
-                               ((BindableObject)xamlelement).SetBinding(property, value as BindingBase);
+                       //If it's an already initialized property, add to it
+                       if (xpe == null && TryAddToProperty(xamlelement, localName, value, lineInfo, serviceProvider, 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);
+                       if (context.DoNotThrowOnExceptions)
+                               System.Diagnostics.Debug.WriteLine(xpe.Message);
+                       else
+                               throw xpe;
+               }
+
+               static bool TryConnectEvent(object element, string localName, object value, object rootElement, IXmlLineInfo lineInfo, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var eventInfo = elementType.GetRuntimeEvent(localName);
+                       var stringValue = value as string;
+
+                       if (eventInfo == null || IsNullOrEmpty(stringValue))
+                               return false;
+
+                       var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
+                       if (methodInfo == null) {
+                               exception = new XamlParseException($"No method {value} found on type {rootElement.GetType()}", lineInfo);
+                               return false;
                        }
 
-                       //If it's a BindableProberty, SetValue
+                       try {
+                               eventInfo.AddEventHandler(element, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
+                               return true;
+                       } catch (ArgumentException ae) {
+                               exception = new XamlParseException($"Method {stringValue} does not have the correct signature", lineInfo, ae);
+                       }
+                       return false;
+               }
+
+               static bool TrySetDynamicResource(object element, BindableProperty property, object value, IXmlLineInfo lineInfo, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var dynamicResource = value as DynamicResource;
+                       var bindable = element as BindableObject;
+
+                       if (dynamicResource == null || property == null)
+                               return false;
+
+                       if (bindable == null) {
+                               exception = new XamlParseException($"{elementType.Name} is not a BindableObject", lineInfo);
+                               return false;
+                       }
+
+                       bindable.SetDynamicResource(property, dynamicResource.Key);
+                       return true;
+               }
+
+               static bool TrySetBinding(object element, BindableProperty property, string localName, object value, IXmlLineInfo lineInfo, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var binding = value as BindingBase;
+                       var bindable = element as BindableObject;
+                       var nativeBindingService = DependencyService.Get<INativeBindingService>();
+
+                       if (binding == null)
+                               return false;
+
+                       if (bindable != null && property != null) {
+                               bindable.SetBinding(property, binding);
+                               return true;
+                       }
+
+                       if (nativeBindingService != null && property != null && nativeBindingService.TrySetBinding(element, property, binding))
+                               return true;
+
+                       if (nativeBindingService != null && nativeBindingService.TrySetBinding(element, localName, binding))
+                               return true;
+
                        if (property != null)
-                       {
-                               if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
-                                       throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
-                               Func<MemberInfo> minforetriever;
-                               if (attached)
-                                       minforetriever = () => elementType.GetRuntimeMethod("Get" + localname, new[] { typeof (BindableObject) });
-                               else
-                                       minforetriever = () => elementType.GetRuntimeProperty(localname);
+                               exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support native bindings", lineInfo);
 
-                               var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
+                       return false;
+               }
 
+               static bool TrySetValue(object element, BindableProperty property, bool attached, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var bindable = element as BindableObject;
+                       var nativeBindingService = DependencyService.Get<INativeBindingService>();
+
+                       if (property == null)
+                               return false;
+
+                       Func<MemberInfo> minforetriever;
+                       if (attached)
+                               minforetriever = () => property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new [] { typeof(BindableObject) });
+                       else
+                               minforetriever = () => property.DeclaringType.GetRuntimeProperty(property.PropertyName);
+                       var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
+
+                       if (bindable != null) {
                                //SetValue doesn't throw on mismatching type, so check before to get a chance to try the property setting or the collection adding
                                var nullable = property.ReturnTypeInfo.IsGenericType &&
-                                              property.ReturnTypeInfo.GetGenericTypeDefinition() == typeof (Nullable<>);
+                                                          property.ReturnTypeInfo.GetGenericTypeDefinition() == typeof(Nullable<>);
                                if ((convertedValue == null && (!property.ReturnTypeInfo.IsValueType || nullable)) ||
-                                   (property.ReturnType.IsInstanceOfType(convertedValue)))
-                               {
-                                       ((BindableObject)xamlelement).SetValue(property, convertedValue);
-                                       return;
+                                       (property.ReturnType.IsInstanceOfType(convertedValue))) {
+                                       bindable.SetValue(property, convertedValue);
+                                       return true;
                                }
+                               return false;
                        }
 
-                       var exception = new XamlParseException(
-                               String.Format("No Property of name {0} found", propertyName.LocalName), lineInfo);
+                       if (nativeBindingService != null && nativeBindingService.TrySetValue(element, property, convertedValue))
+                               return true;
 
-                       //If we can assign that value to a normal property, let's do it
-                       var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localname);
+                       exception = new XamlParseException($"{elementType.Name} is not a BindableObject or does not support setting native BindableProperties", lineInfo);
+                       return false;
+               }
+
+               static bool TrySetProperty(object element, string localName, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName);
                        MethodInfo setter;
-                       if (propertyInfo != null && propertyInfo.CanWrite && (setter = propertyInfo.SetMethod) != null)
-                       {
-                               object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider);
-                               if (convertedValue == null || propertyInfo.PropertyType.IsInstanceOfType(convertedValue))
-                               {
-                                       try
-                                       {
-                                               setter.Invoke(xamlelement, new[] { convertedValue });
-                                               return;
-                                       }
-                                       catch (ArgumentException)
-                                       {
-                                       }
-                               }
-                               else
-                               {
-                                       exception = new XamlParseException(
-                                               String.Format("Cannot assign property \"{0}\": type mismatch between \"{1}\" and \"{2}\"", propertyName.LocalName,
-                                                       value.GetType(), propertyInfo.PropertyType), lineInfo);
-                               }
-                       }
+                       if (propertyInfo == null || !propertyInfo.CanWrite || (setter = propertyInfo.SetMethod) == null)
+                               return false;
 
-                       //If it's an already initialized property, add to it
+                       object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider);
+                       if (convertedValue != null && !propertyInfo.PropertyType.IsInstanceOfType(convertedValue))
+                               return false;
+
+                       setter.Invoke(element, new object [] { convertedValue });
+                       return true;
+               }
+
+               static bool TryAddToProperty(object element, string localName, object value, IXmlLineInfo lineInfo, XamlServiceProvider serviceProvider, out Exception exception)
+               {
+                       exception = null;
+
+                       var elementType = element.GetType();
+                       var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localName);
                        MethodInfo getter;
-                       if (propertyInfo != null && propertyInfo.CanRead && (getter = propertyInfo.GetMethod) != null)
-                       {
-                               IEnumerable collection;
-                               MethodInfo addMethod;
-                               if ((collection = getter.Invoke(xamlelement, new object[] { }) as IEnumerable) != null
-                                   &&
-                                   (addMethod =
-                                           collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) !=
-                                   null)
-                               {
-                                       addMethod.Invoke(collection,
-                                                        new[] { value.ConvertTo(addMethod.GetParameters()[0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
-                                       return;
-                               }
-                       }
+                       if (propertyInfo == null || !propertyInfo.CanRead || (getter = propertyInfo.GetMethod) == null)
+                               return false;
 
-                       if (context.DoNotThrowOnExceptions)
-                               System.Diagnostics.Debug.WriteLine (exception.Message);
-                       else
-                               throw exception;
+                       var collection = getter.Invoke(element, new object [] { }) as IEnumerable;
+                       if (collection == null)
+                               return false;
+
+                       var addMethod = collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
+                       if (addMethod == null)
+                               return false;
+
+                       addMethod.Invoke(collection, new [] { value.ConvertTo(addMethod.GetParameters() [0].ParameterType, (Func<TypeConverter>)null, serviceProvider) });
+                       return true;
                }
 
                void SetTemplate(ElementTemplate dt, INode node)
                {
 #pragma warning disable 0612
-                       ((IDataTemplate)dt).LoadTemplate = () =>
-                       {
+                       ((IDataTemplate)dt).LoadTemplate = () => {
 #pragma warning restore 0612
                                var cnode = node.Clone();
                                var context = new HydratationContext { ParentContext = Context, RootElement = Context.RootElement };
@@ -442,7 +475,7 @@ namespace Xamarin.Forms.Xaml
                                cnode.Accept(new RegisterXNamesVisitor(context), null);
                                cnode.Accept(new FillResourceDictionariesVisitor(context), null);
                                cnode.Accept(new ApplyPropertiesVisitor(context, true), null);
-                               return context.Values[cnode];
+                               return context.Values [cnode];
                        };
                }
        }
index a81ed11..7e377ea 100644 (file)
@@ -1,5 +1,5 @@
 //
-// InternalExtensions.cs
+// TypeConversionExtensions.cs
 //
 // Author:
 //       Stephane Delcroix <stephane@mi8.be>
@@ -153,9 +153,18 @@ namespace Xamarin.Forms.Xaml
                        if (value != null)
                        {
                                var cast = value.GetType().GetRuntimeMethod("op_Implicit", new[] { value.GetType() });
-                               if (cast != null && cast.ReturnType == toType)
-                                       value = cast.Invoke(null, new[] { value });
+                               if (cast != null && cast.ReturnType == toType) {
+                                       value = cast.Invoke(null, new [] { value });
+                                       return value;
+                               }
                        }
+
+                       var nativeValueConverterService = DependencyService.Get<INativeValueConverterService>();
+
+                       object nativeValue = null;
+                       if (nativeValueConverterService != null && nativeValueConverterService.ConvertTo(value, toType, out nativeValue))
+                               return nativeValue;
+
                        return value;
                }
        }
index 7dd79d7..6042475 100644 (file)
@@ -38,7 +38,10 @@ namespace Xamarin.Forms.Xaml
        {
                public static void ParseXaml(RootNode rootNode, XmlReader reader)
                {
-                       var attributes = ParseXamlAttributes(reader);
+                       IList<KeyValuePair<string, string>> xmlns;
+                       var attributes = ParseXamlAttributes(reader, out xmlns);
+                       var prefixes = PrefixesToIgnore(xmlns);
+                       (rootNode.IgnorablePrefixes ?? (rootNode.IgnorablePrefixes=new List<string>())).AddRange(prefixes);
                        rootNode.Properties.AddRange(attributes);
                        ParseXamlElementFor(rootNode, reader);
                }
@@ -136,8 +139,10 @@ namespace Xamarin.Forms.Xaml
                                                var elementName = reader.Name;
                                                var elementNsUri = reader.NamespaceURI;
                                                var elementXmlInfo = (IXmlLineInfo)reader;
+                                               IList<KeyValuePair<string, string>> xmlns;
 
-                                               var attributes = ParseXamlAttributes(reader);
+                                               var attributes = ParseXamlAttributes(reader, out xmlns);
+                                               var prefixes = PrefixesToIgnore(xmlns);
 
                                                IList<XmlType> typeArguments = null;
                                                if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments))
@@ -149,6 +154,7 @@ namespace Xamarin.Forms.Xaml
                                                node = new ElementNode(new XmlType(elementNsUri, elementName, typeArguments), elementNsUri,
                                                        reader as IXmlNamespaceResolver, elementXmlInfo.LineNumber, elementXmlInfo.LinePosition);
                                                ((IElementNode)node).Properties.AddRange(attributes);
+                                               (node.IgnorablePrefixes ?? (node.IgnorablePrefixes = new List<string>())).AddRange(prefixes);
 
                                                ParseXamlElementFor((IElementNode)node, reader);
                                                nodes.Add(node);
@@ -170,17 +176,20 @@ namespace Xamarin.Forms.Xaml
                        throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader);
                }
 
-               static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader)
+               static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader, out IList<KeyValuePair<string,string>> xmlns)
                {
                        Debug.Assert(reader.NodeType == XmlNodeType.Element);
                        var attributes = new List<KeyValuePair<XmlName, INode>>();
+                       xmlns = new List<KeyValuePair<string, string>>();
                        for (var i = 0; i < reader.AttributeCount; i++)
                        {
                                reader.MoveToAttribute(i);
 
                                //skip xmlns
-                               if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
+                               if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/") {
+                                       xmlns.Add(new KeyValuePair<string, string>(reader.LocalName, reader.Value));
                                        continue;
+                               }
 
                                var propertyName = new XmlName(reader.NamespaceURI, reader.LocalName);
 
@@ -239,6 +248,23 @@ namespace Xamarin.Forms.Xaml
                        return attributes;
                }
 
+               static IList<string> PrefixesToIgnore(IList<KeyValuePair<string, string>> xmlns)
+               {
+                       var prefixes = new List<string>();
+                       foreach (var kvp in xmlns) {
+                               var prefix = kvp.Key;
+
+                               string typeName = null, ns = null, asm = null, targetPlatform = null;
+                               XmlnsHelper.ParseXmlns(kvp.Value, out typeName, out ns, out asm, out targetPlatform);
+                               if (targetPlatform == null)
+                                       continue;
+                               TargetPlatform os;
+                               if (Enum.TryParse<TargetPlatform>(targetPlatform, out os) && os != Device.OS)
+                                       prefixes.Add(prefix);
+                       }
+                       return prefixes;
+               }
+
                static IValueNode GetValueNode(object value, XmlReader reader)
                {
                        var valueString = value as string;
@@ -264,31 +290,26 @@ namespace Xamarin.Forms.Xaml
                        var typeArguments = xmlType.TypeArguments;
                        exception = null;
 
-                       List<Tuple<string, Assembly>> lookupAssemblies = new List<Tuple<string, Assembly>>();
-                       List<string> lookupNames = new List<string>();
+                       var lookupAssemblies = new List<Tuple<string, string>>(); //namespace, assemblyqualifiednamed
+                       var lookupNames = new List<string>();
 
                        if (!XmlnsHelper.IsCustom(namespaceURI))
                        {
-                               lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms", typeof (View).GetTypeInfo().Assembly));
-                               lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly));
+                               lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms", typeof (View).GetTypeInfo().Assembly.FullName));
+                               lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly.FullName));
                        }
                        else if (namespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml" ||
                                 namespaceURI == "http://schemas.microsoft.com/winfx/2006/xaml")
                        {
-                               lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly));
-                               lookupAssemblies.Add(new Tuple<string, Assembly>("System", typeof (object).GetTypeInfo().Assembly));
-                               lookupAssemblies.Add(new Tuple<string, Assembly>("System", typeof (Uri).GetTypeInfo().Assembly)); //System.dll
+                               lookupAssemblies.Add(new Tuple<string, string>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly.FullName));
+                               lookupAssemblies.Add(new Tuple<string, string>("System", typeof (object).GetTypeInfo().Assembly.FullName)); //mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+                               lookupAssemblies.Add(new Tuple<string, string>("System", typeof (Uri).GetTypeInfo().Assembly.FullName)); //System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
                        }
                        else
                        {
-                               string ns;
-                               string typename;
-                               string asmstring;
-                               Assembly asm;
-
-                               XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring);
-                               asm = asmstring == null ? currentAssembly : Assembly.Load(new AssemblyName(asmstring));
-                               lookupAssemblies.Add(new Tuple<string, Assembly>(ns, asm));
+                               string ns, asmstring, _;
+                               XmlnsHelper.ParseXmlns(namespaceURI, out _, out ns, out asmstring, out _);
+                               lookupAssemblies.Add(new Tuple<string, string>(ns, asmstring ?? currentAssembly.FullName));
                        }
 
                        lookupNames.Add(elementName);
@@ -305,16 +326,12 @@ namespace Xamarin.Forms.Xaml
                        }
 
                        Type type = null;
-                       foreach (var asm in lookupAssemblies)
-                       {
-                               if (type != null)
-                                       break;
+                       foreach (var asm in lookupAssemblies) {
                                foreach (var name in lookupNames)
-                               {
-                                       if (type != null)
+                                       if ((type = Type.GetType($"{asm.Item1}.{name}, {asm.Item2}")) != null)
                                                break;
-                                       type = asm.Item2.GetType(asm.Item1 + "." + name);
-                               }
+                               if (type != null)
+                                       break;
                        }
 
                        if (type != null && typeArguments != null)
@@ -340,11 +357,7 @@ namespace Xamarin.Forms.Xaml
                        }
 
                        if (type == null)
-                       {
-                               exception = new XamlParseException(string.Format("Type {0} not found in xmlns {1}", elementName, namespaceURI),
-                                       xmlInfo);
-                               return null;
-                       }
+                               exception = new XamlParseException($"Type {elementName} not found in xmlns {namespaceURI}", xmlInfo);
 
                        return type;
                }
index 778d294..e3e37de 100644 (file)
@@ -20,15 +20,16 @@ namespace Xamarin.Forms.Xaml
                        string typeName;
                        string ns;
                        string asm;
+                       string targetPlatform;
 
-                       ParseXmlns(xmlns, out typeName, out ns, out asm);
+                       ParseXmlns(xmlns, out typeName, out ns, out asm, out targetPlatform);
 
                        return ns;
                }
 
-               public static void ParseXmlns(string xmlns, out string typeName, out string ns, out string asm)
+               public static void ParseXmlns(string xmlns, out string typeName, out string ns, out string asm, out string targetPlatform)
                {
-                       typeName = ns = asm = null;
+                       typeName = ns = asm = targetPlatform = null;
 
                        foreach (var decl in xmlns.Split(';'))
                        {
@@ -42,6 +43,10 @@ namespace Xamarin.Forms.Xaml
                                        asm = decl.Substring(9, decl.Length - 9);
                                        continue;
                                }
+                               if (decl.StartsWith("targetPlatform=", StringComparison.Ordinal)) {
+                                       targetPlatform = decl.Substring(15, decl.Length - 15);
+                                       continue;
+                               }
                                var nsind = decl.LastIndexOf(".", StringComparison.Ordinal);
                                if (nsind > 0)
                                {