Browse Source

Get non-animated change information.

- Pass information for all property changes to `OnPropertyChangedCore`; whether they result in an effective value change or not
- Added `GetBaseValue` to get a value with a specified priority
- Change the signature of `OnPropertyChanged` again to take an `AvaloniaPropertyChangedEventArgs<T>`
Steven Kirk 5 years ago
parent
commit
4a752c3f48
39 changed files with 831 additions and 361 deletions
  1. 7 11
      src/Avalonia.Animation/Animatable.cs
  2. 117 81
      src/Avalonia.Base/AvaloniaObject.cs
  3. 59 0
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  4. 7 0
      src/Avalonia.Base/AvaloniaProperty.cs
  5. 15 8
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  6. 16 8
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  7. 5 0
      src/Avalonia.Base/DirectPropertyBase.cs
  8. 13 0
      src/Avalonia.Base/IAvaloniaObject.cs
  9. 12 8
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  10. 9 5
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  11. 3 3
      src/Avalonia.Base/PropertyStore/IValue.cs
  12. 1 5
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  13. 8 3
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  14. 110 51
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  15. 7 0
      src/Avalonia.Base/StyledPropertyBase.cs
  16. 40 29
      src/Avalonia.Base/ValueStore.cs
  17. 1 1
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  18. 4 8
      src/Avalonia.Controls/Button.cs
  19. 4 8
      src/Avalonia.Controls/ButtonSpinner.cs
  20. 4 8
      src/Avalonia.Controls/Calendar/DatePicker.cs
  21. 4 8
      src/Avalonia.Controls/Expander.cs
  22. 4 8
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  23. 8 12
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  24. 4 8
      src/Avalonia.Controls/Primitives/Track.cs
  25. 6 10
      src/Avalonia.Controls/ProgressBar.cs
  26. 16 12
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  27. 4 8
      src/Avalonia.Controls/Slider.cs
  28. 4 8
      src/Avalonia.Controls/Window.cs
  29. 6 6
      src/Avalonia.Input/InputElement.cs
  30. 3 3
      src/Avalonia.Layout/StackLayout.cs
  31. 17 18
      src/Avalonia.Layout/UniformGridLayout.cs
  32. 1 1
      src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs
  33. 3 7
      src/Avalonia.Visuals/Media/DrawingImage.cs
  34. 51 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs
  35. 142 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs
  36. 19 0
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  37. 90 8
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  38. 3 3
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs
  39. 4 4
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

+ 7 - 11
src/Avalonia.Animation/Animatable.cs

@@ -62,30 +62,26 @@ namespace Avalonia.Animation
             }
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            if (_transitions is null || _previousTransitions is null || priority == BindingPriority.Animation)
+            if (_transitions is null || _previousTransitions is null || change.Priority == BindingPriority.Animation)
                 return;
 
             // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
             foreach (var transition in _transitions)
             {
-                if (transition.Property == property)
+                if (transition.Property == change.Property)
                 {
-                    if (_previousTransitions.TryGetValue(property, out var dispose))
+                    if (_previousTransitions.TryGetValue(change.Property, out var dispose))
                         dispose.Dispose();
 
                     var instance = transition.Apply(
                         this,
                         Clock ?? Avalonia.Animation.Clock.GlobalClock,
-                        oldValue.GetValueOrDefault(),
-                        newValue.GetValueOrDefault());
+                        change.OldValue.GetValueOrDefault(),
+                        change.NewValue.GetValueOrDefault());
 
-                    _previousTransitions[property] = instance;
+                    _previousTransitions[change.Property] = instance;
                     return;
                 }
             }

+ 117 - 81
src/Avalonia.Base/AvaloniaObject.cs

@@ -259,6 +259,21 @@ namespace Avalonia
             return registered.InvokeGetter(this);
         }
 
+        /// <inheritdoc/>
+        public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority)
+        {
+            property = property ?? throw new ArgumentNullException(nameof(property));
+            VerifyAccess();
+
+            if (_values is object &&
+                _values.TryGetValue(property, maxPriority, out var value))
+            {
+                return value;
+            }
+
+            return default;
+        }
+
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
         /// </summary>
@@ -458,29 +473,43 @@ namespace Avalonia
             return _propertyChanged?.GetInvocationList();
         }
 
-        void IValueSink.ValueChanged<T>(
-            StyledPropertyBase<T> property,
-            BindingPriority priority,
-            Optional<T> oldValue,
-            BindingValue<T> newValue)
+        void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            oldValue = oldValue.HasValue ? oldValue : GetInheritedOrDefault(property);
-            newValue = newValue.HasValue ? newValue : newValue.WithValue(GetInheritedOrDefault(property));
+            var property = (StyledPropertyBase<T>)change.Property;
 
-            LogIfError(property, newValue);
+            LogIfError(property, change.NewValue);
 
-            if (!EqualityComparer<T>.Default.Equals(oldValue.Value, newValue.Value))
+            // If the change is to the effective value of the property and no old/new value is set
+            // then fill in the old/new value from property inheritance/default value. We don't do
+            // this for non-effective value changes because these are only needed for property
+            // transitions, where knowing e.g. that an inherited value is active at an arbitrary
+            // priority isn't of any use and would introduce overhead.
+            if (change.IsEffectiveValueChange && !change.OldValue.HasValue)
             {
-                RaisePropertyChanged(property, oldValue, newValue, priority);
+                change.SetOldValue(GetInheritedOrDefault<T>(property));
+            }
 
-                Logger.TryGet(LogEventLevel.Verbose)?.Log(
-                    LogArea.Property,
-                    this,
-                    "{Property} changed from {$Old} to {$Value} with priority {Priority}",
-                    property,
-                    oldValue,
-                    newValue,
-                    (BindingPriority)priority);
+            if (change.IsEffectiveValueChange && !change.NewValue.HasValue)
+            {
+                change.SetNewValue(GetInheritedOrDefault(property));
+            }
+
+            if (!change.IsEffectiveValueChange ||
+                !EqualityComparer<T>.Default.Equals(change.OldValue.Value, change.NewValue.Value))
+            {
+                RaisePropertyChanged(change);
+
+                if (change.IsEffectiveValueChange)
+                {
+                    Logger.TryGet(LogEventLevel.Verbose)?.Log(
+                        LogArea.Property,
+                        this,
+                        "{Property} changed from {$Old} to {$Value} with priority {Priority}",
+                        property,
+                        change.OldValue,
+                        change.NewValue,
+                        change.Priority);
+                }
             }
         }
 
@@ -489,7 +518,13 @@ namespace Avalonia
             IPriorityValueEntry entry,
             Optional<T> oldValue) 
         {
-            ((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default);
+            var change = new AvaloniaPropertyChangedEventArgs<T>(
+                this,
+                property,
+                oldValue,
+                default,
+                BindingPriority.Unset);
+            ((IValueSink)this).ValueChanged(change);
         }
 
         /// <summary>
@@ -575,15 +610,20 @@ namespace Avalonia
         /// <summary>
         /// Called when a avalonia property changes on the object.
         /// </summary>
-        /// <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)
+        /// <param name="change">The property change details.</param>
+        protected virtual void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        {
+            if (change.IsEffectiveValueChange)
+            {
+                OnPropertyChanged(change);
+            }
+        }
+
+        /// <summary>
+        /// Called when a avalonia property changes on the object.
+        /// </summary>
+        /// <param name="change">The property change details.</param>
+        protected virtual void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
         }
 
