diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-09-08 20:45:43 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-09-08 11:45:43 -0700 |
commit | 3b7d798fdda51a669683ed7d5c3770ebf3adfa77 (patch) | |
tree | 8e4d16d91e9a6cb5a49a8aecf8514a8c84f36b97 | |
parent | 85426c5d9495eb1d55b3128bf97e50c68a73b53f (diff) | |
download | xamarin-forms-3b7d798fdda51a669683ed7d5c3770ebf3adfa77.tar.gz xamarin-forms-3b7d798fdda51a669683ed7d5c3770ebf3adfa77.tar.bz2 xamarin-forms-3b7d798fdda51a669683ed7d5c3770ebf3adfa77.zip |
[Xaml] support native views and native bindings (#266)
Allows including Native views directly in xaml.
Support for ios, android, UWP
29 files changed, 873 insertions, 214 deletions
diff --git a/Xamarin.Forms.Build.Tasks/XamlGTask.cs b/Xamarin.Forms.Build.Tasks/XamlGTask.cs index 72375d6b..79a49c46 100644 --- a/Xamarin.Forms.Build.Tasks/XamlGTask.cs +++ b/Xamarin.Forms.Build.Tasks/XamlGTask.cs @@ -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"]; diff --git a/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs b/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs index e0810633..52460418 100644 --- a/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/XmlTypeExtensions.cs @@ -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)); } diff --git a/Xamarin.Forms.Controls/CoreGallery.cs b/Xamarin.Forms.Controls/CoreGallery.cs index 06f214b4..a7743400 100644 --- a/Xamarin.Forms.Controls/CoreGallery.cs +++ b/Xamarin.Forms.Controls/CoreGallery.cs @@ -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 index 00000000..7a1fb850 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml @@ -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 index 00000000..1e40fdb7 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/XamlNativeViews.xaml.cs @@ -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 diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj index 3b02adbe..7ad923e0 100644 --- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj +++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj @@ -236,6 +236,9 @@ <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" /> @@ -257,6 +260,9 @@ <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')" /> @@ -308,4 +314,4 @@ </CreateItem> <Copy SourceFiles="@(ConfigFile)" DestinationFiles="controlgallery.config" Condition="!Exists('controlgallery.config')" /> </Target> -</Project>
\ No newline at end of file +</Project> diff --git a/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs index e7c7f9a8..66577f63 100644 --- a/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs +++ b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs @@ -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 index 00000000..d926dae4 --- /dev/null +++ b/Xamarin.Forms.Core/INativeBindingService.cs @@ -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 index 00000000..4309be9a --- /dev/null +++ b/Xamarin.Forms.Core/INativeValueConverterService.cs @@ -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 diff --git a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj index 9e961521..a07b88ca 100644 --- a/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj +++ b/Xamarin.Forms.Core/Xamarin.Forms.Core.csproj @@ -433,6 +433,8 @@ <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> @@ -447,4 +449,4 @@ </PostBuildEvent> </PropertyGroup> <ItemGroup /> -</Project>
\ No newline at end of file +</Project> diff --git a/Xamarin.Forms.Core/XamlParseException.cs b/Xamarin.Forms.Core/XamlParseException.cs index 42e8b618..d953da0f 100644 --- a/Xamarin.Forms.Core/XamlParseException.cs +++ b/Xamarin.Forms.Core/XamlParseException.cs @@ -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 index 00000000..3dd4fe3c --- /dev/null +++ b/Xamarin.Forms.Platform.Android/NativeBindingservice.cs @@ -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 index 00000000..ff6faa90 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/NativeValueConverterService.cs @@ -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 diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj index 00179717..2852b85b 100644 --- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj +++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj @@ -242,6 +242,8 @@ <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 index 00000000..e999af2a --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/NativeBindingService.cs @@ -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 index 00000000..1f0ef68a --- /dev/null +++ b/Xamarin.Forms.Platform.UAP/NativeValueConverterService.cs @@ -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; + } + } +} diff --git a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj index 81ec3ff3..4f383064 100644 --- a/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj +++ b/Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj @@ -189,6 +189,8 @@ <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 index 00000000..3756e2f0 --- /dev/null +++ b/Xamarin.Forms.Platform.iOS/NativeBindingService.cs @@ -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 index 00000000..9e20e045 --- /dev/null +++ b/Xamarin.Forms.Platform.iOS/NativeValueConverterService.cs @@ -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 diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj index 8fde4c81..e31aa3ac 100644 --- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj +++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj @@ -150,6 +150,8 @@ <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" /> @@ -205,4 +207,4 @@ <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> </ItemGroup> -</Project>
\ No newline at end of file +</Project> diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/BPNotResolvedOnSubClass.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/BPNotResolvedOnSubClass.xaml.cs index 65bb73f1..c6913949 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Issues/BPNotResolvedOnSubClass.xaml.cs +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/BPNotResolvedOnSubClass.xaml.cs @@ -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) diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Issue1497.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Issue1497.cs index 3813f73a..b35339d5 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Issues/Issue1497.cs +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Issue1497.cs @@ -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 index 00000000..a35f8e50 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml @@ -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 index 00000000..ce02d63b --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/NativeViewsAndBindings.xaml.cs @@ -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 diff --git a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj index 86e2eeb0..26bf6d41 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj +++ b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj @@ -359,6 +359,9 @@ <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" /> @@ -638,6 +641,9 @@ <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}" /> diff --git a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs index 327a4122..36f5b7fd 100644 --- a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs +++ b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs @@ -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]; }; } } diff --git a/Xamarin.Forms.Xaml/TypeConversionExtensions.cs b/Xamarin.Forms.Xaml/TypeConversionExtensions.cs index a81ed11d..7e377ea4 100644 --- a/Xamarin.Forms.Xaml/TypeConversionExtensions.cs +++ b/Xamarin.Forms.Xaml/TypeConversionExtensions.cs @@ -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; } } diff --git a/Xamarin.Forms.Xaml/XamlParser.cs b/Xamarin.Forms.Xaml/XamlParser.cs index 7dd79d77..60424754 100644 --- a/Xamarin.Forms.Xaml/XamlParser.cs +++ b/Xamarin.Forms.Xaml/XamlParser.cs @@ -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; } diff --git a/Xamarin.Forms.Xaml/XmlnsHelper.cs b/Xamarin.Forms.Xaml/XmlnsHelper.cs index 778d2947..e3e37de4 100644 --- a/Xamarin.Forms.Xaml/XmlnsHelper.cs +++ b/Xamarin.Forms.Xaml/XmlnsHelper.cs @@ -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) { |