summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
blob: c2137ece5ce8f7aed2c0d403a2f60fdd3144d461 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using System;
using System.Reflection;
using System.Xml;
using System.Linq;
using Xamarin.Forms.Internals;

namespace Xamarin.Forms.Xaml
{
	[ContentProperty("Key")]
	public sealed class StaticResourceExtension : IMarkupExtension
	{
		public string Key { get; set; }

		public object ProvideValue(IServiceProvider serviceProvider)
		{
			if (serviceProvider == null)
				throw new ArgumentNullException(nameof(serviceProvider));
			if (Key == null) {
				var lineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
				var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
				throw new XamlParseException("you must specify a key in {StaticResource}", lineInfo);
			}
			var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideParentValues;
			if (valueProvider == null)
				throw new ArgumentException();
			var xmlLineInfoProvider = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider;
			var xmlLineInfo = xmlLineInfoProvider != null ? xmlLineInfoProvider.XmlLineInfo : null;
			object resource = null;

			foreach (var p in valueProvider.ParentObjects) {
				var ve = p as VisualElement;
				var resDict = ve?.Resources ?? p as ResourceDictionary;
				if (resDict == null)
					continue;
				if (resDict.TryGetValue(Key, out resource))
					break;
			}
			resource = resource ?? GetApplicationLevelResource(Key, xmlLineInfo);

			var bp = valueProvider.TargetProperty as BindableProperty;
			var pi = valueProvider.TargetProperty as PropertyInfo;
			var propertyType = bp?.ReturnType ?? pi?.PropertyType;
			if (propertyType == null) {
				if (resource.GetType().GetTypeInfo().IsGenericType && (resource.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>) || resource.GetType().GetGenericTypeDefinition() == typeof(OnIdiom<>))) {
					// This is only there to support our backward compat story with pre 2.3.3 compiled Xaml project who was not providing TargetProperty
					var method = resource.GetType().GetRuntimeMethod("op_Implicit", new[] { resource.GetType() });
					resource = method.Invoke(null, new[] { resource });
				}
				return resource;
			}
			if (propertyType.IsAssignableFrom(resource.GetType()))
				return resource;
			var implicit_op =  resource.GetType().GetImplicitConversionOperator(fromType: resource.GetType(), toType: propertyType)
							?? propertyType.GetImplicitConversionOperator(fromType: resource.GetType(), toType: propertyType);
			if (implicit_op != null)
				return implicit_op.Invoke(resource, new [] { resource });

			//Special case for https://bugzilla.xamarin.com/show_bug.cgi?id=59818
			//On OnPlatform, check for an opImplicit from the targetType
				if (Xamarin.Forms.Device.Flags.Contains("xamlDoubleImplicitOpHack")
				    && resource.GetType().GetTypeInfo().IsGenericType
				    && (resource.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>))) {
				var tType = resource.GetType().GenericTypeArguments[0];
				var opImplicit =   tType.GetImplicitConversionOperator(fromType: tType, toType: propertyType)
								?? propertyType.GetImplicitConversionOperator(fromType: tType, toType: propertyType);

				if (opImplicit != null) {
					//convert the OnPlatform<T> to T
					var opPlatformImplicitConversionOperator = resource.GetType().GetImplicitConversionOperator(fromType: resource.GetType(), toType: tType);
					resource = opPlatformImplicitConversionOperator.Invoke(null, new[] { resource });

					//and convert to toType
					resource = opImplicit.Invoke(null, new[] { resource });
					return resource;
				}
			}

			return resource;
		}

		internal object GetApplicationLevelResource(string key, IXmlLineInfo xmlLineInfo)
		{
			object resource;
			if (Application.Current == null || Application.Current.Resources == null || !Application.Current.Resources.TryGetValue(Key, out resource))
				throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
			return resource;
		}
	}
}