@@ -600,57 +640,12 @@ namespace Avalonia
             BindingValue<T> newValue,
             BindingPriority priority = BindingPriority.LocalValue)
         {
-            property = property ?? throw new ArgumentNullException(nameof(property));
-
-            VerifyAccess();
-
-            property.Notifying?.Invoke(this, true);
-
-            try
-            {
-                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)
-                {
-                    var inpce = new PropertyChangedEventArgs(property.Name);
-                    _inpcChanged(this, inpce);
-                }
-
-                if (property.Inherits && _inheritanceChildren != null)
-                {
-                    foreach (var child in _inheritanceChildren)
-                    {
-                        child.InheritedPropertyChanged(
-                            property,
-                            oldValue,
-                            newValue.ToOptional());
-                    }
-                }
-            }
-            finally
-            {
-                property.Notifying?.Invoke(this, false);
-            }
+            RaisePropertyChanged(new AvaloniaPropertyChangedEventArgs<T>(
+                this,
+                property,
+                oldValue,
+                newValue,
+                priority));
         }
 
         /// <summary>
@@ -689,7 +684,9 @@ namespace Avalonia
             return property.GetDefaultValue(GetType());
         }
 
-        private T GetValueOrInheritedOrDefault<T>(StyledPropertyBase<T> property)
+        private T GetValueOrInheritedOrDefault<T>(
+            StyledPropertyBase<T> property,
+            BindingPriority maxPriority = BindingPriority.Animation)
         {
             var o = this;
             var inherits = property.Inherits;
@@ -699,7 +696,7 @@ namespace Avalonia
             {
                 var values = o._values;
 
-                if (values?.TryGetValue(property, out value) == true)
+                if (values?.TryGetValue(property, maxPriority, out value) == true)
                 {
                     return value;
                 }
@@ -715,6 +712,45 @@ namespace Avalonia
             return property.GetDefaultValue(GetType());
         }
 
+        protected internal void RaisePropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+        {
+            VerifyAccess();
+
+            change.Property.Notifying?.Invoke(this, true);
+
+            try
+            {
+                OnPropertyChangedCore(change);
+
+                if (change.IsEffectiveValueChange)
+                {
+                    change.Property.NotifyChanged(change);
+                    _propertyChanged?.Invoke(this, change);
+
+                    if (_inpcChanged != null)
+                    {
+                        var inpce = new PropertyChangedEventArgs(change.Property.Name);
+                        _inpcChanged(this, inpce);
+                    }
+
+                    if (change.Property.Inherits && _inheritanceChildren != null)
+                    {
+                        foreach (var child in _inheritanceChildren)
+                        {
+                            child.InheritedPropertyChanged(
+                                change.Property,
+                                change.OldValue,
+                                change.NewValue.ToOptional());
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                change.Property.Notifying?.Invoke(this, false);
+            }
+        }
+
         /// <summary>
         /// Sets the value of a direct property.
         /// </summary>

+ 59 - 0
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@@ -448,6 +448,65 @@ namespace Avalonia
             };
         }
 
+        /// <summary>
+        /// Gets an <see cref="AvaloniaProperty"/> base value.
+        /// </summary>
+        /// <param name="target">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <param name="maxPriority">The maximum priority for the value.</param>
+        /// <remarks>
+        /// For styled properties, gets the value of the property if set on the object with a
+        /// priority equal or lower to <paramref name="maxPriority"/>, otherwise
+        /// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
+        /// property values that come from inherited or default values.
+        /// 
+        /// For direct properties returns <see cref="GetValue(IAvaloniaObject, AvaloniaProperty)"/>.
+        /// </remarks>
+        public static object GetBaseValue(
+            this IAvaloniaObject target,
+            AvaloniaProperty property,
+            BindingPriority maxPriority)
+        {
+            target = target ?? throw new ArgumentNullException(nameof(target));
+            property = property ?? throw new ArgumentNullException(nameof(property));
+
+            return property.RouteGetBaseValue(target, maxPriority);
+        }
+
+        /// <summary>
+        /// Gets an <see cref="AvaloniaProperty"/> base value.
+        /// </summary>
+        /// <param name="target">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <param name="maxPriority">The maximum priority for the value.</param>
+        /// <remarks>
+        /// For styled properties, gets the value of the property if set on the object with a
+        /// priority equal or lower to <paramref name="maxPriority"/>, otherwise
+        /// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
+        /// that come from inherited or default values.
+        /// 
+        /// For direct properties returns
+        /// <see cref="IAvaloniaObject.GetValue{T}(DirectPropertyBase{T})"/>.
+        /// </remarks>
+        public static Optional<T> GetBaseValue<T>(
+            this IAvaloniaObject target,
+            AvaloniaProperty<T> property,
+            BindingPriority maxPriority)
+        {
+            target = target ?? throw new ArgumentNullException(nameof(target));
+            property = property ?? throw new ArgumentNullException(nameof(property));
+
+            target = target ?? throw new ArgumentNullException(nameof(target));
+            property = property ?? throw new ArgumentNullException(nameof(property));
+
+            return property switch
+            {
+                StyledPropertyBase<T> styled => target.GetBaseValue(styled, maxPriority),
+                DirectPropertyBase<T> direct => target.GetValue(direct),
+                _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
+            };
+        }
+
         /// <summary>
         /// Sets a <see cref="AvaloniaProperty"/> value.
         /// </summary>

+ 7 - 0
src/Avalonia.Base/AvaloniaProperty.cs

@@ -496,6 +496,13 @@ namespace Avalonia
         /// <param name="o">The object instance.</param>
         internal abstract object RouteGetValue(IAvaloniaObject o);
 
+        /// <summary>
+        /// Routes an untyped GetBaseValue call to a typed call.
+        /// </summary>
+        /// <param name="o">The object instance.</param>
+        /// <param name="maxPriority">The maximum priority for the value.</param>
+        internal abstract object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority);
+
         /// <summary>
         /// Routes an untyped SetValue call to a typed call.
         /// </summary>

+ 15 - 8
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@@ -16,6 +16,7 @@ namespace Avalonia
         {
             Sender = sender;
             Priority = priority;
+            IsEffectiveValueChange = true;
         }
 
         /// <summary>
@@ -35,19 +36,11 @@ namespace Avalonia
         /// <summary>
         /// Gets the old value of the property.
         /// </summary>
-        /// <value>
-        /// The old value of the property or <see cref="AvaloniaProperty.UnsetValue"/> if the
-        /// property previously had no value.
-        /// </value>
         public object? OldValue => GetOldValue();
 
         /// <summary>
         /// Gets the new value of the property.
         /// </summary>
-        /// <value>
-        /// The new value of the property or <see cref="AvaloniaProperty.UnsetValue"/> if the
-        /// property previously had no value.
-        /// </value>
         public object? NewValue => GetNewValue();
 
         /// <summary>
@@ -58,6 +51,20 @@ namespace Avalonia
         /// </value>
         public BindingPriority Priority { get; private set; }
 
+        /// <summary>
+        /// Gets a value indicating whether the change represents a change to the effective value of
+        /// the property.
+        /// </summary>
+        /// <remarks>
+        /// This will usually be true, except in
+        /// <see cref="AvaloniaObject.OnPropertyChangedCore{T}(AvaloniaPropertyChangedEventArgs{T})"/>
+        /// which recieves notifications for all changes to property values, whether a value with a higher
+        /// priority is present or not. When this property is false, the change that is being signalled
+        /// has not resulted in a change to the property value on the object.
+        /// </remarks>
+        public bool IsEffectiveValueChange { get; private set; }
+
+        internal void MarkNonEffectiveValue() => IsEffectiveValueChange = false;
         protected abstract AvaloniaProperty GetProperty();
         protected abstract object? GetOldValue();
         protected abstract object? GetNewValue();

+ 16 - 8
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@@ -1,4 +1,3 @@
-using System;
 using Avalonia.Data;
 
 #nullable enable
@@ -6,7 +5,7 @@ using Avalonia.Data;
 namespace Avalonia
 {
     /// <summary>
-    /// Provides information for a avalonia property change.
+    /// Provides information for an Avalonia property change.
     /// </summary>
     public class AvaloniaPropertyChangedEventArgs<T> : AvaloniaPropertyChangedEventArgs
     {
@@ -42,19 +41,28 @@ namespace Avalonia
         /// <summary>
         /// Gets the old value of the property.
         /// </summary>
-        /// <value>
-        /// The old value of the property.
-        /// </value>
+        /// <remarks>
+        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
+        /// old value of the property on the object. 
+        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false, returns
+        /// <see cref="Optional{T}.Empty"/>.
+        /// </remarks>
         public new Optional<T> OldValue { get; private set; }
 
         /// <summary>
         /// Gets the new value of the property.
         /// </summary>
-        /// <value>
-        /// The new value of the property.
-        /// </value>
+        /// <remarks>
+        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
+        /// value of the property on the object.
+        /// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false returns the
+        /// changed value, or <see cref="Optional{T}.Empty"/> if the value was removed.
+        /// </remarks>
         public new BindingValue<T> NewValue { get; private set; }
 
+        internal void SetOldValue(Optional<T> value) => OldValue = value;
+        internal void SetNewValue(BindingValue<T> value) => NewValue = value;
+
         protected override AvaloniaProperty GetProperty() => Property;
 
         protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);

+ 5 - 0
src/Avalonia.Base/DirectPropertyBase.cs

@@ -120,6 +120,11 @@ namespace Avalonia
             return o.GetValue<TValue>(this);
         }
 
+        internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+        {
+            return o.GetValue<TValue>(this);
+        }
+
         /// <inheritdoc/>
         internal override IDisposable? RouteSetValue(
             IAvaloniaObject o,

+ 13 - 0
src/Avalonia.Base/IAvaloniaObject.cs

@@ -41,6 +41,19 @@ namespace Avalonia
         /// <returns>The value.</returns>
         T GetValue<T>(DirectPropertyBase<T> property);
 
+        /// <summary>
+        /// Gets an <see cref="AvaloniaProperty"/> base value.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="maxPriority">The maximum priority for the value.</param>
+        /// <remarks>
+        /// Gets the value of the property, if set on this object with a priority equal or lower to
+        /// <paramref name="maxPriority"/>, otherwise <see cref="Optional{T}.Empty"/>. Note that
+        /// this method does not return property values that come from inherited or default values.
+        /// </remarks>
+        Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority);
+
         /// <summary>
         /// Checks whether a <see cref="AvaloniaProperty"/> is animating.
         /// </summary>

+ 12 - 8
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@@ -22,6 +22,7 @@ namespace Avalonia.PropertyStore
         private readonly IAvaloniaObject _owner;
         private IValueSink _sink;
         private IDisposable? _subscription;
+        private Optional<T> _value;
 
         public BindingEntry(
             IAvaloniaObject owner,
@@ -40,18 +41,21 @@ namespace Avalonia.PropertyStore
         public StyledPropertyBase<T> Property { get; }
         public BindingPriority Priority { get; }
         public IObservable<BindingValue<T>> Source { get; }
-        public Optional<T> Value { get; private set; }
-        Optional<object> IValue.Value => Value.ToObject();
-        BindingPriority IValue.ValuePriority => Priority;
+        Optional<object> IValue.GetValue() => _value.ToObject();
+
+        public Optional<T> GetValue(BindingPriority maxPriority)
+        {
+            return Priority >= maxPriority ? _value : Optional<T>.Empty;
+        }
 
         public void Dispose()
         {
             _subscription?.Dispose();
             _subscription = null;
-            _sink.Completed(Property, this, Value);
+            _sink.Completed(Property, this, _value);
         }
 
-        public void OnCompleted() => _sink.Completed(Property, this, Value);
+        public void OnCompleted() => _sink.Completed(Property, this, _value);
 
         public void OnError(Exception error)
         {
@@ -94,14 +98,14 @@ namespace Avalonia.PropertyStore
                 return;
             }
 
-            var old = Value;
+            var old = _value;
 
             if (value.Type != BindingValueType.DataValidationError)
             {
-                Value = value.ToOptional();
+                _value = value.ToOptional();
             }
 
-            _sink.ValueChanged(Property, Priority, old, value);
+            _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(_owner, Property, old, value, Priority));
         }
     }
 }

