maui-linux/Xamarin.Forms.Core/BindableObject.cs

580 строки
20 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms
{
public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
{
readonly Dictionary<BindableProperty, BindablePropertyContext> _properties = new Dictionary<BindableProperty, BindablePropertyContext>(4);
bool _applying;
object _inheritedContext;
public static readonly BindableProperty BindingContextProperty =
BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object),
BindingMode.OneWay, null, BindingContextPropertyChanged, null, null, BindingContextPropertyBindingChanging);
public object BindingContext
{
get => _inheritedContext ?? GetValue(BindingContextProperty);
set => SetValue(BindingContextProperty, value);
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
public event EventHandler BindingContextChanged;
public void ClearValue(BindableProperty property) => ClearValue(property, fromStyle: false, checkAccess: true);
internal void ClearValue(BindableProperty property, bool fromStyle) => ClearValue(property, fromStyle: fromStyle, checkAccess: true);
public void ClearValue(BindablePropertyKey propertyKey)
{
if (propertyKey == null)
throw new ArgumentNullException(nameof(propertyKey));
ClearValue(propertyKey.BindableProperty, fromStyle:false, checkAccess: false);
}
void ClearValue(BindableProperty property, bool fromStyle, bool checkAccess)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
if (checkAccess && property.IsReadOnly)
throw new InvalidOperationException($"The BindableProperty \"{property.PropertyName}\" is readonly.");
BindablePropertyContext bpcontext = GetContext(property);
if (bpcontext == null)
return;
if (fromStyle && !CanBeSetFromStyle(property))
return;
object original = bpcontext.Value;
object newValue = property.GetDefaultValue(this);
bool same = Equals(original, newValue);
if (!same)
{
property.PropertyChanging?.Invoke(this, original, newValue);
OnPropertyChanging(property.PropertyName);
}
bpcontext.Attributes &= ~BindableContextAttributes.IsManuallySet;
bpcontext.Value = newValue;
if (property.DefaultValueCreator == null)
bpcontext.Attributes |= BindableContextAttributes.IsDefaultValue;
else
bpcontext.Attributes |= BindableContextAttributes.IsDefaultValueCreated;
if (!same)
{
OnPropertyChanged(property.PropertyName);
property.PropertyChanged?.Invoke(this, original, newValue);
}
}
public object GetValue(BindableProperty property)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
var context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
return context == null ? property.DefaultValue : context.Value;
}
internal (bool IsSet, T Value)[] GetValues<T>(BindableProperty[] propArray)
{
Dictionary<BindableProperty, BindablePropertyContext> properties = _properties;
var resultArray = new (bool IsSet, T Value)[propArray.Length];
for (int i = 0; i < propArray.Length; i++)
{
if (properties.TryGetValue(propArray[i], out var context))
{
resultArray[i].IsSet = (context.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
resultArray[i].Value = (T)context.Value;
}
else
{
resultArray[i].IsSet = false;
resultArray[i].Value = default(T);
}
}
return resultArray;
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("GetValues is obsolete as of 4.0.0. Please use GetValue instead.")]
public object[] GetValues(BindableProperty property0, BindableProperty property1) => new object[] { GetValue(property0), GetValue(property1) };
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("GetValues is obsolete as of 4.0.0. Please use GetValue instead.")]
public object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2) => new object[] { GetValue(property0), GetValue(property1), GetValue(property2) };
public bool IsSet(BindableProperty targetProperty)
{
var bpcontext = GetContext(targetProperty ?? throw new ArgumentNullException(nameof(targetProperty)));
return bpcontext != null
&& (bpcontext.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
}
public void RemoveBinding(BindableProperty property)
{
BindablePropertyContext context = GetContext(property ?? throw new ArgumentNullException(nameof(property)));
if (context?.Binding != null)
RemoveBinding(property, context);
}
public void SetBinding(BindableProperty targetProperty, BindingBase binding) => SetBinding(targetProperty, binding, false);
internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
{
if (targetProperty == null)
throw new ArgumentNullException(nameof(targetProperty));
if (fromStyle && !CanBeSetFromStyle(targetProperty))
return;
var context = GetOrCreateContext(targetProperty);
if (fromStyle)
context.Attributes |= BindableContextAttributes.IsSetFromStyle;
else
context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
if (context.Binding != null)
context.Binding.Unapply();
BindingBase oldBinding = context.Binding;
context.Binding = binding ?? throw new ArgumentNullException(nameof(binding));
targetProperty.BindingChanging?.Invoke(this, oldBinding, binding);
binding.Apply(BindingContext, this, targetProperty);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetInheritedBindingContext(BindableObject bindable, object value)
{
BindablePropertyContext bpContext = bindable.GetContext(BindingContextProperty);
if (bpContext != null && ((bpContext.Attributes & BindableContextAttributes.IsManuallySet) != 0))
return;
object oldContext = bindable._inheritedContext;
if (ReferenceEquals(oldContext, value))
return;
if (bpContext != null && oldContext == null)
oldContext = bpContext.Value;
if (bpContext != null && bpContext.Binding != null)
{
bpContext.Binding.Context = value;
bindable._inheritedContext = null;
}
else
{
bindable._inheritedContext = value;
}
bindable.ApplyBindings(skipBindingContext:false, fromBindingContextChanged:true);
bindable.OnBindingContextChanged();
}
protected void ApplyBindings() => ApplyBindings(skipBindingContext: false, fromBindingContextChanged: false);
protected virtual void OnBindingContextChanged()
{
BindingContextChanged?.Invoke(this, EventArgs.Empty);
if (Shell.GetBackButtonBehavior(this) is BackButtonBehavior buttonBehavior)
SetInheritedBindingContext(buttonBehavior, BindingContext);
if (Shell.GetSearchHandler(this) is SearchHandler searchHandler)
SetInheritedBindingContext(searchHandler, BindingContext);
if (Shell.GetTitleView(this) is View titleView)
SetInheritedBindingContext(titleView, BindingContext);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null)
=> PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
protected void UnapplyBindings()
{
foreach (var context in _properties.Values) {
if (context.Binding == null)
continue;
context.Binding.Unapply();
}
}
internal bool GetIsBound(BindableProperty targetProperty)
{
if (targetProperty == null)
throw new ArgumentNullException("targetProperty");
BindablePropertyContext bpcontext = GetContext(targetProperty);
return bpcontext != null && bpcontext.Binding != null;
}
internal virtual void OnRemoveDynamicResource(BindableProperty property)
{
}
internal virtual void OnSetDynamicResource(BindableProperty property, string key)
{
}
internal void RemoveDynamicResource(BindableProperty property)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
OnRemoveDynamicResource(property);
BindablePropertyContext context = GetOrCreateContext(property);
context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
}
bool CanBeSetFromStyle(BindableProperty property)
{
var context = GetContext(property);
if (context == null)
return true;
if ((context.Attributes & BindableContextAttributes.IsSetFromStyle) == BindableContextAttributes.IsSetFromStyle)
return true;
if ((context.Attributes & BindableContextAttributes.IsDefaultValue) == BindableContextAttributes.IsDefaultValue)
return true;
if ((context.Attributes & BindableContextAttributes.IsDefaultValueCreated) == BindableContextAttributes.IsDefaultValueCreated)
return true;
return false;
}
void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key) => SetDynamicResource(property, key, false);
internal void SetDynamicResource(BindableProperty property, string key) => SetDynamicResource(property, key, false);
internal void SetDynamicResource(BindableProperty property, string key, bool fromStyle)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
if (fromStyle && !CanBeSetFromStyle(property))
return;
var context = GetOrCreateContext(property);
context.Attributes |= BindableContextAttributes.IsDynamicResource;
if (fromStyle)
context.Attributes |= BindableContextAttributes.IsSetFromStyle;
else
context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
OnSetDynamicResource(property, key);
}
public void SetValue(BindableProperty property, object value) => SetValue(property, value, false, true);
public void SetValue(BindablePropertyKey propertyKey, object value)
{
if (propertyKey == null)
throw new ArgumentNullException(nameof(propertyKey));
SetValue(propertyKey.BindableProperty, value, false, false);
}
internal void SetValue(BindableProperty property, object value, bool fromStyle) => SetValue(property, value, fromStyle, true);
void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
if (checkAccess && property.IsReadOnly)
throw new InvalidOperationException($"The BindableProperty \"{property.PropertyName}\" is readonly.");
if (fromStyle && !CanBeSetFromStyle(property))
return;
SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
(fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0));
}
internal void SetValueCore(BindablePropertyKey propertyKey, object value, SetValueFlags attributes = SetValueFlags.None)
=> SetValueCore(propertyKey.BindableProperty, value, attributes, SetValuePrivateFlags.None);
[EditorBrowsable(EditorBrowsableState.Never)]
public void SetValueCore(BindableProperty property, object value, SetValueFlags attributes = SetValueFlags.None)
=> SetValueCore(property, value, attributes, SetValuePrivateFlags.Default);
internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
{
bool checkAccess = (privateAttributes & SetValuePrivateFlags.CheckAccess) != 0;
bool manuallySet = (privateAttributes & SetValuePrivateFlags.ManuallySet) != 0;
bool silent = (privateAttributes & SetValuePrivateFlags.Silent) != 0;
bool fromStyle = (privateAttributes & SetValuePrivateFlags.FromStyle) != 0;
bool converted = (privateAttributes & SetValuePrivateFlags.Converted) != 0;
if (property == null)
throw new ArgumentNullException(nameof(property));
if (checkAccess && property.IsReadOnly)
{
Log.Warning("BindableObject", $"Can not set the BindableProperty \"{property.PropertyName}\" because it is readonly.");
return;
}
if (!converted && !property.TryConvert(ref value))
{
Log.Warning("SetValue", $"Can not convert {value} to type '{property.ReturnType}'");
return;
}
if (property.ValidateValue != null && !property.ValidateValue(this, value))
throw new ArgumentException($"Value is an invalid value for {property.PropertyName}", nameof(value));
if (property.CoerceValue != null)
value = property.CoerceValue(this, value);
BindablePropertyContext context = GetOrCreateContext(property);
if (manuallySet) {
context.Attributes |= BindableContextAttributes.IsManuallySet;
context.Attributes &= ~BindableContextAttributes.IsSetFromStyle;
} else
context.Attributes &= ~BindableContextAttributes.IsManuallySet;
if (fromStyle)
context.Attributes |= BindableContextAttributes.IsSetFromStyle;
// else omitted on purpose
bool currentlyApplying = _applying;
if ((context.Attributes & BindableContextAttributes.IsBeingSet) != 0)
{
Queue<SetValueArgs> delayQueue = context.DelayedSetters;
if (delayQueue == null)
context.DelayedSetters = delayQueue = new Queue<SetValueArgs>();
delayQueue.Enqueue(new SetValueArgs(property, context, value, currentlyApplying, attributes));
}
else
{
context.Attributes |= BindableContextAttributes.IsBeingSet;
SetValueActual(property, context, value, currentlyApplying, attributes, silent);
Queue<SetValueArgs> delayQueue = context.DelayedSetters;
if (delayQueue != null)
{
while (delayQueue.Count > 0)
{
SetValueArgs s = delayQueue.Dequeue();
SetValueActual(s.Property, s.Context, s.Value, s.CurrentlyApplying, s.Attributes);
}
context.DelayedSetters = null;
}
context.Attributes &= ~BindableContextAttributes.IsBeingSet;
}
}
void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
{
object original = context.Value;
bool raiseOnEqual = (attributes & SetValueFlags.RaiseOnEqual) != 0;
bool clearDynamicResources = (attributes & SetValueFlags.ClearDynamicResource) != 0;
bool clearOneWayBindings = (attributes & SetValueFlags.ClearOneWayBindings) != 0;
bool clearTwoWayBindings = (attributes & SetValueFlags.ClearTwoWayBindings) != 0;
bool same = ReferenceEquals(context.Property, BindingContextProperty) ? ReferenceEquals(value, original) : Equals(value, original);
if (!silent && (!same || raiseOnEqual))
{
property.PropertyChanging?.Invoke(this, original, value);
OnPropertyChanging(property.PropertyName);
}
if (!same || raiseOnEqual)
{
context.Value = value;
}
context.Attributes &= ~BindableContextAttributes.IsDefaultValue;
context.Attributes &= ~BindableContextAttributes.IsDefaultValueCreated;
if ((context.Attributes & BindableContextAttributes.IsDynamicResource) != 0 && clearDynamicResources)
RemoveDynamicResource(property);
BindingBase binding = context.Binding;
if (binding != null)
{
if (clearOneWayBindings && binding.GetRealizedMode(property) == BindingMode.OneWay || clearTwoWayBindings && binding.GetRealizedMode(property) == BindingMode.TwoWay)
{
RemoveBinding(property, context);
binding = null;
}
}
if (!silent && (!same || raiseOnEqual))
{
if (binding != null && !currentlyApplying)
{
_applying = true;
binding.Apply(true);
_applying = false;
}
OnPropertyChanged(property.PropertyName);
property.PropertyChanged?.Invoke(this, original, value);
}
}
internal void ApplyBindings(bool skipBindingContext, bool fromBindingContextChanged)
{
var prop = _properties.Values.ToArray();
for (int i = 0, propLength = prop.Length; i < propLength; i++) {
BindablePropertyContext context = prop [i];
BindingBase binding = context.Binding;
if (binding == null)
continue;
if (skipBindingContext && ReferenceEquals(context.Property, BindingContextProperty))
continue;
binding.Unapply(fromBindingContextChanged: fromBindingContextChanged);
binding.Apply(BindingContext, this, context.Property, fromBindingContextChanged: fromBindingContextChanged);
}
}
static void BindingContextPropertyBindingChanging(BindableObject bindable, BindingBase oldBindingBase, BindingBase newBindingBase)
{
object context = bindable._inheritedContext;
var oldBinding = oldBindingBase as Binding;
var newBinding = newBindingBase as Binding;
if (context == null && oldBinding != null)
context = oldBinding.Context;
if (context != null && newBinding != null)
newBinding.Context = context;
}
static void BindingContextPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
bindable._inheritedContext = null;
bindable.ApplyBindings(skipBindingContext: true, fromBindingContextChanged:true);
bindable.OnBindingContextChanged();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext CreateAndAddContext(BindableProperty property)
{
var defaultValueCreator = property.DefaultValueCreator;
var context = new BindablePropertyContext { Property = property, Value = defaultValueCreator != null ? defaultValueCreator(this) : property.DefaultValue };
if (defaultValueCreator == null)
context.Attributes = BindableContextAttributes.IsDefaultValue;
else
context.Attributes = BindableContextAttributes.IsDefaultValueCreated;
_properties.Add(property, context);
return context;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext GetContext(BindableProperty property) => _properties.TryGetValue(property, out var result) ? result : null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext GetOrCreateContext(BindableProperty property) => GetContext(property) ?? CreateAndAddContext(property);
void RemoveBinding(BindableProperty property, BindablePropertyContext context)
{
context.Binding.Unapply();
property.BindingChanging?.Invoke(this, context.Binding, null);
context.Binding = null;
}
[Flags]
enum BindableContextAttributes
{
IsManuallySet = 1 << 0,
IsBeingSet = 1 << 1,
IsDynamicResource = 1 << 2,
IsSetFromStyle = 1 << 3,
IsDefaultValue = 1 << 4,
IsDefaultValueCreated = 1 << 5,
}
class BindablePropertyContext
{
public BindableContextAttributes Attributes;
public BindingBase Binding;
public Queue<SetValueArgs> DelayedSetters;
public BindableProperty Property;
public object Value;
}
[Flags]
internal enum SetValuePrivateFlags
{
None = 0,
CheckAccess = 1 << 0,
Silent = 1 << 1,
ManuallySet = 1 << 2,
FromStyle = 1 << 3,
Converted = 1 << 4,
Default = CheckAccess
}
class SetValueArgs
{
public readonly SetValueFlags Attributes;
public readonly BindablePropertyContext Context;
public readonly bool CurrentlyApplying;
public readonly BindableProperty Property;
public readonly object Value;
public SetValueArgs(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes)
{
Property = property;
Context = context;
Value = value;
CurrentlyApplying = currentlyApplying;
Attributes = attributes;
}
}
}
namespace Internals
{
[Flags]
[EditorBrowsable(EditorBrowsableState.Never)]
public enum SetValueFlags
{
None = 0,
ClearOneWayBindings = 1 << 0,
ClearTwoWayBindings = 1 << 1,
ClearDynamicResource = 1 << 2,
RaiseOnEqual = 1 << 3
}
}
}