From 27daffeb3e963c895f70f4387d71a5331b41b36c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 26 Jun 2018 02:30:47 +0200 Subject: [PATCH] Abstracted storage of styled values into ValueStore. --- src/Avalonia.Base/AvaloniaObject.cs | 124 +++------------- src/Avalonia.Base/IPriorityValueOwner.cs | 9 +- src/Avalonia.Base/PriorityValue.cs | 4 +- src/Avalonia.Base/ValueStore.cs | 134 ++++++++++++++++++ .../PriorityValueTests.cs | 4 +- 5 files changed, 161 insertions(+), 114 deletions(-) create mode 100644 src/Avalonia.Base/ValueStore.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index baca1494be..1b7bd91a8f 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -32,8 +32,7 @@ namespace Avalonia /// /// The set values/bindings on this object. /// - private readonly Dictionary _values = - new Dictionary(); + private readonly ValueStore _values; /// /// Maintains a list of direct property binding subscriptions so that the binding source @@ -73,6 +72,8 @@ namespace Avalonia { VerifyAccess(); + _values = new ValueStore(this); + void Notify(AvaloniaProperty property) { object value = property.IsDirect ? @@ -230,7 +231,7 @@ namespace Avalonia } else { - return GetValueInternal(property); + return _values.GetValue(property); } } @@ -257,7 +258,7 @@ namespace Avalonia Contract.Requires(property != null); VerifyAccess(); - return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false; + return _values.IsAnimating(property); } /// @@ -274,14 +275,7 @@ namespace Avalonia Contract.Requires(property != null); VerifyAccess(); - PriorityValue value; - - if (_values.TryGetValue(property, out value)) - { - return value.Value != AvaloniaProperty.UnsetValue; - } - - return false; + return _values.IsSet(property); } /// @@ -369,14 +363,6 @@ namespace Avalonia } else { - PriorityValue v; - - if (!_values.TryGetValue(property, out v)) - { - v = CreatePriorityValue(property); - _values.Add(property, v); - } - Logger.Verbose( LogArea.Property, this, @@ -385,7 +371,7 @@ namespace Avalonia description, priority); - return v.Add(source, (int)priority); + return _values.AddBinding(property, source, priority); } } @@ -416,20 +402,12 @@ namespace Avalonia public void Revalidate(AvaloniaProperty property) { VerifyAccess(); - PriorityValue value; - - if (_values.TryGetValue(property, out value)) - { - value.Revalidate(); - } + _values.Revalidate(property); } /// - void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue) + void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) { - var property = sender.Property; - var priority = (BindingPriority)sender.ValuePriority; - oldValue = (oldValue == AvaloniaProperty.UnsetValue) ? GetDefaultValue(property) : oldValue; @@ -439,7 +417,7 @@ namespace Avalonia if (!Equals(oldValue, newValue)) { - RaisePropertyChanged(property, oldValue, newValue, priority); + RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); Logger.Verbose( LogArea.Property, @@ -448,14 +426,14 @@ namespace Avalonia property, oldValue, newValue, - priority); + (BindingPriority)priority); } } /// - void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification) + void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) { - UpdateDataValidation(sender.Property, notification); + UpdateDataValidation(property, notification); } /// @@ -468,10 +446,7 @@ namespace Avalonia /// Gets all priority values set on the object. /// /// A collection of property/value tuples. - internal IDictionary GetSetValues() - { - return _values; - } + internal IDictionary GetSetValues() => _values.GetSetValues(); /// /// Forces revalidation of properties when a property value changes. @@ -660,68 +635,18 @@ namespace Avalonia } } - /// - /// Creates a for a . - /// - /// The property. - /// The . - private PriorityValue CreatePriorityValue(AvaloniaProperty property) - { - var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType()); - Func validate2 = null; - - if (validate != null) - { - validate2 = v => validate(this, v); - } - - PriorityValue result = new PriorityValue( - this, - property, - property.PropertyType, - validate2); - - return result; - } - /// /// Gets the default value for a property. /// /// The property. /// The default value. - private object GetDefaultValue(AvaloniaProperty property) + internal object GetDefaultValue(AvaloniaProperty property) { if (property.Inherits && InheritanceParent is AvaloniaObject aobj) - return aobj.GetValueInternal(property); + return aobj._values.GetValue(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } - /// - /// Gets a value - /// without check for registered as this can slow getting the value - /// this method is intended for internal usage in AvaloniaObject only - /// it's called only after check the property is registered - /// - /// The property. - /// The value. - private object GetValueInternal(AvaloniaProperty property) - { - object result = AvaloniaProperty.UnsetValue; - PriorityValue value; - - if (_values.TryGetValue(property, out value)) - { - result = value.Value; - } - - if (result == AvaloniaProperty.UnsetValue) - { - result = GetDefaultValue(property); - } - - return result; - } - /// /// Sets the value of a direct property. /// @@ -801,22 +726,9 @@ namespace Avalonia originalValue, originalValue?.GetType().FullName ?? "(null)")); } - - PriorityValue v; - - if (!_values.TryGetValue(property, out v)) - { - if (value == AvaloniaProperty.UnsetValue) - { - return; - } - - v = CreatePriorityValue(property); - _values.Add(property, v); - } - + LogPropertySet(property, value, priority); - v.SetValue(value, (int)priority); + _values.AddValue(property, value, (int)priority); } /// diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs index aeec720920..5f63f6ef91 100644 --- a/src/Avalonia.Base/IPriorityValueOwner.cs +++ b/src/Avalonia.Base/IPriorityValueOwner.cs @@ -13,18 +13,19 @@ namespace Avalonia /// /// Called when a 's value changes. /// - /// The source of the change. + /// The the property that has changed. + /// The priority of the value. /// The old value. /// The new value. - void Changed(PriorityValue sender, object oldValue, object newValue); + void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue); /// /// Called when a is received by a /// . /// - /// The source of the change. + /// The the property that has changed. /// The notification. - void BindingNotificationReceived(PriorityValue sender, BindingNotification notification); + void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification); /// /// Ensures that the current thread is the UI thread. diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 9b5318083a..c474f9098e 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -281,12 +281,12 @@ namespace Avalonia if (notification == null || notification.HasValue) { - notify(() => Owner?.Changed(this, old, Value)); + notify(() => Owner?.Changed(Property, ValuePriority, old, Value)); } if (notification != null) { - Owner?.BindingNotificationReceived(this, notification); + Owner?.BindingNotificationReceived(Property, notification); } } else diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs new file mode 100644 index 0000000000..562cc165df --- /dev/null +++ b/src/Avalonia.Base/ValueStore.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Data; +using Avalonia.Utilities; + +namespace Avalonia +{ + internal class ValueStore : IPriorityValueOwner + { + private readonly AvaloniaObject _owner; + private readonly Dictionary _values = + new Dictionary(); + + public ValueStore(AvaloniaObject owner) + { + _owner = owner; + } + + public IDisposable AddBinding( + AvaloniaProperty property, + IObservable source, + BindingPriority priority) + { + if (!_values.TryGetValue(property, out PriorityValue v)) + { + v = CreatePriorityValue(property); + _values.Add(property, v); + } + + return v.Add(source, (int)priority); + } + + public void AddValue(AvaloniaProperty property, object value, int priority) + { + var originalValue = value; + + if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value)) + { + throw new ArgumentException(string.Format( + "Invalid value for Property '{0}': '{1}' ({2})", + property.Name, + originalValue, + originalValue?.GetType().FullName ?? "(null)")); + } + + if (!_values.TryGetValue(property, out PriorityValue v)) + { + if (value == AvaloniaProperty.UnsetValue) + { + return; + } + + v = CreatePriorityValue(property); + _values.Add(property, v); + } + + v.SetValue(value, priority); + } + + public IDictionary GetSetValues() => _values; + + public object GetValue(AvaloniaProperty property) + { + var result = AvaloniaProperty.UnsetValue; + + if (_values.TryGetValue(property, out PriorityValue value)) + { + result = value.Value; + } + + if (result == AvaloniaProperty.UnsetValue) + { + result = _owner.GetDefaultValue(property); + } + + return result; + } + + public bool IsAnimating(AvaloniaProperty property) + { + return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false; + } + + public bool IsSet(AvaloniaProperty property) + { + if (_values.TryGetValue(property, out PriorityValue value)) + { + return value.Value != AvaloniaProperty.UnsetValue; + } + + return false; + } + + public void Revalidate(AvaloniaProperty property) + { + if (_values.TryGetValue(property, out PriorityValue value)) + { + value.Revalidate(); + } + } + + void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) + { + ((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); + } + + void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) + { + ((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); + } + + void IPriorityValueOwner.VerifyAccess() => _owner.VerifyAccess(); + + private PriorityValue CreatePriorityValue(AvaloniaProperty property) + { + var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); + Func validate2 = null; + + if (validate != null) + { + validate2 = v => validate(_owner, v); + } + + PriorityValue result = new PriorityValue( + this, + property, + property.PropertyType, + validate2); + + return result; + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs index 6ca1364f96..d6650aa104 100644 --- a/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs +++ b/tests/Avalonia.Base.UnitTests/PriorityValueTests.cs @@ -167,7 +167,7 @@ namespace Avalonia.Base.UnitTests target.Add(Single("foo"), 0); - owner.Verify(x => x.Changed(target, AvaloniaProperty.UnsetValue, "foo")); + owner.Verify(x => x.Changed(target.Property, target.ValuePriority, AvaloniaProperty.UnsetValue, "foo")); } [Fact] @@ -180,7 +180,7 @@ namespace Avalonia.Base.UnitTests target.Add(subject, 0); subject.OnNext("bar"); - owner.Verify(x => x.Changed(target, "foo", "bar")); + owner.Verify(x => x.Changed(target.Property, target.ValuePriority, "foo", "bar")); } [Fact]