+ 9 - 5
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -13,6 +13,7 @@ namespace Avalonia.PropertyStore
     internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IDisposable
     {
         private IValueSink _sink;
+        private Optional<T> _value;
 
         public ConstantValueEntry(
             StyledPropertyBase<T> property,
@@ -21,18 +22,21 @@ namespace Avalonia.PropertyStore
             IValueSink sink)
         {
             Property = property;
-            Value = value;
+            _value = value;
             Priority = priority;
             _sink = sink;
         }
 
         public StyledPropertyBase<T> Property { get; }
         public BindingPriority Priority { get; }
-        public Optional<T> Value { get; }
-        Optional<object> IValue.Value => Value.ToObject();
-        BindingPriority IValue.ValuePriority => Priority;
+        Optional<object> IValue.GetValue() => _value.ToObject();
 
-        public void Dispose() => _sink.Completed(Property, this, Value);
+        public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation)
+        {
+            return Priority >= maxPriority ? _value : Optional<T>.Empty;
+        }
+
+        public void Dispose() => _sink.Completed(Property, this, _value);
         public void Reparent(IValueSink sink) => _sink = sink;
     }
 }

+ 3 - 3
src/Avalonia.Base/PropertyStore/IValue.cs

@@ -9,8 +9,8 @@ namespace Avalonia.PropertyStore
     /// </summary>
     internal interface IValue
     {
-        Optional<object> Value { get; }
-        BindingPriority ValuePriority { get; }
+        Optional<object> GetValue();
+        BindingPriority Priority { get; }
     }
 
     /// <summary>
@@ -19,6 +19,6 @@ namespace Avalonia.PropertyStore
     /// <typeparam name="T">The property type.</typeparam>
     internal interface IValue<T> : IValue
     {
-        new Optional<T> Value { get; }
+        Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation);
     }
 }

+ 1 - 5
src/Avalonia.Base/PropertyStore/IValueSink.cs

@@ -9,11 +9,7 @@ namespace Avalonia.PropertyStore
     /// </summary>
     internal interface IValueSink
     {
-        void ValueChanged<T>(
-            StyledPropertyBase<T> property,
-            BindingPriority priority,
-            Optional<T> oldValue,
-            BindingValue<T> newValue);
+        void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change);
 
         void Completed<T>(
             StyledPropertyBase<T> property,

+ 8 - 3
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -14,9 +14,14 @@ namespace Avalonia.PropertyStore
         private T _value;
 
         public LocalValueEntry(T value) => _value = value;
-        public Optional<T> Value => _value;
-        public BindingPriority ValuePriority => BindingPriority.LocalValue;
-        Optional<object> IValue.Value => Value.ToObject();
+        public BindingPriority Priority => BindingPriority.LocalValue;
+        Optional<object> IValue.GetValue() => new Optional<object>(_value);
+        
+        public Optional<T> GetValue(BindingPriority maxPriority)
+        {
+            return BindingPriority.LocalValue >= maxPriority ? _value : Optional<T>.Empty;
+        }
+
         public void SetValue(T value) => _value = value;
     }
 }

