diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 12b7e3d529..8506414289 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -51,10 +51,19 @@ namespace Avalonia /// private EventHandler _propertyChanged; + private DeferredSetter _directDeferredSetter; + /// /// Delayed setter helper for direct properties. Used to fix #855. /// - private readonly DeferredSetter directDelayedSetter = new DeferredSetter(); + private DeferredSetter DirectDelayedSetter + { + get + { + return _directDeferredSetter ?? + (_directDeferredSetter = new DeferredSetter()); + } + } /// @@ -559,7 +568,7 @@ namespace Avalonia protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value) { VerifyAccess(); - if (!directDelayedSetter.IsNotifying(property)) + if (!DirectDelayedSetter.IsNotifying(property)) { var valueChanged = false; if (!object.Equals(field, value)) @@ -569,16 +578,16 @@ namespace Avalonia valueChanged = true; } - while (directDelayedSetter.HasPendingSet(property)) + while (DirectDelayedSetter.HasPendingSet(property)) { - SetAndRaiseCore(property, ref field, (T)directDelayedSetter.GetFirstPendingSet(property)); + SetAndRaiseCore(property, ref field, (T)DirectDelayedSetter.GetFirstPendingSet(property)); valueChanged = true; } return valueChanged; } else if(!object.Equals(field, value)) { - directDelayedSetter.AddPendingSet(property, value); + DirectDelayedSetter.AddPendingSet(property, value); } return false; } @@ -588,7 +597,7 @@ namespace Avalonia var old = field; field = value; - using (directDelayedSetter.MarkNotifying(property)) + using (DirectDelayedSetter.MarkNotifying(property)) { RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); } @@ -598,14 +607,15 @@ namespace Avalonia AvaloniaProperty property, Action> setterCallback, T value, - Predicate pendingSetCondition = null) + Predicate pendingSetCondition) { Contract.Requires(setterCallback != null); - directDelayedSetter.SetAndNotify( + Contract.Requires(pendingSetCondition != null); + DirectDelayedSetter.SetAndNotify( property, (val, notify) => setterCallback((T)val, notify), value, - pendingSetCondition != null ? o => pendingSetCondition((T)o) : (Predicate)null); + o => pendingSetCondition((T)o)); } /// diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 5d223c3cf8..ea10427b10 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -28,9 +28,10 @@ namespace Avalonia { private readonly Type _valueType; private readonly SingleOrDictionary _levels = new SingleOrDictionary(); - private object _value; + private readonly Func _validate; private static readonly DeferredSetter delayedSetter = new DeferredSetter(); + private (object value, int priority) _value; /// /// Initializes a new instance of the class. @@ -47,9 +48,7 @@ namespace Avalonia { Owner = owner; Property = property; - _valueType = valueType; - _value = AvaloniaProperty.UnsetValue; - ValuePriority = int.MaxValue; + _value = (AvaloniaProperty.UnsetValue, int.MaxValue); _validate = validate; } @@ -66,16 +65,12 @@ namespace Avalonia /// /// Gets the current value. /// - public object Value => _value; + public object Value => _value.value; /// /// Gets the priority of the binding that is currently active. /// - public int ValuePriority - { - get; - private set; - } + public int ValuePriority => _value.priority; /// /// Adds a new binding. @@ -238,7 +233,7 @@ namespace Avalonia delayedSetter.SetAndNotify(this, UpdateCore, (value, priority), - val => !object.Equals(val.value, _value)); + val => !object.Equals(val.value, val.value)); } private void UpdateCore((object value, int priority) update, Action notify) @@ -254,15 +249,14 @@ namespace Avalonia if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) { - var old = _value; + var old = this._value.value; if (_validate != null && castValue != AvaloniaProperty.UnsetValue) { castValue = _validate(castValue); } - ValuePriority = update.priority; - _value = castValue; + this._value = (castValue, update.priority); if (notification?.HasValue == true) { @@ -271,7 +265,7 @@ namespace Avalonia if (notification == null || notification.HasValue) { - notify(() => Owner?.Changed(this, old, _value)); + notify(() => Owner?.Changed(this, old, Value)); } if (notification != null) diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index b87711c1d9..b42ca0bf3d 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -14,11 +14,11 @@ namespace Avalonia.Utilities class DeferredSetter where TProperty: class { - private struct NotifyDisposable : IDisposable + internal struct NotifyDisposable : IDisposable { private readonly SettingStatus status; - public NotifyDisposable(SettingStatus status) + internal NotifyDisposable(SettingStatus status) { this.status = status; status.Notifying = true; @@ -33,7 +33,7 @@ namespace Avalonia.Utilities /// /// Information on current setting/notification status of a property. /// - private class SettingStatus + internal class SettingStatus { public bool Notifying { get; set; } @@ -55,7 +55,7 @@ namespace Avalonia.Utilities /// /// The property to mark as notifying. /// Returns a disposable that when disposed, marks the property as done notifying. - internal IDisposable MarkNotifying(TProperty property) + internal NotifyDisposable MarkNotifying(TProperty property) { Contract.Requires(!IsNotifying(property)); @@ -119,6 +119,7 @@ namespace Avalonia.Utilities Predicate pendingSetCondition) { Contract.Requires(setterCallback != null); + Contract.Requires(pendingSetCondition != null); if (!IsNotifying(property)) { setterCallback(value, notification => @@ -139,7 +140,7 @@ namespace Avalonia.Utilities }); } } - else if(pendingSetCondition?.Invoke(value) ?? true) + else if(pendingSetCondition(value)) { AddPendingSet(property, value); } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 1a401c82bc..3d34bae7eb 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -151,7 +151,7 @@ namespace Avalonia.Controls.Primitives { if (_updateCount == 0) { - SetAndRaise(SelectedIndexProperty, (val, notifierWrapper) => + SetAndRaise(SelectedIndexProperty, (val, notifyWrapper) => { var old = SelectedIndex; var effective = (val >= 0 && val < Items?.Cast().Count()) ? val : -1; @@ -159,10 +159,15 @@ namespace Avalonia.Controls.Primitives if (old != effective) { _selectedIndex = effective; - notifierWrapper(() => RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue)); + notifyWrapper(() => + RaisePropertyChanged( + SelectedIndexProperty, + old, + effective, + BindingPriority.LocalValue)); SelectedItem = ElementAt(Items, effective); } - }, value); + }, value, val => val != SelectedIndex); } else { @@ -196,7 +201,12 @@ namespace Avalonia.Controls.Primitives { _selectedItem = effective; - notifyWrapper(() => RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue)); + notifyWrapper(() => + RaisePropertyChanged( + SelectedItemProperty, + old, + effective, + BindingPriority.LocalValue)); SelectedIndex = index; @@ -215,7 +225,7 @@ namespace Avalonia.Controls.Primitives SelectedItems.Clear(); } } - }, value); + }, value, val => val != SelectedItem); } else { diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index d95acbdb7f..fee4994ee3 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -223,7 +223,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, viewModel.SetterInvokedCount); - //here in real life stack overflow exception is thrown issue #855 + // In Issue #855, a Stackoverflow occured here. target.SelectedItem = viewModel.Items[2]; Assert.Equal(viewModel.Items[1], target.SelectedItem);