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

694 строки
21 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms
{
public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
{
public static readonly BindableProperty BindingContextProperty =
BindableProperty.Create("BindingContext", typeof(object), typeof(BindableObject), default(object),
BindingMode.OneWay, null, BindingContextPropertyChanged, null, null, BindingContextPropertyBindingChanging);
readonly List<BindablePropertyContext> _properties = new List<BindablePropertyContext>(4);
bool _applying;
object _inheritedContext;
public object BindingContext
{
get { return _inheritedContext ?? GetValue(BindingContextProperty); }
set { SetValue(BindingContextProperty, value); }
}
void IDynamicResourceHandler.SetDynamicResource(BindableProperty property, string key)
{
SetDynamicResource(property, key, false);
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler BindingContextChanged;
internal void ClearValue(BindableProperty property, bool fromStyle)
{
ClearValue(property, fromStyle: fromStyle, checkAccess: true);
}
public void ClearValue(BindableProperty property)
{
ClearValue(property, fromStyle: false, checkAccess: true);
}
public void ClearValue(BindablePropertyKey propertyKey)
{
if (propertyKey == null)
throw new ArgumentNullException("propertyKey");
ClearValue(propertyKey.BindableProperty, fromStyle:false, checkAccess: false);
}
public bool IsSet(BindableProperty targetProperty)
{
if (targetProperty == null)
throw new ArgumentNullException(nameof(targetProperty));
var bpcontext = GetContext(targetProperty);
return bpcontext != null
&& (bpcontext.Attributes & BindableContextAttributes.IsDefaultValue) == 0;
}
public object GetValue(BindableProperty property)
{
if (property == null)
throw new ArgumentNullException("property");
BindablePropertyContext context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
if (context == null)
return property.DefaultValue;
return context.Value;
}
public event PropertyChangingEventHandler PropertyChanging;
public void RemoveBinding(BindableProperty property)
{
if (property == null)
throw new ArgumentNullException("property");
BindablePropertyContext context = GetContext(property);
if (context == null || context.Binding == null)
return;
RemoveBinding(property, context);
}
public void SetBinding(BindableProperty targetProperty, BindingBase binding)
{
SetBinding(targetProperty, binding, false);
}
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("propertyKey");
SetValue(propertyKey.BindableProperty, value, false, false);
}
[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);
}
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()
{
for (int i = 0, _propertiesCount = _properties.Count; i < _propertiesCount; i++) {
BindablePropertyContext context = _properties [i];
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;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public object[] GetValues(BindableProperty property0, BindableProperty property1)
{
var values = new object[2];
for (var i = 0; i < _properties.Count; i++)
{
BindablePropertyContext context = _properties[i];
if (ReferenceEquals(context.Property, property0))
{
values[0] = context.Value;
property0 = null;
}
else if (ReferenceEquals(context.Property, property1))
{
values[1] = context.Value;
property1 = null;
}
if (property0 == null && property1 == null)
return values;
}
if (!ReferenceEquals(property0, null))
values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
if (!ReferenceEquals(property1, null))
values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
return values;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public object[] GetValues(BindableProperty property0, BindableProperty property1, BindableProperty property2)
{
var values = new object[3];
for (var i = 0; i < _properties.Count; i++)
{
BindablePropertyContext context = _properties[i];
if (ReferenceEquals(context.Property, property0))
{
values[0] = context.Value;
property0 = null;
}
else if (ReferenceEquals(context.Property, property1))
{
values[1] = context.Value;
property1 = null;
}
else if (ReferenceEquals(context.Property, property2))
{
values[2] = context.Value;
property2 = null;
}
if (property0 == null && property1 == null && property2 == null)
return values;
}
if (!ReferenceEquals(property0, null))
values[0] = property0.DefaultValueCreator == null ? property0.DefaultValue : CreateAndAddContext(property0).Value;
if (!ReferenceEquals(property1, null))
values[1] = property1.DefaultValueCreator == null ? property1.DefaultValue : CreateAndAddContext(property1).Value;
if (!ReferenceEquals(property2, null))
values[2] = property2.DefaultValueCreator == null ? property2.DefaultValue : CreateAndAddContext(property2).Value;
return values;
}
[EditorBrowsable(EditorBrowsableState.Never)]
internal object[] GetValues(params BindableProperty[] properties)
{
var values = new object[properties.Length];
for (var i = 0; i < _properties.Count; i++) {
var context = _properties[i];
var index = properties.IndexOf(context.Property);
if (index < 0)
continue;
values[index] = context.Value;
}
for (var i = 0; i < values.Length; i++) {
if (!ReferenceEquals(values[i], null))
continue;
values[i] = properties[i].DefaultValueCreator == null ? properties[i].DefaultValue : CreateAndAddContext(properties[i]).Value;
}
return values;
}
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("property");
OnRemoveDynamicResource(property);
BindablePropertyContext context = GetOrCreateContext(property);
context.Attributes &= ~BindableContextAttributes.IsDynamicResource;
}
internal void SetBinding(BindableProperty targetProperty, BindingBase binding, bool fromStyle)
{
if (targetProperty == null)
throw new ArgumentNullException("targetProperty");
if (binding == null)
throw new ArgumentNullException("binding");
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;
targetProperty.BindingChanging?.Invoke(this, oldBinding, binding);
binding.Apply(BindingContext, this, targetProperty);
}
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;
}
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);
}
internal void SetValue(BindableProperty property, object value, bool fromStyle)
{
SetValue(property, value, fromStyle, true);
}
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("property");
if (checkAccess && property.IsReadOnly)
{
Debug.WriteLine("Can not set the BindableProperty \"{0}\" because it is readonly.", property.PropertyName);
return;
}
if (!converted && !property.TryConvert(ref value))
{
Log.Warning("SetValue", "Can not convert {0} to type '{1}'", value, property.ReturnType);
return;
}
if (property.ValidateValue != null && !property.ValidateValue(this, value))
throw new ArgumentException("Value was an invalid value for " + property.PropertyName, "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;
}
}
internal void ApplyBindings(bool skipBindingContext, bool fromBindingContextChanged)
{
var prop = _properties.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();
}
void ClearValue(BindableProperty property, bool fromStyle, bool checkAccess)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
if (checkAccess && property.IsReadOnly)
throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
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);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext CreateAndAddContext(BindableProperty property)
{
var context = new BindablePropertyContext { Property = property, Value = property.DefaultValueCreator != null ? property.DefaultValueCreator(this) : property.DefaultValue };
if (property.DefaultValueCreator == null)
context.Attributes = BindableContextAttributes.IsDefaultValue;
else
context.Attributes = BindableContextAttributes.IsDefaultValueCreated;
_properties.Add(context);
return context;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext GetContext(BindableProperty property)
{
List<BindablePropertyContext> properties = _properties;
for (var i = 0; i < properties.Count; i++)
{
BindablePropertyContext context = properties[i];
if (ReferenceEquals(context.Property, property))
return context;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
BindablePropertyContext GetOrCreateContext(BindableProperty property)
{
BindablePropertyContext context = GetContext(property);
if (context == null)
{
context = CreateAndAddContext(property);
}
return context;
}
void RemoveBinding(BindableProperty property, BindablePropertyContext context)
{
context.Binding.Unapply();
property.BindingChanging?.Invoke(this, context.Binding, null);
context.Binding = null;
}
void SetValue(BindableProperty property, object value, bool fromStyle, bool checkAccess)
{
if (property == null)
throw new ArgumentNullException("property");
if (checkAccess && property.IsReadOnly)
throw new InvalidOperationException(string.Format("The BindableProperty \"{0}\" is readonly.", property.PropertyName));
if (fromStyle && !CanBeSetFromStyle(property))
return;
SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearDynamicResource,
(fromStyle ? SetValuePrivateFlags.FromStyle : SetValuePrivateFlags.ManuallySet) | (checkAccess ? SetValuePrivateFlags.CheckAccess : 0));
}
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);
}
}
[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
}
}
}