+ 110 - 51
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using Avalonia.Data;
 
 #nullable enable
@@ -24,6 +25,7 @@ namespace Avalonia.PropertyStore
         private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>();
         private readonly Func<IAvaloniaObject, T, T>? _coerceValue;
         private Optional<T> _localValue;
+        private Optional<T> _value;
 
         public PriorityValue(
             IAvaloniaObject owner,
@@ -50,11 +52,13 @@ namespace Avalonia.PropertyStore
         {
             existing.Reparent(this);
             _entries.Add(existing);
+
+            var v = existing.GetValue();
             
-            if (existing.Value.HasValue)
+            if (v.HasValue)
             {
-                Value = existing.Value;
-                ValuePriority = existing.Priority;
+                _value = v;
+                Priority = existing.Priority;
             }
         }
 
@@ -65,18 +69,39 @@ namespace Avalonia.PropertyStore
             LocalValueEntry<T> existing)
             : this(owner, property, sink)
         {
-            _localValue = existing.Value;
-            Value = _localValue;
-            ValuePriority = BindingPriority.LocalValue;
+            _value = _localValue = existing.GetValue(BindingPriority.LocalValue);
+            Priority = BindingPriority.LocalValue;
         }
 
         public StyledPropertyBase<T> Property { get; }
-        public Optional<T> Value { get; private set; }
-        public BindingPriority ValuePriority { get; private set; }
+        public BindingPriority Priority { get; private set; } = BindingPriority.Unset;
         public IReadOnlyList<IPriorityValueEntry<T>> Entries => _entries;
-        Optional<object> IValue.Value => Value.ToObject();
+        Optional<object> IValue.GetValue() => _value.ToObject();
+
+        public void ClearLocalValue()
+        {
+            UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
+                _owner,
+                Property,
+                default,
+                default,
+                BindingPriority.LocalValue));
+        }
+
+        public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation)
+        {
+            if (Priority == BindingPriority.Unset)
+            {
+                return default;
+            }
 
-        public void ClearLocalValue() => UpdateEffectiveValue();
+            if (Priority >= maxPriority)
+            {
+                return _value;
+            }
+
+            return CalculateValue(maxPriority).Item1;
+        }
 
         public IDisposable? SetValue(T value, BindingPriority priority)
         {
@@ -94,7 +119,13 @@ namespace Avalonia.PropertyStore
                 result = entry;
             }
 
-            UpdateEffectiveValue();
+            UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
+                _owner,
+                Property,
+                default,
+                value,
+                priority));
+
             return result;
         }
 
@@ -106,20 +137,19 @@ namespace Avalonia.PropertyStore
             return binding;
         }
 
-        public void CoerceValue() => UpdateEffectiveValue();
+        public void CoerceValue() => UpdateEffectiveValue(null);
 
-        void IValueSink.ValueChanged<TValue>(
-            StyledPropertyBase<TValue> property,
-            BindingPriority priority,
-            Optional<TValue> oldValue,
-            BindingValue<TValue> newValue)
+        void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
         {
-            if (priority == BindingPriority.LocalValue)
+            if (change.Priority == BindingPriority.LocalValue)
             {
                 _localValue = default;
             }
 
-            UpdateEffectiveValue();
+            if (change is AvaloniaPropertyChangedEventArgs<T> c)
+            {
+                UpdateEffectiveValue(c);
+            }
         }
 
         void IValueSink.Completed<TValue>(
@@ -128,7 +158,16 @@ namespace Avalonia.PropertyStore
             Optional<TValue> oldValue)
         {
             _entries.Remove((IPriorityValueEntry<T>)entry);
-            UpdateEffectiveValue();
+
+            if (oldValue is Optional<T> o)
+            {
+                UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
+                    _owner,
+                    Property,
+                    o,
+                    default,
+                    entry.Priority));
+            }
         }
 
         private int FindInsertPoint(BindingPriority priority)
@@ -147,53 +186,73 @@ namespace Avalonia.PropertyStore
             return result;
         }
 
-        private void UpdateEffectiveValue()
+        public (Optional<T>, BindingPriority) CalculateValue(BindingPriority maxPriority)
         {
             var reachedLocalValues = false;
-            var value = default(Optional<T>);
 
-            if (_entries.Count > 0)
+            for (var i = _entries.Count - 1; i >= 0; --i)
             {
-                for (var i = _entries.Count - 1; i >= 0; --i)
+                var entry = _entries[i];
+
+                if (entry.Priority < maxPriority)
+                {
+                    continue;
+                }
+
+                if (!reachedLocalValues &&
+                    entry.Priority >= BindingPriority.LocalValue &&
+                    maxPriority <= BindingPriority.LocalValue &&
+                    _localValue.HasValue)
+                {
+                    return (_localValue, BindingPriority.LocalValue);
+                }
+
+                var entryValue = entry.GetValue();
+
+                if (entryValue.HasValue)
                 {
-                    var entry = _entries[i];
-
-                    if (!reachedLocalValues && entry.Priority >= BindingPriority.LocalValue)
-                    {
-                        reachedLocalValues = true;
-
-                        if (_localValue.HasValue)
-                        {
-                            value = _localValue;
-                            ValuePriority = BindingPriority.LocalValue;
-                            break;
-                        }
-                    }
-
-                    if (entry.Value.HasValue)
-                    {
-                        value = entry.Value;
-                        ValuePriority = entry.Priority;
-                        break;
-                    }
+                    return (entryValue, entry.Priority);
                 }
             }
-            else if (_localValue.HasValue)
+
+            if (!reachedLocalValues &&
+                maxPriority <= BindingPriority.LocalValue &&
+                _localValue.HasValue)
             {
-                value = _localValue;
-                ValuePriority = BindingPriority.LocalValue;
+                return (_localValue, BindingPriority.LocalValue);
             }
 
+            return (default, BindingPriority.Unset);
+        }
+
+        private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs<T>? change)
+        {
+            var (value, priority) = CalculateValue(BindingPriority.Animation);
+
             if (value.HasValue && _coerceValue != null)
             {
                 value = _coerceValue(_owner, value.Value);
             }
 
-            if (value != Value)
+            Priority = priority;
+
+            if (value != _value)
+            {
+                var old = _value;
+                _value = value;
+
+                _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+                    _owner,
+                    Property,
+                    old,
+                    value,
+                    Priority));
+            }
+            else if (change is object)
             {
-                var old = Value;
-                Value = value;
-                _sink.ValueChanged(Property, ValuePriority, old, value);
+                change.MarkNonEffectiveValue();
+                change.SetOldValue(default);
+                _sink.ValueChanged(change);
             }
         }
     }

+ 7 - 0
src/Avalonia.Base/StyledPropertyBase.cs

@@ -197,6 +197,13 @@ namespace Avalonia
             return o.GetValue<TValue>(this);
         }
 
+        /// <inheritdoc/>
+        internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+        {
+            var value = o.GetBaseValue<TValue>(this, maxPriority);
+            return value.HasValue ? value.Value : AvaloniaProperty.UnsetValue;
+        }
+
         /// <inheritdoc/>
         internal override IDisposable RouteSetValue(
             IAvaloniaObject o,

+ 40 - 29
src/Avalonia.Base/ValueStore.cs

@@ -37,7 +37,7 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                return slot.ValuePriority < BindingPriority.LocalValue;
+                return slot.Priority < BindingPriority.LocalValue;
             }
 
             return false;
