Browse Source

PR Feedback.

pull/856/head
Jeremy Koritzinsky 8 years ago
parent
commit
ca9a4c4128
  1. 28
      src/Avalonia.Base/AvaloniaObject.cs
  2. 24
      src/Avalonia.Base/PriorityValue.cs
  3. 11
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  4. 20
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  5. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

28
src/Avalonia.Base/AvaloniaObject.cs

@ -51,10 +51,19 @@ namespace Avalonia
/// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
/// <summary>
/// Delayed setter helper for direct properties. Used to fix #855.
/// </summary>
private readonly DeferredSetter<AvaloniaProperty, object> directDelayedSetter = new DeferredSetter<AvaloniaProperty, object>();
private DeferredSetter<AvaloniaProperty, object> DirectDelayedSetter
{
get
{
return _directDeferredSetter ??
(_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
}
}
/// <summary>
@ -559,7 +568,7 @@ namespace Avalonia
protected bool SetAndRaise<T>(AvaloniaProperty<T> 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<T> property,
Action<T, Action<Action>> setterCallback,
T value,
Predicate<T> pendingSetCondition = null)
Predicate<T> pendingSetCondition)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
directDelayedSetter.SetAndNotify(
Contract.Requires<ArgumentNullException>(pendingSetCondition != null);
DirectDelayedSetter.SetAndNotify(
property,
(val, notify) => setterCallback((T)val, notify),
value,
pendingSetCondition != null ? o => pendingSetCondition((T)o) : (Predicate<object>)null);
o => pendingSetCondition((T)o));
}
/// <summary>

24
src/Avalonia.Base/PriorityValue.cs

@ -28,9 +28,10 @@ namespace Avalonia
{
private readonly Type _valueType;
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private object _value;
private readonly Func<object, object> _validate;
private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
private (object value, int priority) _value;
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> 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
/// <summary>
/// Gets the current value.
/// </summary>
public object Value => _value;
public object Value => _value.value;
/// <summary>
/// Gets the priority of the binding that is currently active.
/// </summary>
public int ValuePriority
{
get;
private set;
}
public int ValuePriority => _value.priority;
/// <summary>
/// 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<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)

11
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -14,11 +14,11 @@ namespace Avalonia.Utilities
class DeferredSetter<TProperty, TValue>
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
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
private class SettingStatus
internal class SettingStatus
{
public bool Notifying { get; set; }
@ -55,7 +55,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property to mark as notifying.</param>
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
internal IDisposable MarkNotifying(TProperty property)
internal NotifyDisposable MarkNotifying(TProperty property)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
@ -119,6 +119,7 @@ namespace Avalonia.Utilities
Predicate<TValue> pendingSetCondition)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
Contract.Requires<ArgumentNullException>(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);
}

20
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<object>().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
{

2
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);

Loading…
Cancel
Save