File: RelativeBindingSource.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Maui.Controls.Internals;
 
namespace Microsoft.Maui.Controls
{
	/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="Type[@FullName='Microsoft.Maui.Controls.RelativeBindingSource']/Docs/*" />
	public sealed class RelativeBindingSource
	{
		static RelativeBindingSource _self;
		static RelativeBindingSource _templatedParent;
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='.ctor']/Docs/*" />
		public RelativeBindingSource(
			RelativeBindingSourceMode mode,
			Type ancestorType = null,
			int ancestorLevel = 1)
		{
			if ((mode == RelativeBindingSourceMode.FindAncestor ||
				 mode == RelativeBindingSourceMode.FindAncestorBindingContext) &&
				ancestorType == null)
			{
				throw new InvalidOperationException(
					$"{nameof(RelativeBindingSourceMode.FindAncestor)} and " +
					$"{nameof(RelativeBindingSourceMode.FindAncestorBindingContext)} " +
					$"require non-null {nameof(AncestorType)}");
			}
 
			Mode = mode;
			AncestorType = ancestorType;
			AncestorLevel = ancestorLevel;
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='Mode']/Docs/*" />
		public RelativeBindingSourceMode Mode
		{
			get;
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='AncestorType']/Docs/*" />
		public Type AncestorType
		{
			get;
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='AncestorLevel']/Docs/*" />
		public int AncestorLevel
		{
			get;
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='Self']/Docs/*" />
		public static RelativeBindingSource Self
		{
			get
			{
				return _self ?? (_self = new RelativeBindingSource(RelativeBindingSourceMode.Self));
			}
		}
 
		/// <include file="../../docs/Microsoft.Maui.Controls/RelativeBindingSource.xml" path="//Member[@MemberName='TemplatedParent']/Docs/*" />
		public static RelativeBindingSource TemplatedParent
		{
			get
			{
				return _templatedParent ?? (_templatedParent = new RelativeBindingSource(RelativeBindingSourceMode.TemplatedParent));
			}
		}
 
#nullable enable
 
		internal async Task Apply(BindingExpression expression, Element relativeSourceTarget, BindableObject targetObject, BindableProperty targetProperty, SetterSpecificity specificity)
		{
			var bindingAdapter = new BindingExpressionAdapter(expression, targetObject, targetProperty, specificity);
			await Apply(bindingAdapter, relativeSourceTarget);
		}
 
		internal async Task Apply(TypedBindingBase binding, Element relativeSourceTarget, BindableObject targetObject, BindableProperty targetProperty, SetterSpecificity specificity)
		{
			var bindingAdapter = new TypedBindingAdapter(binding, targetObject, targetProperty, specificity);
			await Apply(bindingAdapter, relativeSourceTarget);
		}
 
		private async Task Apply(IBindingAdapter bindingAdapter, Element relativeSourceTarget)
		{
			object? resolvedSource = null;
			switch (Mode)
			{
				case RelativeBindingSourceMode.Self:
					resolvedSource = relativeSourceTarget;
					break;
 
				case RelativeBindingSourceMode.TemplatedParent:
					resolvedSource = await TemplateUtilities.FindTemplatedParentAsync(relativeSourceTarget);
					break;
 
				case RelativeBindingSourceMode.FindAncestor:
				case RelativeBindingSourceMode.FindAncestorBindingContext:
					ApplyAncestorTypeBinding(
						bindingAdapter,
						relativeSourceTarget,
						chain: new List<Element> { relativeSourceTarget },
						currentLevel: 0);
					return;
 
				default:
					throw new InvalidOperationException();
			}
 
			bindingAdapter.Apply(resolvedSource);
		}
 
		private void ApplyAncestorTypeBinding(
			IBindingAdapter bindingAdapter,
			Element currentElement,
			int currentLevel,
			List<Element> chain,
			object? lastMatchingBctx = null)
		{
			if (currentElement.RealParent is Application ||
				currentElement.RealParent == null)
			{
				// Couldn't find the desired ancestor type in the chain, but it may be added later, 
				// so apply with a null source for now.
				bindingAdapter.Apply(null);
				bindingAdapter.SubscribeToAncestryChanges(
					chain, includeBindingContext: Mode == RelativeBindingSourceMode.FindAncestorBindingContext, rootIsSource: false);
			}
			else if (currentElement.RealParent != null)
			{
				chain.Add(currentElement.RealParent);
				if (ElementFitsAncestorTypeAndLevel(currentElement.RealParent, ref currentLevel, ref lastMatchingBctx))
				{
					object? resolvedSource;
					if (Mode == RelativeBindingSourceMode.FindAncestor)
						resolvedSource = currentElement.RealParent;
					else
						resolvedSource = currentElement.RealParent?.BindingContext;
					bindingAdapter.Apply(resolvedSource);
					bindingAdapter.SubscribeToAncestryChanges(
						chain, includeBindingContext: Mode == RelativeBindingSourceMode.FindAncestorBindingContext, rootIsSource: true);
				}
				else
				{
					ApplyAncestorTypeBinding(bindingAdapter, currentElement.RealParent, currentLevel, chain, lastMatchingBctx);
				}
			}
			else
			{
				EventHandler? onElementParentSet = null;
				onElementParentSet = (sender, e) =>
				{
					currentElement.ParentSet -= onElementParentSet;
					ApplyAncestorTypeBinding(bindingAdapter, currentElement, currentLevel, chain, lastMatchingBctx);
				};
				currentElement.ParentSet += onElementParentSet;
			}
		}
 
		private bool ElementFitsAncestorTypeAndLevel(Element element, ref int level, ref object? lastPotentialBctx)
		{
			bool fitsElementType =
				Mode == RelativeBindingSourceMode.FindAncestor &&
				AncestorType!.IsAssignableFrom(element.GetType());
 
			bool fitsBindingContextType =
				element.BindingContext != null &&
				Mode == RelativeBindingSourceMode.FindAncestorBindingContext &&
				AncestorType!.IsAssignableFrom(element.BindingContext.GetType());
 
			if (!fitsElementType && !fitsBindingContextType)
				return false;
 
			if (fitsBindingContextType)
			{
				if (!object.ReferenceEquals(lastPotentialBctx, element.BindingContext))
				{
					lastPotentialBctx = element.BindingContext;
					level++;
				}
			}
			else
			{
				level++;
			}
 
			return level >= AncestorLevel;
		}
 
		private interface IBindingAdapter
		{
			void Apply(object? resolvedSource);
			void Unapply();
			void SubscribeToAncestryChanges(List<Element> chain, bool includeBindingContext, bool rootIsSource);
		}
 
		private struct BindingExpressionAdapter(
			BindingExpression expression,
			BindableObject target,
			BindableProperty property,
			SetterSpecificity specificity)
			: IBindingAdapter
		{
			public void Apply(object? resolvedSource)
				=> expression.Apply(resolvedSource, target, property, specificity);
 
			public void Unapply()
				=> expression.Unapply();
 
			public void SubscribeToAncestryChanges(List<Element> chain, bool includeBindingContext, bool rootIsSource)
				=> expression.SubscribeToAncestryChanges(chain, includeBindingContext, rootIsSource);
		}
 
		private struct TypedBindingAdapter(
			TypedBindingBase binding,
			BindableObject target,
			BindableProperty property,
			SetterSpecificity specificity)
			: IBindingAdapter
		{
			public void Apply(object? resolvedSource)
				=> binding.ApplyToResolvedSource(resolvedSource, target, property, false, specificity);
 
			public void Unapply()
				=> binding.Unapply();
 
			public void SubscribeToAncestryChanges(List<Element> chain, bool includeBindingContext, bool rootIsSource)
				=> binding.SubscribeToAncestryChanges(chain, includeBindingContext, rootIsSource);
		}
	}
}