@@ -47,21 +47,24 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                return slot.Value.HasValue;
+                return slot.GetValue().HasValue;
             }
 
             return false;
         }
 
-        public bool TryGetValue<T>(StyledPropertyBase<T> property, out T value)
+        public bool TryGetValue<T>(
+            StyledPropertyBase<T> property,
+            BindingPriority maxPriority,
+            out T value)
         {
             if (_values.TryGetValue(property, out var slot))
             {
-                var v = (IValue<T>)slot;
+                var v = ((IValue<T>)slot).GetValue(maxPriority);
 
-                if (v.Value.HasValue)
+                if (v.HasValue)
                 {
-                    value = v.Value.Value;
+                    value = v.Value;
                     return true;
                 }
             }
@@ -90,17 +93,22 @@ namespace Avalonia
                 _values.AddValue(property, entry);
                 result = entry.SetValue(value, priority);
             }
-            else if (priority == BindingPriority.LocalValue)
-            {
-                _values.AddValue(property, new LocalValueEntry<T>(value));
-                _sink.ValueChanged(property, priority, default, value);
-            }
             else
             {
-                var entry = new ConstantValueEntry<T>(property, value, priority, this);
-                _values.AddValue(property, entry);
-                _sink.ValueChanged(property, priority, default, value);
-                result = entry;
+                var change = new AvaloniaPropertyChangedEventArgs<T>(_owner, property, default, value, priority);
+
+                if (priority == BindingPriority.LocalValue)
+                {
+                    _values.AddValue(property, new LocalValueEntry<T>(value));
+                    _sink.ValueChanged(change);
+                }
+                else
+                {
+                    var entry = new ConstantValueEntry<T>(property, value, priority, this);
+                    _values.AddValue(property, entry);
+                    _sink.ValueChanged(change);
+                    result = entry;
+                }
             }
 
             return result;
@@ -149,13 +157,14 @@ namespace Avalonia
 
                     if (remove)
                     {
-                        var old = TryGetValue(property, out var value) ? value : default;
+                        var old = TryGetValue(property, BindingPriority.LocalValue, out var value) ? value : default;
                         _values.Remove(property);
-                        _sink.ValueChanged(
+                        _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+                            _owner,
                             property,
-                            BindingPriority.Unset,
                             old,
-                            BindingValue<T>.Unset);
+                            default,
+                            BindingPriority.Unset));
                     }
                 }
             }
@@ -176,23 +185,20 @@ namespace Avalonia
         {
             if (_values.TryGetValue(property, out var slot))
             {
+                var slotValue = slot.GetValue();
                 return new Diagnostics.AvaloniaPropertyValue(
                     property,
-                    slot.Value.HasValue ? slot.Value.Value : AvaloniaProperty.UnsetValue,
-                    slot.ValuePriority,
+                    slotValue.HasValue ? slotValue.Value : AvaloniaProperty.UnsetValue,
+                    slot.Priority,
                     null);
             }
 
             return null;
         }
 
-        void IValueSink.ValueChanged<T>(
-            StyledPropertyBase<T> property,
-            BindingPriority priority,
-            Optional<T> oldValue,
-            BindingValue<T> newValue)
+        void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            _sink.ValueChanged(property, priority, oldValue, newValue);
+            _sink.ValueChanged(change);
         }
 
         void IValueSink.Completed<T>(
@@ -232,9 +238,14 @@ namespace Avalonia
             {
                 if (priority == BindingPriority.LocalValue)
                 {
-                    var old = l.Value;
+                    var old = l.GetValue(BindingPriority.LocalValue);
                     l.SetValue(value);
-                    _sink.ValueChanged(property, priority, old, value);
+                    _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
+                        _owner,
+                        property,
+                        old,
+                        value,
+                        priority));
                 }
                 else
                 {

+ 1 - 1
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -767,7 +767,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// ItemsProperty property changed handler.
         /// </summary>
-        /// <param name="e">AvaloniaPropertyChangedEventArgs.</param>
+        /// <param name="e">AvaloniaPropertyChangedEventArgsdEventArgs.</param>
         private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
         {
             if (!_areHandlersSuspended)

+ 4 - 8
src/Avalonia.Controls/Button.cs

@@ -313,17 +313,13 @@ namespace Avalonia.Controls
             IsPressed = false;
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == IsPressedProperty)
+            if (change.Property == IsPressedProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>());
             }
         }
 

+ 4 - 8
src/Avalonia.Controls/ButtonSpinner.cs

@@ -205,17 +205,13 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == ButtonSpinnerLocationProperty)
+            if (change.Property == ButtonSpinnerLocationProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Location>());
             }
         }
 

+ 4 - 8
src/Avalonia.Controls/Calendar/DatePicker.cs

@@ -512,17 +512,13 @@ namespace Avalonia.Controls
             base.OnTemplateApplied(e);
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == SelectedDateProperty)
+            if (change.Property == SelectedDateProperty)
             {
-                DataValidationErrors.SetError(this, newValue.Error);
+                DataValidationErrors.SetError(this, change.NewValue.Error);
             }
         }
 

+ 4 - 8
src/Avalonia.Controls/Expander.cs

@@ -78,17 +78,13 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == ExpandDirectionProperty)
+            if (change.Property == ExpandDirectionProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<ExpandDirection>());
             }
         }
 

+ 4 - 8
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@@ -138,17 +138,13 @@ namespace Avalonia.Controls.Notifications
             notificationControl.Close();
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == PositionProperty)
+            if (change.Property == PositionProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<NotificationPosition>());
             }
         }
 

+ 8 - 12
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -123,24 +123,20 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == OrientationProperty)
+            if (change.Property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
             }
             else
             {
-                if (property == MinimumProperty ||
-                    property == MaximumProperty || 
-                    property == ViewportSizeProperty ||
-                    property == VisibilityProperty)
+                if (change.Property == MinimumProperty ||
+                    change.Property == MaximumProperty ||
+                    change.Property == ViewportSizeProperty ||
+                    change.Property == VisibilityProperty)
                 {
                     UpdateIsVisible();
                 }

+ 4 - 8
src/Avalonia.Controls/Primitives/Track.cs

@@ -280,17 +280,13 @@ namespace Avalonia.Controls.Primitives
             return arrangeSize;
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == OrientationProperty)
+            if (change.Property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 6 - 10
src/Avalonia.Controls/ProgressBar.cs

@@ -83,21 +83,17 @@ namespace Avalonia.Controls
             return base.ArrangeOverride(finalSize);
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == IsIndeterminateProperty)
+            if (change.Property == IsIndeterminateProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>(), null);
             }
