summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core/TemplateUtilities.cs
blob: 0599e74706aefd7754fdb0dcdbe0f92b4df49342 (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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace Xamarin.Forms
{
	internal static class TemplateUtilities
	{
		public static async Task<Element> FindTemplatedParentAsync(Element element)
		{
			if (element.RealParent is Application)
				return null;

			var skipCount = 0;
			element = await GetRealParentAsync(element);
			while (!Application.IsApplicationOrNull(element))
			{
				var controlTemplated = element as IControlTemplated;
				if (controlTemplated?.ControlTemplate != null)
				{
					if (skipCount == 0)
						return element;
					skipCount--;
				}
				if (element is ContentPresenter)
					skipCount++;
				element = await GetRealParentAsync(element);
			}

			return null;
		}

		public static Task<Element> GetRealParentAsync(Element element)
		{
			Element parent = element.RealParent;
			if (parent is Application)
				return Task.FromResult<Element>(null);

			if (parent != null)
				return Task.FromResult(parent);

			var tcs = new TaskCompletionSource<Element>();
			EventHandler handler = null;
			handler = (sender, args) =>
			{
				tcs.TrySetResult(element.RealParent);
				element.ParentSet -= handler;
			};
			element.ParentSet += handler;

			return tcs.Task;
		}

		public static void OnContentChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var self = (IControlTemplated)bindable;
			var newElement = (Element)newValue;
			if (self.ControlTemplate == null)
			{
				for (var i = 0; i < self.InternalChildren.Count; i++)
				{
					self.InternalChildren.Remove(self.InternalChildren[i]);
				}

				if (newValue != null)
					self.InternalChildren.Add(newElement);
			}
			else
			{
				if (newElement != null)
				{
					BindableObject.SetInheritedBindingContext(newElement, bindable.BindingContext);
				}
			}
		}

		public static void OnControlTemplateChanged(BindableObject bindable, object oldValue, object newValue)
		{
			var self = (IControlTemplated)bindable;

			// First make sure any old ContentPresenters are no longer bound up. This MUST be
			// done before we attempt to make the new template.
			if (oldValue != null)
			{
				var queue = new Queue<Element>(16);
				queue.Enqueue((Element)self);

				while (queue.Count > 0)
				{
					ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildren;
					for (var i = 0; i < children.Count; i++)
					{
						Element child = children[i];
						var controlTemplated = child as IControlTemplated;

						var presenter = child as ContentPresenter;
						if (presenter != null)
							presenter.Clear();
						else if (controlTemplated == null || controlTemplated.ControlTemplate == null)
							queue.Enqueue(child);
					}
				}
			}

			// Now remove all remnants of any other children just to be sure
			for (var i = 0; i < self.InternalChildren.Count; i++)
			{
				self.InternalChildren.Remove(self.InternalChildren[i]);
			}

			ControlTemplate template = self.ControlTemplate;
			var content = template.CreateContent() as View;

			if (content == null)
			{
				throw new NotSupportedException("ControlTemplate must return a type derived from View.");
			}

			self.InternalChildren.Add(content);
		}
	}
}