Assert.AreEqual(label1.Text, stack2.StyleId);
Assert.AreEqual(label1.BindingContext, "foobar");
}
-
- [Test]
- public void RelativeSourceTemplatedParentBinding()
- {
- var cc1 = new CustomControl
- {
- CustomText = "RelativeSource Binding 1!",
- ControlTemplate = new ControlTemplate(typeof(MyCustomControlTemplate))
- };
- var cc2 = new CustomControl
- {
- CustomText = "RelativeSource Binding 2!",
- ControlTemplate = new ControlTemplate(typeof(MyCustomControlTemplate))
- };
-
- var realLabel = cc1.LogicalChildren[0].LogicalChildren[0] as Label;
- Assert.AreEqual(cc1.CustomText, realLabel.Text);
- Assert.AreEqual(realLabel.TemplatedParent, cc1);
-
- // Test reparenting
- int templatedParentChangeCount = 0;
- realLabel.PropertyChanged += (sender, e) =>
- {
- if (e.PropertyName == nameof(Element.TemplatedParent))
- templatedParentChangeCount++;
- };
- ((StackLayout)realLabel.Parent).Children.Remove(realLabel);
- Assert.IsNull(realLabel.TemplatedParent);
- Assert.IsNull(realLabel.Text);
- ((StackLayout)cc2.LogicalChildren[0]).Children.Add(realLabel);
- Assert.AreEqual(realLabel.TemplatedParent, cc2);
- Assert.AreEqual(cc2.CustomText, realLabel.Text);
- Assert.AreEqual(2, templatedParentChangeCount);
- }
}
public class CustomControl : ContentView
ThrowIfApplied();
_source = value;
if ((value as RelativeBindingSource)?.Mode == RelativeBindingSourceMode.TemplatedParent)
- this.AllowChaining = true;
+ AllowChaining = true;
}
}
}
}
- void ApplyRelativeSourceBinding(
+#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
+ async void ApplyRelativeSourceBinding(
BindableObject targetObject,
BindableProperty targetProperty)
+#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
{
if (!(targetObject is Element elem))
throw new InvalidOperationException();
if (!(Source is RelativeBindingSource relativeSource))
return;
- object resolvedSource = null;
-
+ object resolvedSource;
switch (relativeSource.Mode)
{
case RelativeBindingSourceMode.Self:
break;
case RelativeBindingSourceMode.TemplatedParent:
- _expression.SubscribeToTemplatedParentChanges(elem, targetProperty);
- resolvedSource = elem.TemplatedParent;
+ resolvedSource = await TemplateUtilities.FindTemplatedParentAsync(elem);
break;
case RelativeBindingSourceMode.FindAncestor:
readonly List<BindingExpressionPart> _parts = new List<BindingExpressionPart>();
- bool _trackingTemplatedParent;
BindableProperty _targetProperty;
WeakReference<object> _weakSource;
WeakReference<BindableObject> _weakTarget;
internal BindingExpression(BindingBase binding, string path)
{
- if (binding == null)
- throw new ArgumentNullException(nameof(binding));
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- Binding = binding;
- Path = path;
+ Binding = binding ?? throw new ArgumentNullException(nameof(binding));
+ Path = path ?? throw new ArgumentNullException(nameof(path));
ParsePath();
}
if (_weakSource == null || _weakTarget == null)
return;
- BindableObject target;
- if (!_weakTarget.TryGetTarget(out target))
+ if (!_weakTarget.TryGetTarget(out BindableObject target))
{
Unapply();
return;
}
- object source;
- if (_weakSource.TryGetTarget(out source) && _targetProperty != null)
+ if (_weakSource.TryGetTarget(out var source) && _targetProperty != null)
ApplyCore(source, target, _targetProperty, fromTarget);
}
{
_targetProperty = property;
- BindableObject prevTarget;
- if (_weakTarget != null && _weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, target))
+ if (_weakTarget != null && _weakTarget.TryGetTarget(out BindableObject prevTarget) && !ReferenceEquals(prevTarget, target))
throw new InvalidOperationException("Binding instances can not be reused");
- object previousSource;
- if (_weakSource != null && _weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, sourceObject))
+ if (_weakSource != null && _weakSource.TryGetTarget(out var previousSource) && !ReferenceEquals(previousSource, sourceObject))
throw new InvalidOperationException("Binding instances can not be reused");
_weakSource = new WeakReference<object>(sourceObject);
internal void Unapply()
{
- object sourceObject;
- if (_weakSource != null && _weakSource.TryGetTarget(out sourceObject))
+ if (_weakSource != null && _weakSource.TryGetTarget(out var sourceObject))
{
for (var i = 0; i < _parts.Count - 1; i++)
{
BindingExpressionPart part = _parts[i];
if (!part.IsSelf)
- {
part.TryGetValue(sourceObject, out sourceObject);
- }
part.Unsubscribe();
}
}
- if (_trackingTemplatedParent)
- {
- BindableObject target = null;
- if (_weakTarget?.TryGetTarget(out target) == true && target is Element elem)
- elem.TemplatedParentChanged -= OnTargetTemplatedParentChanged;
- }
-
_weakSource = null;
_weakTarget = null;
bool needsSetter = !needsGetter && ((mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource);
object current = sourceObject;
- object previous = null;
BindingExpressionPart part = null;
for (var i = 0; i < _parts.Count; i++)
if (part.NextPart != null && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
&& current is INotifyPropertyChanged inpc)
part.Subscribe(inpc);
-
- previous = current;
}
Debug.Assert(part != null, "There should always be at least the self part in the expression.");
}
}
#if !NETSTANDARD1_0
- TupleElementNamesAttribute tupleEltNames;
if ( property != null
&& part.NextPart != null
&& property.PropertyType.IsGenericType
|| property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,>)
|| property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,>)
|| property.PropertyType.GetGenericTypeDefinition() == typeof(ValueTuple<,,,,,,,>))
- && (tupleEltNames = property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) as TupleElementNamesAttribute) != null)
+ && property.GetCustomAttribute(typeof(TupleElementNamesAttribute)) is TupleElementNamesAttribute tupleEltNames)
{
//modify the nextPart to access the tuple item via the ITuple indexer
var nextPart = part.NextPart;
}
}
- static Type[] DecimalTypes = new[] { typeof(float), typeof(decimal), typeof(double) };
+ static readonly Type[] DecimalTypes = { typeof(float), typeof(decimal), typeof(double) };
internal static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
{
}
}
- internal void SubscribeToTemplatedParentChanges(Element target, BindableProperty targetProperty)
- {
- _targetProperty = targetProperty;
- target.TemplatedParentChanged += OnTargetTemplatedParentChanged;
- _trackingTemplatedParent = true;
- }
-
- void OnTargetTemplatedParentChanged(object sender, EventArgs e)
- {
- if (!(sender is Element elem) ||
- !(this.Binding is Binding binding))
- return;
- binding.Unapply();
- binding.Apply(null, elem, _targetProperty);
- }
-
// SubscribeToAncestryChanges, ClearAncestryChangeSubscriptions, FindAncestryIndex, and
// OnElementParentSet are used with RelativeSource ancestor-type bindings, to detect when
// there has been an ancestry change requiring re-applying the binding, and to minimize
{
public static readonly BindableProperty MenuProperty = BindableProperty.CreateAttached(nameof(Menu), typeof(Menu), typeof(Element), null);
- public static Menu GetMenu(BindableObject bindable)
- {
- return (Menu)bindable.GetValue(MenuProperty);
- }
-
- public static void SetMenu(BindableObject bindable, Menu menu)
- {
- bindable.SetValue(MenuProperty, menu);
- }
+ public static Menu GetMenu(BindableObject bindable) => (Menu)bindable.GetValue(MenuProperty);
+ public static void SetMenu(BindableObject bindable, Menu menu) => bindable.SetValue(MenuProperty, menu);
internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(new Element[0]);
public string ClassId
{
- get { return (string)GetValue(ClassIdProperty); }
- set { SetValue(ClassIdProperty, value); }
+ get => (string)GetValue(ClassIdProperty);
+ set => SetValue(ClassIdProperty, value);
}
public IList<Effect> Effects
[EditorBrowsable(EditorBrowsableState.Never)]
public Element RealParent { get; private set; }
- Dictionary<BindableProperty, string> DynamicResources
- {
- get { return _dynamicResources ?? (_dynamicResources = new Dictionary<BindableProperty, string>()); }
- }
+ Dictionary<BindableProperty, string> DynamicResources => _dynamicResources ?? (_dynamicResources = new Dictionary<BindableProperty, string>());
void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
{
OnParentSet();
OnPropertyChanged();
-
- RefreshTemplatedParent();
- }
- }
-
- internal event EventHandler TemplatedParentChanged;
-
- BindableObject _templatedParent;
- public BindableObject TemplatedParent
- {
- get => _templatedParent;
- private set
- {
- _templatedParent = value;
- TemplatedParentChanged?.Invoke(this, null);
- OnPropertyChanged();
- }
- }
-
- void RefreshTemplatedParent()
- {
- var templatedParent = this.IsTemplateRoot
- ? this.Parent
- : this.Parent?.TemplatedParent;
- if (ReferenceEquals(templatedParent, this.TemplatedParent))
- return;
-
- this.TemplatedParent = templatedParent;
- foreach (var element in this.LogicalChildren)
- {
- if (!element.IsTemplateRoot)
- element.RefreshTemplatedParent();
}
}
readonly SpanCollection _spans = new SpanCollection();
internal event NotifyCollectionChangedEventHandler SpansCollectionChanged;
- public FormattedString()
- {
- _spans.CollectionChanged += OnCollectionChanged;
- }
+ public FormattedString() => _spans.CollectionChanged += OnCollectionChanged;
protected override void OnBindingContextChanged()
{
SetInheritedBindingContext(Spans[i], BindingContext);
}
- public IList<Span> Spans
- {
- get { return _spans; }
- }
-
- public static explicit operator string(FormattedString formatted)
- {
- return formatted.ToString();
- }
+ public IList<Span> Spans => _spans;
- public static implicit operator FormattedString(string text)
- {
- return new FormattedString { Spans = { new Span { Text = text } } };
- }
+ public static explicit operator string(FormattedString formatted) => formatted.ToString();
- public override string ToString()
- {
- return string.Concat(Spans.Select(span => span.Text));
- }
+ public static implicit operator FormattedString(string text) => new FormattedString { Spans = { new Span { Text = text } } };
+
+ public override string ToString() => string.Concat(Spans.Select(span => span.Text));
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SpansCollectionChanged?.Invoke(sender, e);
}
- void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- OnPropertyChanged(nameof(Spans));
- }
+ void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e) => OnPropertyChanged(nameof(Spans));
- void OnItemPropertyChanging(object sender, PropertyChangingEventArgs e)
- {
- OnPropertyChanging(nameof(Spans));
- }
+ void OnItemPropertyChanging(object sender, PropertyChangingEventArgs e) => OnPropertyChanging(nameof(Spans));
class SpanCollection : ObservableCollection<Span>
{
- protected override void InsertItem(int index, Span item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
-
- base.InsertItem(index, item);
- }
-
- protected override void SetItem(int index, Span item)
- {
- if (item == null)
- throw new ArgumentNullException("item");
-
- base.SetItem(index, item);
- }
+ protected override void InsertItem(int index, Span item) => base.InsertItem(index, item ?? throw new ArgumentNullException(nameof(item)));
+ protected override void SetItem(int index, Span item) => base.SetItem(index, item ?? throw new ArgumentNullException(nameof(item)));
protected override void ClearItems()
{
- List<Span> removed = new List<Span>(this);
+ var removed = new List<Span>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage
+ xmlns="http://xamarin.com/schemas/2014/forms"
+ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+ xmlns:local="using:Xamarin.Forms.Xaml.UnitTests"
+ x:Class="Xamarin.Forms.Xaml.UnitTests.Gh7494">
+ <local:Gh7494Content Title="Foo">
+ <local:Gh7494Content.ControlTemplate>
+ <ControlTemplate>
+ <StackLayout>
+ <Label>
+ <Label.FormattedText>
+ <FormattedString>
+ <Span Text="{TemplateBinding Title}" />
+ </FormattedString>
+ </Label.FormattedText>
+ </Label>
+ <ContentPresenter/>
+ </StackLayout>
+ </ControlTemplate>
+ </local:Gh7494Content.ControlTemplate>
+ <Label Text="Content"/>
+ </local:Gh7494Content>
+</ContentPage>
--- /dev/null
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+ public partial class Gh7494 : ContentPage
+ {
+ public Gh7494()
+ {
+ InitializeComponent();
+ }
+ public Gh7494(bool useCompiledXaml)
+ {
+ //this stub will be replaced at compile time
+ }
+
+ [TestFixture]
+ class Tests
+ {
+ [SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
+ [TearDown] public void TearDown() => Device.PlatformServices = null;
+
+ [Test]
+ public void TemplateBindingInSpans([Values(false, true)]bool useCompiledXaml)
+ {
+ var layout = new Gh7494(useCompiledXaml);
+ var view = layout.Content as Gh7494Content;
+ var templatedLabel = ((StackLayout)view.Children[0]).Children[0] as Label;
+
+ Assert.That(templatedLabel.FormattedText.Spans[0].Text, Is.EqualTo(view.Title));
+ }
+ }
+ }
+
+ public class Gh7494Content : ContentView
+ {
+ public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Gh7494Content), default(string));
+
+ public string Title {
+ get => (string)GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+ }
+}