-            else if (property == OrientationProperty)
+            else if (change.Property == OrientationProperty)
             {
-                UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
+                UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 16 - 12
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -375,11 +375,11 @@ namespace Avalonia.Controls
             _viewportManager.ResetScrollers();
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            if (property == ItemsProperty)
+            if (change.Property == ItemsProperty)
             {
-                var newEnumerable = newValue.GetValueOrDefault<IEnumerable>();
+                var newEnumerable = change.NewValue.GetValueOrDefault<IEnumerable>();
                 var newDataSource = newEnumerable as ItemsSourceView;
                 if (newEnumerable != null && newDataSource == null)
                 {
@@ -388,24 +388,28 @@ namespace Avalonia.Controls
 
                 OnDataSourcePropertyChanged(ItemsSourceView, newDataSource);
             }
-            else if (property == ItemTemplateProperty)
+            else if (change.Property == ItemTemplateProperty)
             {
-                OnItemTemplateChanged(oldValue.GetValueOrDefault<IDataTemplate>(), newValue.GetValueOrDefault<IDataTemplate>());
+                OnItemTemplateChanged(
+                    change.OldValue.GetValueOrDefault<IDataTemplate>(),
+                    change.NewValue.GetValueOrDefault<IDataTemplate>());
             }
-            else if (property == LayoutProperty)
+            else if (change.Property == LayoutProperty)
             {
-                OnLayoutChanged(oldValue.GetValueOrDefault<AttachedLayout>(), newValue.GetValueOrDefault<AttachedLayout>());
+                OnLayoutChanged(
+                    change.OldValue.GetValueOrDefault<AttachedLayout>(),
+                    change.NewValue.GetValueOrDefault<AttachedLayout>());
             }
-            else if (property == HorizontalCacheLengthProperty)
+            else if (change.Property == HorizontalCacheLengthProperty)
             {
-                _viewportManager.HorizontalCacheLength = newValue.GetValueOrDefault<double>();
+                _viewportManager.HorizontalCacheLength = change.NewValue.GetValueOrDefault<double>();
             }
-            else if (property == VerticalCacheLengthProperty)
+            else if (change.Property == VerticalCacheLengthProperty)
             {
-                _viewportManager.VerticalCacheLength = newValue.GetValueOrDefault<double>();
+                _viewportManager.VerticalCacheLength = change.NewValue.GetValueOrDefault<double>();
             }
 
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
         }
 
         internal IControl GetElementImpl(int index, bool forceCreate, bool supressAutoRecycle)

+ 4 - 8
src/Avalonia.Controls/Slider.cs

@@ -134,17 +134,13 @@ namespace Avalonia.Controls
             }
         }
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == OrientationProperty)
+            if (change.Property == OrientationProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<Orientation>());
             }
         }
 

+ 4 - 8
src/Avalonia.Controls/Window.cs

@@ -645,19 +645,15 @@ namespace Avalonia.Controls
         /// </remarks>
         protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
 
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            if (property == SystemDecorationsProperty)
+            if (change.Property == SystemDecorationsProperty)
             {
-                var typedNewValue = newValue.GetValueOrDefault<SystemDecorations>();
+                var typedNewValue = change.NewValue.GetValueOrDefault<SystemDecorations>();
 
                 PlatformImpl?.SetSystemDecorations(typedNewValue);
 
-                var o = oldValue.GetValueOrDefault<SystemDecorations>() == SystemDecorations.Full;
+                var o = change.OldValue.GetValueOrDefault<SystemDecorations>() == SystemDecorations.Full;
                 var n = typedNewValue == SystemDecorations.Full;
 
                 if (o != n)

+ 6 - 6
src/Avalonia.Input/InputElement.cs

@@ -526,17 +526,17 @@ namespace Avalonia.Input
         {
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == IsFocusedProperty)
+            if (change.Property == IsFocusedProperty)
             {
-                UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
+                UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>(), null);
             }
-            else if (property == IsPointerOverProperty)
+            else if (change.Property == IsPointerOverProperty)
             {
-                UpdatePseudoClasses(null, newValue.GetValueOrDefault<bool>());
+                UpdatePseudoClasses(null, change.NewValue.GetValueOrDefault<bool>());
             }
         }
 

+ 3 - 3
src/Avalonia.Layout/StackLayout.cs

