diff options
7 files changed, 86 insertions, 6 deletions
diff --git a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs index 159bbf5f..5f1826db 100644 --- a/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs +++ b/Xamarin.Forms.Build.Tasks/BindablePropertyReferenceExtensions.cs @@ -26,7 +26,7 @@ namespace Xamarin.Forms.Build.Tasks md.IsStatic && md.IsPublic && md.Parameters.Count == 1 && - md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.Item1; + md.Parameters[0].ParameterType.InheritsFromOrImplements(module.Import(typeof(BindableObject))), module).SingleOrDefault()?.Item1; if (getter == null) throw new XamlParseException($"Missing a public static Get{bpName} or a public instance property getter for the attached property \"{bpRef.DeclaringType}.{bpRef.Name}\"", iXmlLineInfo); @@ -43,7 +43,7 @@ namespace Xamarin.Forms.Build.Tasks md.IsStatic && md.IsPublic && md.Parameters.Count == 1 && - md.Parameters [0].ParameterType.FullName == "Xamarin.Forms.BindableObject", module).SingleOrDefault()?.Item1; + md.Parameters[0].ParameterType.InheritsFromOrImplements(module.Import(typeof(BindableObject))), module).SingleOrDefault()?.Item1; var attributes = new List<CustomAttribute>(); if (property != null && property.HasCustomAttributes) diff --git a/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs b/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs index 92a53539..c1d11487 100644 --- a/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs +++ b/Xamarin.Forms.Build.Tasks/CompiledConverters/BindablePropertyConverter.cs @@ -18,7 +18,12 @@ namespace Xamarin.Forms.Core.XamlC yield return Instruction.Create(OpCodes.Ldnull); yield break; } + var bpRef = GetBindablePropertyFieldReference(value, module, node); + yield return Instruction.Create(OpCodes.Ldsfld, bpRef); + } + public FieldReference GetBindablePropertyFieldReference(string value, ModuleDefinition module, BaseNode node) + { FieldReference bpRef = null; string typeName = null, propertyName = null; @@ -49,7 +54,7 @@ namespace Xamarin.Forms.Core.XamlC bpRef = GetBindablePropertyFieldReference(typeRef, propertyName, module); if (bpRef == null) throw new XamlParseException($"Can't resolve {propertyName} on {typeRef.Name}", node); - yield return Instruction.Create(OpCodes.Ldsfld, bpRef); + return bpRef; } public static TypeReference GetTypeReference(string xmlType, ModuleDefinition module, BaseNode iNode) diff --git a/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs new file mode 100644 index 00000000..37418bd9 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/ICompiledValueProvider.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Forms.Build.Tasks; + +namespace Xamarin.Forms.Xaml +{ + interface ICompiledValueProvider + { + IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs new file mode 100644 index 00000000..163bd9e7 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/CompiledValueProviders/SetterValueProvider.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; + +using Xamarin.Forms.Xaml; +using Xamarin.Forms.Build.Tasks; + +namespace Xamarin.Forms.Core.XamlC +{ + class SetterValueProvider : ICompiledValueProvider + { + public IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ModuleDefinition module, BaseNode node, ILContext context) + { + var valueNode = ((IElementNode)node).Properties[new XmlName("", "Value")]; + + //if it's an elementNode, there's probably no need to convert it + if (valueNode is IElementNode) + yield break; + + var value = ((string)((ValueNode)valueNode).Value); + var bpNode = ((ValueNode)((IElementNode)node).Properties[new XmlName("", "Property")]); + var bpRef = (new BindablePropertyConverter()).GetBindablePropertyFieldReference((string)bpNode.Value, module, bpNode); + + TypeReference _; + var setValueRef = module.Import(module.Import(typeof(Setter)).GetProperty(p => p.Name == "Value", out _).SetMethod); + + //push the setter + yield return Instruction.Create(OpCodes.Ldloc, vardefref.VariableDefinition); + + //push the value + foreach (var instruction in ((ValueNode)valueNode).PushConvertedValue(context, bpRef, valueNode.PushServiceProvider(context, bpRef: bpRef), boxValueTypes: true, unboxValueTypes: false)) + yield return instruction; + + //set the value + yield return Instruction.Create(OpCodes.Callvirt, setValueRef); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs index 6a7a3bef..9cf62221 100644 --- a/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs @@ -242,7 +242,8 @@ namespace Xamarin.Forms.Build.Tasks } public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference vardefref, ILContext context, - ModuleDefinition module, ElementNode node, FieldReference bpRef = null, PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null) + ModuleDefinition module, ElementNode node, FieldReference bpRef = null, + PropertyReference propertyRef = null, TypeReference propertyDeclaringTypeRef = null) { GenericInstanceType markupExtension; IList<TypeReference> genericArguments; @@ -306,8 +307,25 @@ namespace Xamarin.Forms.Build.Tasks } else if (context.Variables[node].VariableType.ImplementsInterface(module.Import(typeof (IValueProvider)))) { - var markExt = module.Import(typeof (IValueProvider)).Resolve(); - var provideValueInfo = markExt.Methods.First(md => md.Name == "ProvideValue"); + var valueProviderType = context.Variables[node].VariableType; + //If the IValueProvider has a ProvideCompiledAttribute that can be resolved, shortcut this + var compiledValueProviderName = valueProviderType?.GetCustomAttribute(module.Import(typeof(ProvideCompiledAttribute)))?.ConstructorArguments?[0].Value as string; + Type compiledValueProviderType; + if (compiledValueProviderName != null && (compiledValueProviderType = Type.GetType(compiledValueProviderName)) != null) { + var compiledValueProvider = Activator.CreateInstance(compiledValueProviderType); + var cProvideValue = typeof(ICompiledValueProvider).GetMethods().FirstOrDefault(md => md.Name == "ProvideValue"); + var instructions = (IEnumerable<Instruction>)cProvideValue.Invoke(compiledValueProvider, new object[] { + vardefref, + context.Body.Method.Module, + node as BaseNode, + context}); + foreach (var i in instructions) + yield return i; + yield break; + } + + var valueProviderDef = module.Import(typeof (IValueProvider)).Resolve(); + var provideValueInfo = valueProviderDef.Methods.First(md => md.Name == "ProvideValue"); var provideValue = module.Import(provideValueInfo); vardefref.VariableDefinition = new VariableDefinition(module.TypeSystem.Object); diff --git a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj index ef10720d..51dd60be 100644 --- a/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj +++ b/Xamarin.Forms.Build.Tasks/Xamarin.Forms.Build.Tasks.csproj @@ -105,6 +105,8 @@ <Compile Include="CompiledConverters\ThicknessTypeConverter.cs" /> <Compile Include="MethodBodyExtensions.cs" /> <Compile Include="CompiledConverters\TypeTypeConverter.cs" /> + <Compile Include="CompiledValueProviders\SetterValueProvider.cs" /> + <Compile Include="CompiledValueProviders\ICompiledValueProvider.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Target Name="AfterBuild"> @@ -128,6 +130,7 @@ </ItemGroup> <ItemGroup> <Folder Include="CompiledMarkupExtensions\" /> + <Folder Include="CompiledValueProviders\" /> </ItemGroup> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> diff --git a/Xamarin.Forms.Core/Setter.cs b/Xamarin.Forms.Core/Setter.cs index b260e8f0..ea58d4fd 100644 --- a/Xamarin.Forms.Core/Setter.cs +++ b/Xamarin.Forms.Core/Setter.cs @@ -8,6 +8,7 @@ using Xamarin.Forms.Xaml; namespace Xamarin.Forms { [ContentProperty("Value")] + [ProvideCompiled("Xamarin.Forms.Core.XamlC.SetterValueProvider")] public sealed class Setter : IValueProvider { readonly ConditionalWeakTable<BindableObject, object> _originalValues = new ConditionalWeakTable<BindableObject, object>(); |