|
@@ -4,11 +4,10 @@
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.ComponentModel;
|
|
|
-using System.Linq;
|
|
|
-using System.Reactive.Linq;
|
|
|
using Avalonia.Data;
|
|
|
using Avalonia.Diagnostics;
|
|
|
using Avalonia.Logging;
|
|
|
+using Avalonia.PropertyStore;
|
|
|
using Avalonia.Threading;
|
|
|
using Avalonia.Utilities;
|
|
|
|
|
@@ -20,13 +19,13 @@ namespace Avalonia
|
|
|
/// <remarks>
|
|
|
/// This class is analogous to DependencyObject in WPF.
|
|
|
/// </remarks>
|
|
|
- public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
|
|
|
+ public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink
|
|
|
{
|
|
|
private IAvaloniaObject _inheritanceParent;
|
|
|
- private List<DirectBindingSubscription> _directBindings;
|
|
|
+ private List<IDisposable> _directBindings;
|
|
|
private PropertyChangedEventHandler _inpcChanged;
|
|
|
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
|
|
|
- private EventHandler<AvaloniaPropertyChangedEventArgs> _inheritablePropertyChanged;
|
|
|
+ private List<IAvaloniaObject> _inheritanceChildren;
|
|
|
private ValueStore _values;
|
|
|
private ValueStore Values => _values ?? (_values = new ValueStore(this));
|
|
|
|
|
@@ -57,15 +56,6 @@ namespace Avalonia
|
|
|
remove { _inpcChanged -= value; }
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Raised when an inheritable <see cref="AvaloniaProperty"/> value changes on this object.
|
|
|
- /// </summary>
|
|
|
- event EventHandler<AvaloniaPropertyChangedEventArgs> IAvaloniaObject.InheritablePropertyChanged
|
|
|
- {
|
|
|
- add { _inheritablePropertyChanged += value; }
|
|
|
- remove { _inheritablePropertyChanged -= value; }
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Gets or sets the parent object that inherited <see cref="AvaloniaProperty"/> values
|
|
|
/// are inherited from.
|
|
@@ -83,47 +73,27 @@ namespace Avalonia
|
|
|
set
|
|
|
{
|
|
|
VerifyAccess();
|
|
|
+
|
|
|
if (_inheritanceParent != value)
|
|
|
{
|
|
|
- if (_inheritanceParent != null)
|
|
|
- {
|
|
|
- _inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
|
|
|
- }
|
|
|
+ var oldParent = _inheritanceParent;
|
|
|
+ var valuestore = _values;
|
|
|
|
|
|
- var oldInheritanceParent = _inheritanceParent;
|
|
|
+ _inheritanceParent?.RemoveInheritanceChild(this);
|
|
|
_inheritanceParent = value;
|
|
|
- var valuestore = _values;
|
|
|
|
|
|
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()))
|
|
|
{
|
|
|
- if (valuestore != null && valuestore.GetValue(property) != AvaloniaProperty.UnsetValue)
|
|
|
+ if (valuestore?.IsSet(property) == true)
|
|
|
{
|
|
|
- // if local value set there can be no change
|
|
|
+ // If local value set there can be no change.
|
|
|
continue;
|
|
|
}
|
|
|
- // get the value as it would have been with the previous InheritanceParent
|
|
|
- object oldValue;
|
|
|
- if (oldInheritanceParent is AvaloniaObject aobj)
|
|
|
- {
|
|
|
- oldValue = aobj.GetValueOrDefaultUnchecked(property);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- oldValue = ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
|
|
|
- }
|
|
|
-
|
|
|
- object newValue = GetDefaultValue(property);
|
|
|
|
|
|
- if (!Equals(oldValue, newValue))
|
|
|
- {
|
|
|
- RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
|
|
|
- }
|
|
|
+ property.RouteInheritanceParentChanged(this, oldParent);
|
|
|
}
|
|
|
|
|
|
- if (_inheritanceParent != null)
|
|
|
- {
|
|
|
- _inheritanceParent.InheritablePropertyChanged += ParentPropertyChanged;
|
|
|
- }
|
|
|
+ _inheritanceParent?.AddInheritanceChild(this);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -167,9 +137,31 @@ namespace Avalonia
|
|
|
public void ClearValue(AvaloniaProperty property)
|
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(property != null);
|
|
|
+ property.RouteClearValue(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Clears a <see cref="AvaloniaProperty"/>'s local value.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ public void ClearValue<T>(AvaloniaProperty<T> property)
|
|
|
+ {
|
|
|
VerifyAccess();
|
|
|
|
|
|
- SetValue(property, AvaloniaProperty.UnsetValue);
|
|
|
+ switch (property)
|
|
|
+ {
|
|
|
+ case StyledPropertyBase<T> styled:
|
|
|
+ _values.ClearLocalValue(styled);
|
|
|
+ break;
|
|
|
+ case DirectPropertyBase<T> direct:
|
|
|
+ var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, direct);
|
|
|
+ p.InvokeSetter(this, p.GetUnsetValue(GetType()));
|
|
|
+ break;
|
|
|
+ case null:
|
|
|
+ throw new ArgumentNullException(nameof(property));
|
|
|
+ default:
|
|
|
+ throw new NotSupportedException("Unsupported AvaloniaProperty type.");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -210,21 +202,38 @@ namespace Avalonia
|
|
|
/// <returns>The value.</returns>
|
|
|
public object GetValue(AvaloniaProperty property)
|
|
|
{
|
|
|
- if (property is null)
|
|
|
+ return property.RouteGetValue(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets a <see cref="AvaloniaProperty"/> value.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <returns>The value.</returns>
|
|
|
+ public T GetValue<T>(AvaloniaProperty<T> property)
|
|
|
+ {
|
|
|
+ return property switch
|
|
|
{
|
|
|
- throw new ArgumentNullException(nameof(property));
|
|
|
- }
|
|
|
+ StyledPropertyBase<T> styled => GetValue(styled),
|
|
|
+ DirectPropertyBase<T> direct => GetValue(direct),
|
|
|
+ null => throw new ArgumentNullException(nameof(property)),
|
|
|
+ _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
|
|
|
+ };
|
|
|
+ }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Gets a <see cref="AvaloniaProperty"/> value.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <returns>The value.</returns>
|
|
|
+ public T GetValue<T>(StyledPropertyBase<T> property)
|
|
|
+ {
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
VerifyAccess();
|
|
|
|
|
|
- if (property.IsDirect)
|
|
|
- {
|
|
|
- return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return GetValueOrDefaultUnchecked(property);
|
|
|
- }
|
|
|
+ return GetValueOrInheritedOrDefault(property);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -233,14 +242,13 @@ namespace Avalonia
|
|
|
/// <typeparam name="T">The type of the property.</typeparam>
|
|
|
/// <param name="property">The property.</param>
|
|
|
/// <returns>The value.</returns>
|
|
|
- public T GetValue<T>(AvaloniaProperty<T> property)
|
|
|
+ public T GetValue<T>(DirectPropertyBase<T> property)
|
|
|
{
|
|
|
- if (property is null)
|
|
|
- {
|
|
|
- throw new ArgumentNullException(nameof(property));
|
|
|
- }
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
+ VerifyAccess();
|
|
|
|
|
|
- return (T)GetValue((AvaloniaProperty)property);
|
|
|
+ var registered = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
|
|
|
+ return registered.InvokeGetter(this);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -284,16 +292,33 @@ namespace Avalonia
|
|
|
object value,
|
|
|
BindingPriority priority = BindingPriority.LocalValue)
|
|
|
{
|
|
|
- Contract.Requires<ArgumentNullException>(property != null);
|
|
|
- VerifyAccess();
|
|
|
+ property.RouteSetValue(this, value, priority);
|
|
|
+ }
|
|
|
|
|
|
- if (property.IsDirect)
|
|
|
- {
|
|
|
- SetDirectValue(property, value);
|
|
|
- }
|
|
|
- else
|
|
|
+ /// <summary>
|
|
|
+ /// Sets a <see cref="AvaloniaProperty"/> value.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <param name="value">The value.</param>
|
|
|
+ /// <param name="priority">The priority of the value.</param>
|
|
|
+ public void SetValue<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ T value,
|
|
|
+ BindingPriority priority = BindingPriority.LocalValue)
|
|
|
+ {
|
|
|
+ switch (property)
|
|
|
{
|
|
|
- SetStyledValue(property, value, priority);
|
|
|
+ case StyledPropertyBase<T> styled:
|
|
|
+ SetValue(styled, value, priority);
|
|
|
+ break;
|
|
|
+ case DirectPropertyBase<T> direct:
|
|
|
+ SetValue(direct, value);
|
|
|
+ break;
|
|
|
+ case null:
|
|
|
+ throw new ArgumentNullException(nameof(property));
|
|
|
+ default:
|
|
|
+ throw new NotSupportedException("Unsupported AvaloniaProperty type.");
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -305,13 +330,46 @@ namespace Avalonia
|
|
|
/// <param name="value">The value.</param>
|
|
|
/// <param name="priority">The priority of the value.</param>
|
|
|
public void SetValue<T>(
|
|
|
- AvaloniaProperty<T> property,
|
|
|
+ StyledPropertyBase<T> property,
|
|
|
T value,
|
|
|
BindingPriority priority = BindingPriority.LocalValue)
|
|
|
{
|
|
|
- Contract.Requires<ArgumentNullException>(property != null);
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
+ VerifyAccess();
|
|
|
|
|
|
- SetValue((AvaloniaProperty)property, value, priority);
|
|
|
+ LogPropertySet(property, value, priority);
|
|
|
+
|
|
|
+ if (value is UnsetValueType)
|
|
|
+ {
|
|
|
+ if (priority == BindingPriority.LocalValue)
|
|
|
+ {
|
|
|
+ Values.ClearLocalValue(property);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new NotSupportedException(
|
|
|
+ "Canot set property to Unset at non-local value priority.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (!(value is DoNothingType))
|
|
|
+ {
|
|
|
+ Values.SetValue(property, value, priority);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Sets a <see cref="AvaloniaProperty"/> value.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <param name="value">The value.</param>
|
|
|
+ public void SetValue<T>(DirectPropertyBase<T> property, T value)
|
|
|
+ {
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
+ VerifyAccess();
|
|
|
+
|
|
|
+ LogPropertySet(property, value, BindingPriority.LocalValue);
|
|
|
+ SetDirectValueUnchecked(property, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -325,47 +383,34 @@ namespace Avalonia
|
|
|
/// </returns>
|
|
|
public IDisposable Bind(
|
|
|
AvaloniaProperty property,
|
|
|
- IObservable<object> source,
|
|
|
+ IObservable<BindingValue<object>> source,
|
|
|
BindingPriority priority = BindingPriority.LocalValue)
|
|
|
{
|
|
|
- Contract.Requires<ArgumentNullException>(property != null);
|
|
|
- Contract.Requires<ArgumentNullException>(source != null);
|
|
|
-
|
|
|
- VerifyAccess();
|
|
|
-
|
|
|
- if (property.IsDirect)
|
|
|
- {
|
|
|
- if (property.IsReadOnly)
|
|
|
- {
|
|
|
- throw new ArgumentException($"The property {property.Name} is readonly.");
|
|
|
- }
|
|
|
-
|
|
|
- Logger.TryGet(LogEventLevel.Verbose)?.Log(
|
|
|
- LogArea.Property,
|
|
|
- this,
|
|
|
- "Bound {Property} to {Binding} with priority LocalValue",
|
|
|
- property,
|
|
|
- GetDescription(source));
|
|
|
-
|
|
|
- if (_directBindings == null)
|
|
|
- {
|
|
|
- _directBindings = new List<DirectBindingSubscription>();
|
|
|
- }
|
|
|
+ return property.RouteBind(this, source, priority);
|
|
|
+ }
|
|
|
|
|
|
- return new DirectBindingSubscription(this, property, source);
|
|
|
- }
|
|
|
- else
|
|
|
+ /// <summary>
|
|
|
+ /// Binds a <see cref="AvaloniaProperty"/> to an observable.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <param name="source">The observable.</param>
|
|
|
+ /// <param name="priority">The priority of the binding.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// A disposable which can be used to terminate the binding.
|
|
|
+ /// </returns>
|
|
|
+ public IDisposable Bind<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ IObservable<BindingValue<T>> source,
|
|
|
+ BindingPriority priority = BindingPriority.LocalValue)
|
|
|
+ {
|
|
|
+ return property switch
|
|
|
{
|
|
|
- Logger.TryGet(LogEventLevel.Verbose)?.Log(
|
|
|
- LogArea.Property,
|
|
|
- this,
|
|
|
- "Bound {Property} to {Binding} with priority {Priority}",
|
|
|
- property,
|
|
|
- GetDescription(source),
|
|
|
- priority);
|
|
|
-
|
|
|
- return Values.AddBinding(property, source, priority);
|
|
|
- }
|
|
|
+ StyledPropertyBase<T> styled => Bind(styled, source, priority),
|
|
|
+ DirectPropertyBase<T> direct => Bind(direct, source),
|
|
|
+ null => throw new ArgumentNullException(nameof(property)),
|
|
|
+ _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -379,37 +424,96 @@ namespace Avalonia
|
|
|
/// A disposable which can be used to terminate the binding.
|
|
|
/// </returns>
|
|
|
public IDisposable Bind<T>(
|
|
|
- AvaloniaProperty<T> property,
|
|
|
- IObservable<T> source,
|
|
|
+ StyledPropertyBase<T> property,
|
|
|
+ IObservable<BindingValue<T>> source,
|
|
|
BindingPriority priority = BindingPriority.LocalValue)
|
|
|
{
|
|
|
- Contract.Requires<ArgumentNullException>(property != null);
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
+ VerifyAccess();
|
|
|
|
|
|
- return Bind(property, source.Select(x => (object)x), priority);
|
|
|
+ return Values.AddBinding(property, source, priority);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Forces the specified property to be revalidated.
|
|
|
+ /// Binds a <see cref="AvaloniaProperty"/> to an observable.
|
|
|
/// </summary>
|
|
|
+ /// <typeparam name="T">The type of the property.</typeparam>
|
|
|
/// <param name="property">The property.</param>
|
|
|
- public void Revalidate(AvaloniaProperty property)
|
|
|
+ /// <param name="source">The observable.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// A disposable which can be used to terminate the binding.
|
|
|
+ /// </returns>
|
|
|
+ public IDisposable Bind<T>(
|
|
|
+ DirectPropertyBase<T> property,
|
|
|
+ IObservable<BindingValue<T>> source)
|
|
|
{
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
+ source = source ?? throw new ArgumentNullException(nameof(source));
|
|
|
VerifyAccess();
|
|
|
- _values?.Revalidate(property);
|
|
|
+
|
|
|
+ property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
|
|
|
+
|
|
|
+ if (property.IsReadOnly)
|
|
|
+ {
|
|
|
+ throw new ArgumentException($"The property {property.Name} is readonly.");
|
|
|
+ }
|
|
|
+
|
|
|
+ Logger.TryGet(LogEventLevel.Verbose)?.Log(
|
|
|
+ LogArea.Property,
|
|
|
+ this,
|
|
|
+ "Bound {Property} to {Binding} with priority LocalValue",
|
|
|
+ property,
|
|
|
+ GetDescription(source));
|
|
|
+
|
|
|
+ _directBindings ??= new List<IDisposable>();
|
|
|
+
|
|
|
+ return new DirectBindingSubscription<T>(this, property, source);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ void IAvaloniaObject.AddInheritanceChild(IAvaloniaObject child)
|
|
|
+ {
|
|
|
+ _inheritanceChildren ??= new List<IAvaloniaObject>();
|
|
|
+ _inheritanceChildren.Add(child);
|
|
|
}
|
|
|
|
|
|
- internal void PriorityValueChanged(AvaloniaProperty property, int priority, object oldValue, object newValue)
|
|
|
+ /// <inheritdoc/>
|
|
|
+ void IAvaloniaObject.RemoveInheritanceChild(IAvaloniaObject child)
|
|
|
{
|
|
|
- oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
|
|
|
- GetDefaultValue(property) :
|
|
|
- oldValue;
|
|
|
- newValue = (newValue == AvaloniaProperty.UnsetValue) ?
|
|
|
- GetDefaultValue(property) :
|
|
|
- newValue;
|
|
|
+ _inheritanceChildren?.Remove(child);
|
|
|
+ }
|
|
|
|
|
|
- if (!Equals(oldValue, newValue))
|
|
|
+ void IAvaloniaObject.InheritedPropertyChanged<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ Optional<T> oldValue,
|
|
|
+ Optional<T> newValue)
|
|
|
+ {
|
|
|
+ if (property.Inherits && !IsSet(property))
|
|
|
{
|
|
|
- RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
|
|
|
+ RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc/>
|
|
|
+ Delegate[] IAvaloniaObjectDebug.GetPropertyChangedSubscribers()
|
|
|
+ {
|
|
|
+ return _propertyChanged?.GetInvocationList();
|
|
|
+ }
|
|
|
+
|
|
|
+ void IValueSink.ValueChanged<T>(
|
|
|
+ StyledPropertyBase<T> property,
|
|
|
+ BindingPriority priority,
|
|
|
+ Optional<T> oldValue,
|
|
|
+ BindingValue<T> newValue)
|
|
|
+ {
|
|
|
+ oldValue = oldValue.HasValue ? oldValue : GetInheritedOrDefault(property);
|
|
|
+ newValue = newValue.HasValue ? newValue : newValue.WithValue(GetInheritedOrDefault(property));
|
|
|
+
|
|
|
+ LogIfError(property, newValue);
|
|
|
+
|
|
|
+ if (!EqualityComparer<T>.Default.Equals(oldValue.Value, newValue.Value))
|
|
|
+ {
|
|
|
+ RaisePropertyChanged(property, oldValue, newValue, priority);
|
|
|
|
|
|
Logger.TryGet(LogEventLevel.Verbose)?.Log(
|
|
|
LogArea.Property,
|
|
@@ -421,39 +525,32 @@ namespace Avalonia
|
|
|
(BindingPriority)priority);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
|
|
|
- {
|
|
|
- LogIfError(property, notification);
|
|
|
- UpdateDataValidation(property, notification);
|
|
|
- }
|
|
|
|
|
|
- /// <inheritdoc/>
|
|
|
- Delegate[] IAvaloniaObjectDebug.GetPropertyChangedSubscribers()
|
|
|
- {
|
|
|
- return _propertyChanged?.GetInvocationList();
|
|
|
- }
|
|
|
+ void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) { }
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Gets all priority values set on the object.
|
|
|
+ /// Called for each inherited property when the <see cref="InheritanceParent"/> changes.
|
|
|
/// </summary>
|
|
|
- /// <returns>A collection of property/value tuples.</returns>
|
|
|
- internal IDictionary<AvaloniaProperty, object> GetSetValues() => Values?.GetSetValues();
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Forces revalidation of properties when a property value changes.
|
|
|
- /// </summary>
|
|
|
- /// <param name="property">The property to that affects validation.</param>
|
|
|
- /// <param name="affected">The affected properties.</param>
|
|
|
- protected static void AffectsValidation(AvaloniaProperty property, params AvaloniaProperty[] affected)
|
|
|
+ /// <typeparam name="T">The type of the property value.</typeparam>
|
|
|
+ /// <param name="property">The property.</param>
|
|
|
+ /// <param name="oldParent">The old inheritance parent.</param>
|
|
|
+ internal void InheritanceParentChanged<T>(
|
|
|
+ StyledPropertyBase<T> property,
|
|
|
+ IAvaloniaObject oldParent)
|
|
|
{
|
|
|
- property.Changed.Subscribe(e =>
|
|
|
+ var oldValue = oldParent switch
|
|
|
{
|
|
|
- foreach (var p in affected)
|
|
|
- {
|
|
|
- e.Sender.Revalidate(p);
|
|
|
- }
|
|
|
- });
|
|
|
+ AvaloniaObject o => o.GetValueOrInheritedOrDefault(property),
|
|
|
+ null => property.GetDefaultValue(GetType()),
|
|
|
+ _ => oldParent.GetValue(property)
|
|
|
+ };
|
|
|
+
|
|
|
+ var newValue = GetInheritedOrDefault(property);
|
|
|
+
|
|
|
+ if (!EqualityComparer<T>.Default.Equals(oldValue, newValue))
|
|
|
+ {
|
|
|
+ RaisePropertyChanged(property, oldValue, newValue);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -477,18 +574,25 @@ namespace Avalonia
|
|
|
/// enabled.
|
|
|
/// </summary>
|
|
|
/// <param name="property">The property.</param>
|
|
|
- /// <param name="status">The new validation status.</param>
|
|
|
- protected virtual void UpdateDataValidation(
|
|
|
- AvaloniaProperty property,
|
|
|
- BindingNotification status)
|
|
|
+ /// <param name="value">The new binding value for the property.</param>
|
|
|
+ protected virtual void UpdateDataValidation<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ BindingValue<T> value)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Called when a avalonia property changes on the object.
|
|
|
/// </summary>
|
|
|
- /// <param name="e">The event arguments.</param>
|
|
|
- protected virtual void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ /// <param name="property">The property whose value has changed.</param>
|
|
|
+ /// <param name="oldValue">The old value of the property.</param>
|
|
|
+ /// <param name="newValue">The new value of the property.</param>
|
|
|
+ /// <param name="priority">The priority of the new value.</param>
|
|
|
+ protected virtual void OnPropertyChanged<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ Optional<T> oldValue,
|
|
|
+ BindingValue<T> newValue,
|
|
|
+ BindingPriority priority)
|
|
|
{
|
|
|
}
|
|
|
|
|
@@ -499,40 +603,57 @@ namespace Avalonia
|
|
|
/// <param name="oldValue">The old property value.</param>
|
|
|
/// <param name="newValue">The new property value.</param>
|
|
|
/// <param name="priority">The priority of the binding that produced the value.</param>
|
|
|
- protected internal void RaisePropertyChanged(
|
|
|
- AvaloniaProperty property,
|
|
|
- object oldValue,
|
|
|
- object newValue,
|
|
|
+ protected internal void RaisePropertyChanged<T>(
|
|
|
+ AvaloniaProperty<T> property,
|
|
|
+ Optional<T> oldValue,
|
|
|
+ BindingValue<T> newValue,
|
|
|
BindingPriority priority = BindingPriority.LocalValue)
|
|
|
{
|
|
|
- Contract.Requires<ArgumentNullException>(property != null);
|
|
|
- VerifyAccess();
|
|
|
+ property = property ?? throw new ArgumentNullException(nameof(property));
|
|
|
|
|
|
- AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs(
|
|
|
- this,
|
|
|
- property,
|
|
|
- oldValue,
|
|
|
- newValue,
|
|
|
- priority);
|
|
|
+ VerifyAccess();
|
|
|
|
|
|
property.Notifying?.Invoke(this, true);
|
|
|
|
|
|
try
|
|
|
{
|
|
|
- OnPropertyChanged(e);
|
|
|
- property.NotifyChanged(e);
|
|
|
+ AvaloniaPropertyChangedEventArgs<T> e = null;
|
|
|
+ var hasChanged = property.HasChangedSubscriptions;
|
|
|
+
|
|
|
+ if (hasChanged || _propertyChanged != null)
|
|
|
+ {
|
|
|
+ e = new AvaloniaPropertyChangedEventArgs<T>(
|
|
|
+ this,
|
|
|
+ property,
|
|
|
+ oldValue,
|
|
|
+ newValue,
|
|
|
+ priority);
|
|
|
+ }
|
|
|
+
|
|
|
+ OnPropertyChanged(property, oldValue, newValue, priority);
|
|
|
+
|
|
|
+ if (hasChanged)
|
|
|
+ {
|
|
|
+ property.NotifyChanged(e);
|
|
|
+ }
|
|
|
|
|
|
_propertyChanged?.Invoke(this, e);
|
|
|
|
|
|
if (_inpcChanged != null)
|
|
|
{
|
|
|
- PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
|
|
|
- _inpcChanged(this, e2);
|
|
|
+ var inpce = new PropertyChangedEventArgs(property.Name);
|
|
|
+ _inpcChanged(this, inpce);
|
|
|
}
|
|
|
|
|
|
- if (property.Inherits)
|
|
|
+ if (property.Inherits && _inheritanceChildren != null)
|
|
|
{
|
|
|
- _inheritablePropertyChanged?.Invoke(this, e);
|
|
|
+ foreach (var child in _inheritanceChildren)
|
|
|
+ {
|
|
|
+ child.InheritedPropertyChanged(
|
|
|
+ property,
|
|
|
+ oldValue,
|
|
|
+ newValue.ToOptional());
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
finally
|
|
@@ -561,216 +682,103 @@ namespace Avalonia
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- DeferredSetter<T> setter = Values.GetDirectDeferredSetter(property);
|
|
|
-
|
|
|
- return setter.SetAndNotify(this, property, ref field, value);
|
|
|
+ var old = field;
|
|
|
+ field = value;
|
|
|
+ RaisePropertyChanged(property, old, value);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Tries to cast a value to a type, taking into account that the value may be a
|
|
|
- /// <see cref="BindingNotification"/>.
|
|
|
- /// </summary>
|
|
|
- /// <param name="value">The value.</param>
|
|
|
- /// <param name="type">The type.</param>
|
|
|
- /// <returns>The cast value, or a <see cref="BindingNotification"/>.</returns>
|
|
|
- private static object CastOrDefault(object value, Type type)
|
|
|
+ private T GetInheritedOrDefault<T>(StyledPropertyBase<T> property)
|
|
|
{
|
|
|
- var notification = value as BindingNotification;
|
|
|
-
|
|
|
- if (notification == null)
|
|
|
+ if (property.Inherits && InheritanceParent is AvaloniaObject o)
|
|
|
{
|
|
|
- return TypeUtilities.ConvertImplicitOrDefault(value, type);
|
|
|
+ return o.GetValueOrInheritedOrDefault(property);
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- if (notification.HasValue)
|
|
|
- {
|
|
|
- notification.SetValue(TypeUtilities.ConvertImplicitOrDefault(notification.Value, type));
|
|
|
- }
|
|
|
|
|
|
- return notification;
|
|
|
- }
|
|
|
+ return property.GetDefaultValue(GetType());
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets the default value for a property.
|
|
|
- /// </summary>
|
|
|
- /// <param name="property">The property.</param>
|
|
|
- /// <returns>The default value.</returns>
|
|
|
- private object GetDefaultValue(AvaloniaProperty property)
|
|
|
+ private T GetValueOrInheritedOrDefault<T>(StyledPropertyBase<T> property)
|
|
|
{
|
|
|
- if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
|
|
|
- return aobj.GetValueOrDefaultUnchecked(property);
|
|
|
- return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
|
|
|
- }
|
|
|
+ var o = this;
|
|
|
+ var inherits = property.Inherits;
|
|
|
+ var value = default(T);
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Gets the value or default value for a property.
|
|
|
- /// </summary>
|
|
|
- /// <param name="property">The property.</param>
|
|
|
- /// <returns>The default value.</returns>
|
|
|
- private object GetValueOrDefaultUnchecked(AvaloniaProperty property)
|
|
|
- {
|
|
|
- var aobj = this;
|
|
|
- var valuestore = aobj._values;
|
|
|
- if (valuestore != null)
|
|
|
+ while (o != null)
|
|
|
{
|
|
|
- var result = valuestore.GetValue(property);
|
|
|
- if (result != AvaloniaProperty.UnsetValue)
|
|
|
- {
|
|
|
- return result;
|
|
|
- }
|
|
|
- }
|
|
|
- if (property.Inherits)
|
|
|
- {
|
|
|
- while (aobj.InheritanceParent is AvaloniaObject parent)
|
|
|
- {
|
|
|
- aobj = parent;
|
|
|
- valuestore = aobj._values;
|
|
|
- if (valuestore != null)
|
|
|
- {
|
|
|
- var result = valuestore.GetValue(property);
|
|
|
- if (result != AvaloniaProperty.UnsetValue)
|
|
|
- {
|
|
|
- return result;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Sets the value of a direct property.
|
|
|
- /// </summary>
|
|
|
- /// <param name="property">The property.</param>
|
|
|
- /// <param name="value">The value.</param>
|
|
|
- private void SetDirectValue(AvaloniaProperty property, object value)
|
|
|
- {
|
|
|
- void Set()
|
|
|
- {
|
|
|
- var notification = value as BindingNotification;
|
|
|
+ var values = o._values;
|
|
|
|
|
|
- if (notification != null)
|
|
|
+ if (values?.TryGetValue(property, out value) == true)
|
|
|
{
|
|
|
- LogIfError(property, notification);
|
|
|
- value = notification.Value;
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
- if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
|
|
|
+ if (!inherits)
|
|
|
{
|
|
|
- var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
|
|
|
- var accessor = (IDirectPropertyAccessor)GetRegistered(property);
|
|
|
- var finalValue = value == AvaloniaProperty.UnsetValue ?
|
|
|
- metadata.UnsetValue : value;
|
|
|
-
|
|
|
- LogPropertySet(property, value, BindingPriority.LocalValue);
|
|
|
-
|
|
|
- accessor.SetValue(this, finalValue);
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
- if (notification != null)
|
|
|
- {
|
|
|
- UpdateDataValidation(property, notification);
|
|
|
- }
|
|
|
+ o = o.InheritanceParent as AvaloniaObject;
|
|
|
}
|
|
|
|
|
|
- if (Dispatcher.UIThread.CheckAccess())
|
|
|
- {
|
|
|
- Set();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- Dispatcher.UIThread.Post(Set);
|
|
|
- }
|
|
|
+ return property.GetDefaultValue(GetType());
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Sets the value of a styled property.
|
|
|
+ /// Sets the value of a direct property.
|
|
|
/// </summary>
|
|
|
/// <param name="property">The property.</param>
|
|
|
/// <param name="value">The value.</param>
|
|
|
- /// <param name="priority">The priority of the value.</param>
|
|
|
- private void SetStyledValue(AvaloniaProperty property, object value, BindingPriority priority)
|
|
|
+ private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
|
|
|
{
|
|
|
- var notification = value as BindingNotification;
|
|
|
+ var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
|
|
|
|
|
|
- // We currently accept BindingNotifications for non-direct properties but we just
|
|
|
- // strip them to their underlying value.
|
|
|
- if (notification != null)
|
|
|
+ if (value is UnsetValueType)
|
|
|
{
|
|
|
- if (!notification.HasValue)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- value = notification.Value;
|
|
|
- }
|
|
|
+ p.InvokeSetter(this, p.GetUnsetValue(GetType()));
|
|
|
}
|
|
|
-
|
|
|
- var originalValue = value;
|
|
|
-
|
|
|
- if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
|
|
|
+ else if (!(value is DoNothingType))
|
|
|
{
|
|
|
- throw new ArgumentException(string.Format(
|
|
|
- "Invalid value for Property '{0}': '{1}' ({2})",
|
|
|
- property.Name,
|
|
|
- originalValue,
|
|
|
- originalValue?.GetType().FullName ?? "(null)"));
|
|
|
+ p.InvokeSetter(this, value);
|
|
|
}
|
|
|
-
|
|
|
- LogPropertySet(property, value, priority);
|
|
|
- Values.AddValue(property, value, (int)priority);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Given a direct property, returns a registered avalonia property that is equivalent or
|
|
|
- /// throws if not found.
|
|
|
+ /// Sets the value of a direct property.
|
|
|
/// </summary>
|
|
|
/// <param name="property">The property.</param>
|
|
|
- /// <returns>The registered property.</returns>
|
|
|
- private AvaloniaProperty GetRegistered(AvaloniaProperty property)
|
|
|
+ /// <param name="value">The value.</param>
|
|
|
+ private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
|
|
|
{
|
|
|
- var direct = property as IDirectPropertyAccessor;
|
|
|
-
|
|
|
- if (direct == null)
|
|
|
- {
|
|
|
- throw new AvaloniaInternalException(
|
|
|
- "AvaloniaObject.GetRegistered should only be called for direct properties");
|
|
|
- }
|
|
|
+ var p = AvaloniaPropertyRegistry.Instance.FindRegisteredDirect(this, property);
|
|
|
|
|
|
- if (property.OwnerType.IsAssignableFrom(GetType()))
|
|
|
+ if (p == null)
|
|
|
{
|
|
|
- return property;
|
|
|
+ throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
|
|
|
}
|
|
|
|
|
|
- var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
|
|
|
- .FirstOrDefault(x => x == property);
|
|
|
+ LogIfError(property, value);
|
|
|
|
|
|
- if (result == null)
|
|
|
+ switch (value.Type)
|
|
|
{
|
|
|
- throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
|
|
|
+ case BindingValueType.UnsetValue:
|
|
|
+ case BindingValueType.BindingError:
|
|
|
+ var fallback = value.HasValue ? value : value.WithValue(property.GetUnsetValue(GetType()));
|
|
|
+ property.InvokeSetter(this, fallback);
|
|
|
+ break;
|
|
|
+ case BindingValueType.DataValidationError:
|
|
|
+ property.InvokeSetter(this, value);
|
|
|
+ break;
|
|
|
+ case BindingValueType.Value:
|
|
|
+ case BindingValueType.BindingErrorWithFallback:
|
|
|
+ case BindingValueType.DataValidationErrorWithFallback:
|
|
|
+ property.InvokeSetter(this, value);
|
|
|
+ break;
|
|
|
}
|
|
|
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Called when a property is changed on the current <see cref="InheritanceParent"/>.
|
|
|
- /// </summary>
|
|
|
- /// <param name="sender">The event sender.</param>
|
|
|
- /// <param name="e">The event args.</param>
|
|
|
- /// <remarks>
|
|
|
- /// Checks for changes in an inherited property value.
|
|
|
- /// </remarks>
|
|
|
- private void ParentPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
|
|
|
- {
|
|
|
- Contract.Requires<ArgumentNullException>(e != null);
|
|
|
-
|
|
|
- if (e.Property.Inherits && !IsSet(e.Property))
|
|
|
+ if (p.IsDataValidationEnabled)
|
|
|
{
|
|
|
- RaisePropertyChanged(e.Property, e.OldValue, e.NewValue, BindingPriority.LocalValue);
|
|
|
+ UpdateDataValidation(property, value);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -779,7 +787,7 @@ namespace Avalonia
|
|
|
/// </summary>
|
|
|
/// <param name="o">The observable.</param>
|
|
|
/// <returns>The description.</returns>
|
|
|
- private string GetDescription(IObservable<object> o)
|
|
|
+ private string GetDescription(object o)
|
|
|
{
|
|
|
var description = o as IDescription;
|
|
|
return description?.Description ?? o.ToString();
|
|
@@ -789,12 +797,12 @@ namespace Avalonia
|
|
|
/// Logs a mesage if the notification represents a binding error.
|
|
|
/// </summary>
|
|
|
/// <param name="property">The property being bound.</param>
|
|
|
- /// <param name="notification">The binding notification.</param>
|
|
|
- private void LogIfError(AvaloniaProperty property, BindingNotification notification)
|
|
|
+ /// <param name="value">The binding notification.</param>
|
|
|
+ private void LogIfError<T>(AvaloniaProperty property, BindingValue<T> value)
|
|
|
{
|
|
|
- if (notification.ErrorType == BindingErrorType.Error)
|
|
|
+ if (value.HasError)
|
|
|
{
|
|
|
- if (notification.Error is AggregateException aggregate)
|
|
|
+ if (value.Error is AggregateException aggregate)
|
|
|
{
|
|
|
foreach (var inner in aggregate.InnerExceptions)
|
|
|
{
|
|
@@ -803,7 +811,7 @@ namespace Avalonia
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- LogBindingError(property, notification.Error);
|
|
|
+ LogBindingError(property, value.Error);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -814,7 +822,7 @@ namespace Avalonia
|
|
|
/// <param name="property">The property.</param>
|
|
|
/// <param name="value">The new value.</param>
|
|
|
/// <param name="priority">The priority.</param>
|
|
|
- private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority)
|
|
|
+ private void LogPropertySet<T>(AvaloniaProperty<T> property, T value, BindingPriority priority)
|
|
|
{
|
|
|
Logger.TryGet(LogEventLevel.Verbose)?.Log(
|
|
|
LogArea.Property,
|
|
@@ -825,16 +833,16 @@ namespace Avalonia
|
|
|
priority);
|
|
|
}
|
|
|
|
|
|
- private class DirectBindingSubscription : IObserver<object>, IDisposable
|
|
|
+ private class DirectBindingSubscription<T> : IObserver<BindingValue<T>>, IDisposable
|
|
|
{
|
|
|
- readonly AvaloniaObject _owner;
|
|
|
- readonly AvaloniaProperty _property;
|
|
|
- IDisposable _subscription;
|
|
|
+ private readonly AvaloniaObject _owner;
|
|
|
+ private readonly DirectPropertyBase<T> _property;
|
|
|
+ private readonly IDisposable _subscription;
|
|
|
|
|
|
public DirectBindingSubscription(
|
|
|
AvaloniaObject owner,
|
|
|
- AvaloniaProperty property,
|
|
|
- IObservable<object> source)
|
|
|
+ DirectPropertyBase<T> property,
|
|
|
+ IObservable<BindingValue<T>> source)
|
|
|
{
|
|
|
_owner = owner;
|
|
|
_property = property;
|
|
@@ -850,11 +858,22 @@ namespace Avalonia
|
|
|
|
|
|
public void OnCompleted() => Dispose();
|
|
|
public void OnError(Exception error) => Dispose();
|
|
|
-
|
|
|
- public void OnNext(object value)
|
|
|
+ public void OnNext(BindingValue<T> value)
|
|
|
{
|
|
|
- var castValue = CastOrDefault(value, _property.PropertyType);
|
|
|
- _owner.SetDirectValue(_property, castValue);
|
|
|
+ if (Dispatcher.UIThread.CheckAccess())
|
|
|
+ {
|
|
|
+ _owner.SetDirectValueUnchecked(_property, value);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // To avoid allocating closure in the outer scope we need to capture variables
|
|
|
+ // locally. This allows us to skip most of the allocations when on UI thread.
|
|
|
+ var instance = _owner;
|
|
|
+ var property = _property;
|
|
|
+ var newValue = value;
|
|
|
+
|
|
|
+ Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|