@@ -296,11 +296,11 @@ namespace Avalonia.Layout
             InvalidateLayout();
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            if (property == OrientationProperty)
+            if (change.Property == OrientationProperty)
             {
-                var orientation = newValue.GetValueOrDefault<Orientation>();
+                var orientation = change.NewValue.GetValueOrDefault<Orientation>();
 
                 //Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation.
                 //Horizontal Orientation means we have a Horizontal ScrollOrientation.

+ 17 - 18
src/Avalonia.Layout/UniformGridLayout.cs

@@ -463,45 +463,44 @@ namespace Avalonia.Layout
             gridState.ClearElementOnDataSourceChange(context, args);
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            if (property == OrientationProperty)
+            if (change.Property == OrientationProperty)
             {
-                var orientation = newValue.GetValueOrDefault<Orientation>();
+                var orientation = change.NewValue.GetValueOrDefault<Orientation>();
 
                 //Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation.
                 //i.e. the properties are the inverse of each other.
                 var scrollOrientation = (orientation == Orientation.Horizontal) ? ScrollOrientation.Vertical : ScrollOrientation.Horizontal;
                 _orientation.ScrollOrientation = scrollOrientation;
             }
-            else if (property == MinColumnSpacingProperty)
+            else if (change.Property == MinColumnSpacingProperty)
             {
-                _minColumnSpacing = newValue.GetValueOrDefault<double>();
+                _minColumnSpacing = change.NewValue.GetValueOrDefault<double>();
             }
-            else if (property == MinRowSpacingProperty)
+            else if (change.Property == MinRowSpacingProperty)
             {
-                _minRowSpacing = newValue.GetValueOrDefault<double>();
+                _minRowSpacing = change.NewValue.GetValueOrDefault<double>();
             }
-            else if (property == ItemsJustificationProperty)
+            else if (change.Property == ItemsJustificationProperty)
             {
-                _itemsJustification = newValue.GetValueOrDefault<UniformGridLayoutItemsJustification>();
-                ;
+                _itemsJustification = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsJustification>();
             }
-            else if (property == ItemsStretchProperty)
+            else if (change.Property == ItemsStretchProperty)
             {
-                _itemsStretch = newValue.GetValueOrDefault<UniformGridLayoutItemsStretch>();
+                _itemsStretch = change.NewValue.GetValueOrDefault<UniformGridLayoutItemsStretch>();
             }
-            else if (property == MinItemWidthProperty)
+            else if (change.Property == MinItemWidthProperty)
             {
-                _minItemWidth = newValue.GetValueOrDefault<double>();
+                _minItemWidth = change.NewValue.GetValueOrDefault<double>();
             }
-            else if (property == MinItemHeightProperty)
+            else if (change.Property == MinItemHeightProperty)
             {
-                _minItemHeight = newValue.GetValueOrDefault<double>();
+                _minItemHeight = change.NewValue.GetValueOrDefault<double>();
             }
-            else if (property == MaximumRowsOrColumnsProperty)
+            else if (change.Property == MaximumRowsOrColumnsProperty)
             {
-                _maximumRowsOrColumns = newValue.GetValueOrDefault<int>();
+                _maximumRowsOrColumns = change.NewValue.GetValueOrDefault<int>();
             }
 
             InvalidateLayout();

+ 1 - 1
src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs

@@ -160,7 +160,7 @@ namespace Avalonia.Styling
 
         private void ConvertAndPublishNext(object? value)
         {
-            _value = value is T v ? v : BindingValue<object>.FromUntyped(value).Convert<T>();
+            _value = value is T v ? v : BindingValue<T>.FromUntyped(value);
 
             if (_isActive)
             {

+ 3 - 7
src/Avalonia.Visuals/Media/DrawingImage.cs

@@ -63,15 +63,11 @@ namespace Avalonia.Media
         }
 
         /// <inheritdoc/>
-        protected override void OnPropertyChanged<T>(
-            AvaloniaProperty<T> property,
-            Optional<T> oldValue,
-            BindingValue<T> newValue,
-            BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            base.OnPropertyChanged(change);
 
-            if (property == DrawingProperty)
+            if (change.Property == DrawingProperty)
             {
                 RaiseInvalidated(EventArgs.Empty);
             }

+ 51 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Reactive.Subjects;
+using Avalonia.Data;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
@@ -63,6 +64,56 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
         }
 
+        [Fact]
+        public void GetBaseValue_LocalValue_Ignores_Default_Value()
+        {
+            var target = new Class3();
+
+            target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation);
+            Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).HasValue);
+        }
+
+        [Fact]
+        public void GetBaseValue_LocalValue_Returns_Local_Value()
+        {
+            var target = new Class3();
+
+            target.SetValue(Class1.FooProperty, "local");
+            target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation);
+            Assert.Equal("local", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value);
+        }
+
+        [Fact]
+        public void GetBaseValue_LocalValue_Returns_Style_Value()
+        {
+            var target = new Class3();
+
+            target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
+            target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation);
+            Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.LocalValue).Value);
+        }
+
+        [Fact]
+        public void GetBaseValue_Style_Ignores_LocalValue_Animated_Value()
+        {
+            var target = new Class3();
+
+            target.Bind(Class1.FooProperty, new BehaviorSubject<string>("animated"), BindingPriority.Animation);
+            target.SetValue(Class1.FooProperty, "local");
+            Assert.False(target.GetBaseValue(Class1.FooProperty, BindingPriority.Style).HasValue);
+        }
+
+        [Fact]
+        public void GetBaseValue_Style_Returns_Style_Value()
+        {
+            var target = new Class3();
+
+            target.SetValue(Class1.FooProperty, "local");
+            target.SetValue(Class1.FooProperty, "style", BindingPriority.Style);
+            target.Bind(Class1.FooProperty, new BehaviorSubject<string>("animated"), BindingPriority.Animation);
+            Assert.Equal("style", target.GetBaseValue(Class1.FooProperty, BindingPriority.Style));
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly StyledProperty<string> FooProperty =

+ 142 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_OnPropertyChanged.cs

@@ -0,0 +1,142 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Data;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests
+{
+    public class AvaloniaObjectTests_OnPropertyChanged
+    {
+        [Fact]
+        public void OnPropertyChangedCore_Is_Called_On_Property_Change()
+        {
+            var target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "newvalue");
+
+            Assert.Equal(1, target.CoreChanges.Count);
+
+            var change = (AvaloniaPropertyChangedEventArgs<string>)target.CoreChanges[0];
+
+            Assert.Equal("newvalue", change.NewValue.Value);
+            Assert.Equal("foodefault", change.OldValue.Value);
+            Assert.Equal(BindingPriority.LocalValue, change.Priority);
+            Assert.True(change.IsEffectiveValueChange);
+        }
+
+        [Fact]
+        public void OnPropertyChangedCore_Is_Called_On_Non_Effective_Property_Value_Change()
+        {
+            var target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "newvalue");
+            target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style);
+
+            Assert.Equal(2, target.CoreChanges.Count);
+
+            var change = (AvaloniaPropertyChangedEventArgs<string>)target.CoreChanges[1];
+
+            Assert.Equal("styled", change.NewValue.Value);
+            Assert.False(change.OldValue.HasValue);
+            Assert.Equal(BindingPriority.Style, change.Priority);
+            Assert.False(change.IsEffectiveValueChange);
+        }
+
+        [Fact]
+        public void OnPropertyChangedCore_Is_Called_On_All_Binding_Property_Changes()
+        {
+            var target = new Class1();
+            var style = new Subject<BindingValue<string>>();
+            var animation = new Subject<BindingValue<string>>();
+            var templatedParent = new Subject<BindingValue<string>>();
+
+            target.Bind(Class1.FooProperty, style, BindingPriority.Style);
+            target.Bind(Class1.FooProperty, animation, BindingPriority.Animation);
+            target.Bind(Class1.FooProperty, templatedParent, BindingPriority.TemplatedParent);
+
+            style.OnNext("style1");
+            templatedParent.OnNext("tp1");
+            animation.OnNext("a1");
+            templatedParent.OnNext("tp2");
+            templatedParent.OnCompleted();
+            animation.OnNext("a2");
+            style.OnNext("style2");
+            style.OnCompleted();
+            animation.OnCompleted();
+
+            var changes = target.CoreChanges.Cast<AvaloniaPropertyChangedEventArgs<string>>();
+
+            Assert.Equal(
+                new[] { true, true, true, false, false, true, false, false, true },
+                changes.Select(x => x.IsEffectiveValueChange).ToList());
+            Assert.Equal(
+                new[] { "style1", "tp1", "a1", "tp2", "$unset", "a2", "style2", "$unset", "foodefault" },
+                changes.Select(x => x.NewValue.GetValueOrDefault("$unset")).ToList());
+            Assert.Equal(
+                new[] { "foodefault", "style1", "tp1", "$unset", "$unset", "a1", "$unset", "$unset", "a2" },
+                changes.Select(x => x.OldValue.GetValueOrDefault("$unset")).ToList());
+        }
+
+        [Fact]
+        public void OnPropertyChanged_Is_Called_Only_For_Effective_Value_Changes()
+        {
+            var target = new Class1();
+
+            target.SetValue(Class1.FooProperty, "newvalue");
+            target.SetValue(Class1.FooProperty, "styled", BindingPriority.Style);
+
+            Assert.Equal(1, target.Changes.Count);
+            Assert.Equal(2, target.CoreChanges.Count);
+        }
+
+        private class Class1 : AvaloniaObject
+        {
+            public static readonly StyledProperty<string> FooProperty =
+                AvaloniaProperty.Register<Class1, string>("Foo", "foodefault");
+
+            public Class1()
+            {
+                Changes = new List<AvaloniaPropertyChangedEventArgs>();
+                CoreChanges = new List<AvaloniaPropertyChangedEventArgs>();
+            }
+
+            public List<AvaloniaPropertyChangedEventArgs> Changes { get; }
+            public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; }
+
+            protected override void OnPropertyChangedCore<T>(AvaloniaPropertyChangedEventArgs<T> change)
+            {
+                CoreChanges.Add(Clone(change));
+                base.OnPropertyChangedCore(change);
+            }
+
+            protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+            {
+                Changes.Add(Clone(change));
+                base.OnPropertyChanged(change);
+            }
+
+            private static AvaloniaPropertyChangedEventArgs<T> Clone<T>(AvaloniaPropertyChangedEventArgs<T> change)
+            {
+                var result = new AvaloniaPropertyChangedEventArgs<T>(
+                    change.Sender,
+                    change.Property,
+                    change.OldValue,
+                    change.NewValue,
+                    change.Priority);
+
+                if (!change.IsEffectiveValueChange)
+                {
+                    result.MarkNonEffectiveValue();
+                }
+
+                return result;
+            }
+        }
+    }
+}

+ 19 - 0
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using Avalonia.Data;
 using Avalonia.Utilities;
 using Xunit;
@@ -88,6 +89,19 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("newvalue", value);
         }
 
+        [Fact]
+        public void Changed_Observable_Fired_Only_On_Effective_Value_Change()
+        {
+            var target = new Class1();
+            var result = new List<string>();
+
+            Class1.FooProperty.Changed.Subscribe(x => result.Add((string)x.NewValue));
+            target.SetValue(Class1.FooProperty, "animated", BindingPriority.Animation);
+            target.SetValue(Class1.FooProperty, "local");
+
+            Assert.Equal(new[] { "animated" }, result);
+        }
+
         [Fact]
         public void Property_Equals_Should_Handle_Null()
         {
@@ -144,6 +158,11 @@ namespace Avalonia.Base.UnitTests
                 throw new NotImplementedException();
             }
 
