From f304f25df2d80094d2c31fda4986f92454599a7e Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Tue, 19 Jul 2016 01:17:29 +0200 Subject: [PATCH] [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 --- .nuspec/Xamarin.Forms.Debug.targets | 2 +- Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs | 15 ++--- Xamarin.Forms.Build.Tasks/XamlCTask.cs | 49 +++++++++++++++++ .../Xamarin.Forms.Xaml.UnitTests.csproj | 6 ++ .../XamlLoaderCreateTests.cs | 3 + .../XamlLoaderGetXamlForTypeTests.xaml | 6 ++ .../XamlLoaderGetXamlForTypeTests.xaml.cs | 55 +++++++++++++++++++ Xamarin.Forms.Xaml/XamlLoader.cs | 22 +++++++- .../XamlLoader.xml | 34 ++++++++++++ docs/Xamarin.Forms.Xaml/index.xml | 1 + 10 files changed, 179 insertions(+), 14 deletions(-) create mode 100644 Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml create mode 100644 Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml.cs create mode 100644 docs/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.Internals/XamlLoader.xml diff --git a/.nuspec/Xamarin.Forms.Debug.targets b/.nuspec/Xamarin.Forms.Debug.targets index 68f61bd73..ce1977138 100644 --- a/.nuspec/Xamarin.Forms.Debug.targets +++ b/.nuspec/Xamarin.Forms.Debug.targets @@ -14,8 +14,8 @@ $(CompileDependsOn); - GenerateDebugCode; XamlC; + GenerateDebugCode; diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs index bec6386db..e0f819728 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 19548b80b..b6387c0f4 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 [Xamarin.Forms.Xaml.Internals]Xamarin.Forms.Xaml.XamlLoader::get_XamlFileProvider() + // IL_000c: brfalse IL_0031 + // IL_0011: call class [mscorlib]System.Func`2 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::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)) + .Resolve() + .Methods.FirstOrDefault(md => md.Name == "Invoke")); + func = func.ResolveGenericParameters(body.Method.Module.Import(typeof(Func)), 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 417e800ae..cd22258cf 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 @@ SharedResourceDictionary2.xaml + + XamlLoaderGetXamlForTypeTests.xaml + @@ -617,6 +620,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + diff --git a/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs index a175b57f8..23c72a00d 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 000000000..ec5517712 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/XamlLoaderGetXamlForTypeTests.xaml @@ -0,0 +1,6 @@ + + +