[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
This commit is contained in:
Stephane Delcroix 2016-07-19 01:17:29 +02:00 коммит произвёл Jason Smith
Родитель 272033723e
Коммит f304f25df2
10 изменённых файлов: 179 добавлений и 14 удалений

Просмотреть файл

@ -14,8 +14,8 @@
<PropertyGroup>
<CompileDependsOn>
$(CompileDependsOn);
GenerateDebugCode;
XamlC;
GenerateDebugCode;
</CompileDependsOn>
</PropertyGroup>

Просмотреть файл

@ -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 =

Просмотреть файл

@ -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);

Просмотреть файл

@ -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}" />

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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>());
}
}
}
}

Просмотреть файл

@ -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))

Просмотреть файл

@ -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&lt;Type,string&gt; XamlFileProvider { get; }" />
<MemberSignature Language="ILAsm" Value=".property class System.Func`2&lt;class System.Type, string&gt; XamlFileProvider" />
<MemberType>Property</MemberType>
<AssemblyInfo>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
</AssemblyInfo>
<ReturnValue>
<ReturnType>System.Func&lt;System.Type,System.String&gt;</ReturnType>
</ReturnValue>
<Docs>
<summary>To be added.</summary>
<value>To be added.</value>
<remarks>To be added.</remarks>
</Docs>
</Member>
</Members>
</Type>

Просмотреть файл

@ -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" />