+            internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+            {
+                throw new NotImplementedException();
+            }
+
             internal override void RouteInheritanceParentChanged(AvaloniaObject o, IAvaloniaObject oldParent)
             {
                 throw new NotImplementedException();

+ 90 - 8
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Base.UnitTests
 {
     public class PriorityValueTests
     {
-        private static readonly IValueSink NullSink = Mock.Of<IValueSink>();
+        private static readonly IValueSink NullSink = new MockSink();
         private static readonly IAvaloniaObject Owner = Mock.Of<IAvaloniaObject>();
         private static readonly StyledProperty<string> TestProperty = new StyledProperty<string>(
             "Test",
@@ -30,8 +30,28 @@ namespace Avalonia.Base.UnitTests
                     BindingPriority.StyleTrigger,
                     NullSink));
 
-            Assert.Equal("1", target.Value.Value);
-            Assert.Equal(BindingPriority.StyleTrigger, target.ValuePriority);
+            Assert.Equal("1", target.GetValue().Value);
+            Assert.Equal(BindingPriority.StyleTrigger, target.Priority);
+        }
+
+        [Fact]
+        public void GetValue_Should_Respect_MaxPriority()
+        {
+            var target = new PriorityValue<string>(
+                Owner,
+                TestProperty,
+                NullSink);
+
+            target.SetValue("animation", BindingPriority.Animation);
+            target.SetValue("local", BindingPriority.LocalValue);
+            target.SetValue("styletrigger", BindingPriority.StyleTrigger);
+            target.SetValue("style", BindingPriority.Style);
+
+            Assert.Equal("animation", target.GetValue(BindingPriority.Animation));
+            Assert.Equal("local", target.GetValue(BindingPriority.LocalValue));
+            Assert.Equal("styletrigger", target.GetValue(BindingPriority.StyleTrigger));
+            Assert.Equal("style", target.GetValue(BindingPriority.TemplatedParent));
+            Assert.Equal("style", target.GetValue(BindingPriority.Style));
         }
 
         [Fact]
@@ -61,12 +81,31 @@ namespace Avalonia.Base.UnitTests
 
             var result = target.Entries
                 .OfType<ConstantValueEntry<string>>()
-                .Select(x => x.Value.Value)
+                .Select(x => x.GetValue().Value)
                 .ToList();
 
             Assert.Equal(new[] { "1", "2" }, result);
         }
 
+        [Fact]
+        public void Priority_Should_Be_Set()
+        {
+            var target = new PriorityValue<string>(
+                Owner,
+                TestProperty,
+                NullSink);
+
+            Assert.Equal(BindingPriority.Unset, target.Priority);
+            target.SetValue("style", BindingPriority.Style);
+            Assert.Equal(BindingPriority.Style, target.Priority);
+            target.SetValue("local", BindingPriority.LocalValue);
+            Assert.Equal(BindingPriority.LocalValue, target.Priority);
+            target.SetValue("animation", BindingPriority.Animation);
+            Assert.Equal(BindingPriority.Animation, target.Priority);
+            target.SetValue("local2", BindingPriority.LocalValue);
+            Assert.Equal(BindingPriority.Animation, target.Priority);
+        }
+
         [Fact]
         public void Binding_With_Same_Priority_Should_Be_Appended()
         {
@@ -184,7 +223,7 @@ namespace Avalonia.Base.UnitTests
             target.AddBinding(source2, BindingPriority.Style).Start();
             target.AddBinding(source3, BindingPriority.Style).Start();
 
-            Assert.Equal("1", target.Value.Value);
+            Assert.Equal("1", target.GetValue().Value);
         }
 
         [Fact]
@@ -196,7 +235,7 @@ namespace Avalonia.Base.UnitTests
             target.AddBinding(source1, BindingPriority.LocalValue).Start();
             target.SetValue("2", BindingPriority.LocalValue);
 
-            Assert.Equal("2", target.Value.Value);
+            Assert.Equal("2", target.GetValue().Value);
         }
 
         [Fact]
@@ -208,7 +247,7 @@ namespace Avalonia.Base.UnitTests
             target.AddBinding(source1, BindingPriority.Style).Start();
             target.SetValue("2", BindingPriority.LocalValue);
 
-            Assert.Equal("2", target.Value.Value);
+            Assert.Equal("2", target.GetValue().Value);
         }
 
         [Fact]
@@ -220,7 +259,39 @@ namespace Avalonia.Base.UnitTests
             target.AddBinding(source1, BindingPriority.Animation).Start();
             target.SetValue("2", BindingPriority.LocalValue);
 
-            Assert.Equal("1", target.Value.Value);
+            Assert.Equal("1", target.GetValue().Value);
+        }
+
+        [Fact]
+        public void NonAnimated_Value_Should_Be_Correct_1()
+        {
+            var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
+            var source1 = new Source("1");
+            var source2 = new Source("2");
+            var source3 = new Source("3");
+
+            target.AddBinding(source1, BindingPriority.LocalValue).Start();
+            target.AddBinding(source2, BindingPriority.Style).Start();
+            target.AddBinding(source3, BindingPriority.Animation).Start();
+
+            Assert.Equal("3", target.GetValue().Value);
+            Assert.Equal("1", target.GetValue(BindingPriority.LocalValue).Value);
+        }
+
+        [Fact]
+        public void NonAnimated_Value_Should_Be_Correct_2()
+        {
+            var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
+            var source1 = new Source("1");
+            var source2 = new Source("2");
+            var source3 = new Source("3");
+
+            target.AddBinding(source1, BindingPriority.Animation).Start();
+            target.AddBinding(source2, BindingPriority.Style).Start();
+            target.AddBinding(source3, BindingPriority.Style).Start();
+
+            Assert.Equal("1", target.GetValue().Value);
+            Assert.Equal("3", target.GetValue(BindingPriority.LocalValue).Value);
         }
 
         private class Source : IObservable<BindingValue<string>>
@@ -239,5 +310,16 @@ namespace Avalonia.Base.UnitTests
 
             public void OnCompleted() => _observer.OnCompleted();
         }
+
+        private class MockSink : IValueSink
+        {
+            public void Completed<T>(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
+            {
+            }
+
+            public void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
+            {
+            }
+        }
     }
 }

+ 3 - 3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/InitializationOrderTracker.cs

@@ -18,10 +18,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             base.OnAttachedToLogicalTree(e);
         }
 
-        protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
+        protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
         {
-            Order.Add($"Property {property.Name} Changed");
-            base.OnPropertyChanged(property, oldValue, newValue, priority);
+            Order.Add($"Property {change.Property.Name} Changed");
+            base.OnPropertyChanged(change);
         }
 
         void ISupportInitialize.BeginInit()

+ 4 - 4
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@@ -36,7 +36,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             }
         }
 
-        [Fact]
+        [Fact(Skip = "https://github.com/moq/moq4/issues/988")]
         public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate()
         {
             using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@@ -59,7 +59,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             }
         }
 
-        [Fact]
+        [Fact(Skip = "https://github.com/moq/moq4/issues/988")]
         public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds()
         {
             using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             }
         }
 
-        [Fact]
+        [Fact(Skip = "https://github.com/moq/moq4/issues/988")]
         public void Should_Render_Child_In_Parent_With_RenderTransform()
         {
             using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@@ -145,7 +145,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
             }
         }
 
-        [Fact]
+        [Fact(Skip = "https://github.com/moq/moq4/issues/988")]
         public void Should_Render_Child_In_Parent_With_RenderTransform2()
         {
             using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))