diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-07-19 01:17:29 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-07-18 16:17:29 -0700 |
commit | f304f25df2d80094d2c31fda4986f92454599a7e (patch) | |
tree | e9b9a32bea7e5f8c5e07fbcb4cabbaa5abb0c2f8 | |
parent | 272033723ea275ceb8a288fa605eafd035c79f2d (diff) | |
download | xamarin-forms-f304f25df2d80094d2c31fda4986f92454599a7e.tar.gz xamarin-forms-f304f25df2d80094d2c31fda4986f92454599a7e.tar.bz2 xamarin-forms-f304f25df2d80094d2c31fda4986f92454599a7e.zip |
[Xaml] allow the Previewer to provide their own Xaml files for any type (#262)
* [Xaml] allow the Previewer to provide their own Xaml files for any type
* [Xaml] use a Func instead of an interface, easier to use by reflection. Add tests
* [XamlC] move the InitializeComponent duplication to XamlC task
* [XamlC] generate branching code
* [XamlC] fix the XamlC issue
* [XamlC] make the API public
* [docs] fix docs
-rw-r--r-- | .nuspec/Xamarin.Forms.Debug.targets | 2 | ||||
-rw-r--r-- | Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs | 15 | ||||
-rw-r--r-- | Xamarin.Forms.Build.Tasks/XamlCTask.cs | 49 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj | 6 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs | 3 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml | 6 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs | 55 | ||||
-rw-r--r-- | Xamarin.Forms.Xaml/XamlLoader.cs | 22 | ||||
-rw-r--r-- | docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml | 34 | ||||
-rw-r--r-- | docs/Xamarin.Forms.Xaml/index.xml | 1 |
10 files changed, 179 insertions, 14 deletions
diff --git a/.nuspec/Xamarin.Forms.Debug.targets b/.nuspec/Xamarin.Forms.Debug.targets index 68f61bd7..ce197713 100644 --- a/.nuspec/Xamarin.Forms.Debug.targets +++ b/.nuspec/Xamarin.Forms.Debug.targets @@ -14,8 +14,8 @@ <PropertyGroup> <CompileDependsOn> $(CompileDependsOn); - GenerateDebugCode; XamlC; + GenerateDebugCode; </CompileDependsOn> </PropertyGroup> diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs index bec6386d..e0f81972 100644 --- a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs +++ b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs @@ -67,18 +67,11 @@ namespace Xamarin.Forms.Build.Tasks LogLine(2, "no InitializeComponent found... skipped."); continue; } - if (typeDef.Methods.FirstOrDefault(md => md.Name == "InitCompRuntime") != null) - { - LogLine(2, "InitCompRuntime already exists... skipped"); + var initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); + if (initCompRuntime == null) { + LogLine(2, "no __InitComponentRuntime found... skipped."); continue; } - LogLine(2, ""); - - LogString(2, " Duplicating {0}.InitializeComponent () into {0}.InitCompRuntime ... ", typeDef.Name); - var initCompRuntime = new MethodDefinition("InitCompRuntime", initComp.Attributes, initComp.ReturnType); - initCompRuntime.Body = initComp.Body; - typeDef.Methods.Add(initCompRuntime); - LogLine(2, "done."); // IL_0000: ldarg.0 // IL_0001: callvirt instance void class [Xamarin.Forms.Core]Xamarin.Forms.ContentPage::'.ctor'() @@ -92,7 +85,7 @@ namespace Xamarin.Forms.Build.Tasks // IL_0013: br IL_001e // // IL_0018: ldarg.0 - // IL_0019: callvirt instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::InitCompRuntime() + // IL_0019: callvirt instance void class Xamarin.Forms.Xaml.XamlcTests.MyPage::__InitComponentRuntime() // IL_001e: ret var altCtor = diff --git a/Xamarin.Forms.Build.Tasks/XamlCTask.cs b/Xamarin.Forms.Build.Tasks/XamlCTask.cs index 19548b80..b6387c0f 100644 --- a/Xamarin.Forms.Build.Tasks/XamlCTask.cs +++ b/Xamarin.Forms.Build.Tasks/XamlCTask.cs @@ -232,6 +232,16 @@ namespace Xamarin.Forms.Build.Tasks } LogLine(2, ""); + if (typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime") != null) { + LogLine(2, " __InitComponentRuntime already exists... not duplicating"); + } else { + LogString(2, " Duplicating {0}.InitializeComponent () into {0}.__InitComponentRuntime ... ", typeDef.Name); + var initCompRuntime = new MethodDefinition("__InitComponentRuntime", initComp.Attributes, initComp.ReturnType); + initCompRuntime.Body = initComp.Body; + typeDef.Methods.Add(initCompRuntime); + LogLine(2, "done."); + } + LogString(2, " Parsing Xaml... "); var rootnode = ParseXaml(resource.GetResourceStream(), typeDef); if (rootnode == null) @@ -249,6 +259,45 @@ namespace Xamarin.Forms.Build.Tasks var body = new MethodBody(initComp); var il = body.GetILProcessor(); il.Emit(OpCodes.Nop); + + // Generating branching code for the Previewer + // IL_0007: call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider() + // IL_000c: brfalse IL_0031 + // IL_0011: call class [mscorlib]System.Func`2<class [mscorlib]System.Type,string> class [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider() + // IL_0016: ldarg.0 + // IL_0017: call instance class [mscorlib]System.Type object::GetType() + // IL_001c: callvirt instance !1 class [mscorlib]System.Func`2<class [mscorlib]System.Type, string>::Invoke(!0) + // IL_0021: brfalse IL_0031 + // IL_0026: ldarg.0 + // IL_0027: call instance void class Xamarin.Forms.Xaml.UnitTests.XamlLoaderGetXamlForTypeTests::__InitComponentRuntime() + // IL_002c: ret + // IL_0031: nop + + var nop = Instruction.Create(OpCodes.Nop); + var getXamlFileProvider = body.Method.Module.Import(body.Method.Module.Import(typeof(Xamarin.Forms.Xaml.Internals.XamlLoader)) + .Resolve() + .Properties.FirstOrDefault(pd => pd.Name == "XamlFileProvider") + .GetMethod); + il.Emit(OpCodes.Call, getXamlFileProvider); + il.Emit(OpCodes.Brfalse, nop); + il.Emit(OpCodes.Call, getXamlFileProvider); + il.Emit(OpCodes.Ldarg_0); + var getType = body.Method.Module.Import(body.Method.Module.Import(typeof(object)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "GetType")); + il.Emit(OpCodes.Call, getType); + var func = body.Method.Module.Import(body.Method.Module.Import(typeof(Func<Type, string>)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "Invoke")); + func = func.ResolveGenericParameters(body.Method.Module.Import(typeof(Func<Type, string>)), body.Method.Module); + il.Emit(OpCodes.Callvirt, func); + il.Emit(OpCodes.Brfalse, nop); + il.Emit(OpCodes.Ldarg_0); + var initCompRuntime = typeDef.Methods.FirstOrDefault(md => md.Name == "__InitComponentRuntime"); + il.Emit(OpCodes.Call, initCompRuntime); + il.Emit(OpCodes.Ret); + il.Append(nop); + var visitorContext = new ILContext(il, body); rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null); diff --git a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj index 417e800a..cd22258c 100644 --- a/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj +++ b/Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj @@ -347,6 +347,9 @@ <Compile Include="SharedResourceDictionary2.xaml.cs"> <DependentUpon>SharedResourceDictionary2.xaml</DependentUpon> </Compile> + <Compile Include="XamlLoaderGetXamlForTypeTests.xaml.cs"> + <DependentUpon>XamlLoaderGetXamlForTypeTests.xaml</DependentUpon> + </Compile> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" /> @@ -617,6 +620,9 @@ <EmbeddedResource Include="SharedResourceDictionary2.xaml"> <Generator>MSBuild:UpdateDesignTimeXaml</Generator> </EmbeddedResource> + <EmbeddedResource Include="XamlLoaderGetXamlForTypeTests.xaml"> + <Generator>MSBuild:UpdateDesignTimeXaml</Generator> + </EmbeddedResource> </ItemGroup> <ItemGroup> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs index a175b57f..23c72a00 100644 --- a/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs +++ b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs @@ -3,6 +3,8 @@ using NUnit.Framework; namespace Xamarin.Forms.Xaml.UnitTests { +#pragma warning disable 0618 //retaining legacy call to obsolete code + [TestFixture] public class XamlLoaderCreateTests { @@ -35,4 +37,5 @@ namespace Xamarin.Forms.Xaml.UnitTests Assert.NotNull (button); } } +#pragma warning restore 0618 }
\ No newline at end of file diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml new file mode 100644 index 00000000..ec551771 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" + x:Class="Xamarin.Forms.Xaml.UnitTests.XamlLoaderGetXamlForTypeTests"> + <Button x:Name="Button"/> +</ContentPage> diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs new file mode 100644 index 00000000..10c3f2e2 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +using Xamarin.Forms; + +namespace Xamarin.Forms.Xaml.UnitTests +{ + public partial class XamlLoaderGetXamlForTypeTests : ContentPage + { + public XamlLoaderGetXamlForTypeTests() + { + InitializeComponent(); + } + + public XamlLoaderGetXamlForTypeTests(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + public class Tests + { + [SetUp] + public void SetUp() + { + Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = null; + } + + [TestCase(false)] + [TestCase(true)] + public void XamlContentIsReplaced(bool useCompiledXaml) + { + var layout = new XamlLoaderGetXamlForTypeTests(useCompiledXaml); + Assert.That(layout.Content, Is.TypeOf<Button>()); + + Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider = (t) => { + if (t == typeof(XamlLoaderGetXamlForTypeTests)) + return @" + <ContentPage xmlns=""http://xamarin.com/schemas/2014/forms"" + xmlns:x=""http://schemas.microsoft.com/winfx/2009/xaml"" + x:Class=""Xamarin.Forms.Xaml.UnitTests.XamlLoaderGetXamlForTypeTests""> + <Label x:Name=""Label""/> + </ContentPage>"; + return null; + }; + + layout = new XamlLoaderGetXamlForTypeTests(useCompiledXaml); + Assert.That(layout.Content, Is.TypeOf<Label>()); + } + } + } +} + diff --git a/Xamarin.Forms.Xaml/XamlLoader.cs b/Xamarin.Forms.Xaml/XamlLoader.cs index 37bea4e4..40414092 100644 --- a/Xamarin.Forms.Xaml/XamlLoader.cs +++ b/Xamarin.Forms.Xaml/XamlLoader.cs @@ -33,6 +33,15 @@ using System.Reflection; using System.Text.RegularExpressions; using System.Xml; +namespace Xamarin.Forms.Xaml.Internals +{ + public static class XamlLoader + { + public static Func<Type, string> XamlFileProvider { get; internal set; } + internal static bool DoNotThrowOnExceptions { get; set; } + } +} + namespace Xamarin.Forms.Xaml { internal static class XamlLoader @@ -64,12 +73,16 @@ namespace Xamarin.Forms.Xaml var rootnode = new RuntimeRootNode (new XmlType (reader.NamespaceURI, reader.Name, null), view, (IXmlNamespaceResolver)reader); XamlParser.ParseXaml (rootnode, reader); - Visit (rootnode, new HydratationContext { RootElement = view }); + Visit (rootnode, new HydratationContext { + RootElement = view, + DoNotThrowOnExceptions = Xamarin.Forms.Xaml.Internals.XamlLoader.DoNotThrowOnExceptions + }); break; } } } + [Obsolete ("Use the XamlFileProvider to provide xaml files. We will remove this when Cycle 8 hits Stable.")] public static object Create (string xaml, bool doNotThrow = false) { object inflatedView = null; @@ -113,6 +126,12 @@ namespace Xamarin.Forms.Xaml static string GetXamlForType(Type type) { + string xaml = null; + + //the Previewer might want to provide it's own xaml for this... let them do that + if (Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider != null && (xaml = Xamarin.Forms.Xaml.Internals.XamlLoader.XamlFileProvider(type)) != null) + return xaml; + var assembly = type.GetTypeInfo().Assembly; string resourceId; @@ -129,7 +148,6 @@ namespace Xamarin.Forms.Xaml // first pass, pray to find it because the user named it correctly - string xaml = null; foreach (var resource in resourceNames) { if (ResourceMatchesFilename(assembly, resource, likelyResourceName)) diff --git a/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml b/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml new file mode 100644 index 00000000..ae6d96b9 --- /dev/null +++ b/docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml @@ -0,0 +1,34 @@ +<Type Name="XamlLoader" FullName="Xamarin.Forms.Xaml.Internals.XamlLoader"> + <TypeSignature Language="C#" Value="public static class XamlLoader" /> + <TypeSignature Language="ILAsm" Value=".class public auto ansi abstract sealed beforefieldinit XamlLoader extends System.Object" /> + <AssemblyInfo> + <AssemblyName>Xamarin.Forms.Xaml</AssemblyName> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <Base> + <BaseTypeName>System.Object</BaseTypeName> + </Base> + <Interfaces /> + <Docs> + <summary>To be added.</summary> + <remarks>To be added.</remarks> + </Docs> + <Members> + <Member MemberName="XamlFileProvider"> + <MemberSignature Language="C#" Value="public static Func<Type,string> XamlFileProvider { get; }" /> + <MemberSignature Language="ILAsm" Value=".property class System.Func`2<class System.Type, string> XamlFileProvider" /> + <MemberType>Property</MemberType> + <AssemblyInfo> + <AssemblyVersion>2.0.0.0</AssemblyVersion> + </AssemblyInfo> + <ReturnValue> + <ReturnType>System.Func<System.Type,System.String></ReturnType> + </ReturnValue> + <Docs> + <summary>To be added.</summary> + <value>To be added.</value> + <remarks>To be added.</remarks> + </Docs> + </Member> + </Members> +</Type> diff --git a/docs/Xamarin.Forms.Xaml/index.xml b/docs/Xamarin.Forms.Xaml/index.xml index 1c4a761e..4627dd66 100644 --- a/docs/Xamarin.Forms.Xaml/index.xml +++ b/docs/Xamarin.Forms.Xaml/index.xml @@ -75,6 +75,7 @@ <Namespace Name="Xamarin.Forms.Xaml.Internals"> <Type Name="NameScopeProvider" Kind="Class" /> <Type Name="SimpleValueTargetProvider" Kind="Class" /> + <Type Name="XamlLoader" Kind="Class" /> <Type Name="XamlServiceProvider" Kind="Class" /> <Type Name="XamlTypeResolver" Kind="Class" /> <Type Name="XmlLineInfoProvider" Kind="Class" /> |