Browse Source

PR Feedback.

pull/856/head
Jeremy Koritzinsky 9 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> /// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged; private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
/// <summary> /// <summary>
/// Delayed setter helper for direct properties. Used to fix #855. /// Delayed setter helper for direct properties. Used to fix #855.
/// </summary> /// </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> /// <summary>
@ -559,7 +568,7 @@ namespace Avalonia
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value) protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
{ {
VerifyAccess(); VerifyAccess();
if (!directDelayedSetter.IsNotifying(property)) if (!DirectDelayedSetter.IsNotifying(property))
{ {
var valueChanged = false; var valueChanged = false;
if (!object.Equals(field, value)) if (!object.Equals(field, value))
@ -569,16 +578,16 @@ namespace Avalonia
valueChanged = true; 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; valueChanged = true;
} }
return valueChanged; return valueChanged;
} }
else if(!object.Equals(field, value)) else if(!object.Equals(field, value))
{ {
directDelayedSetter.AddPendingSet(property, value); DirectDelayedSetter.AddPendingSet(property, value);
} }
return false; return false;
} }
@ -588,7 +597,7 @@ namespace Avalonia
var old = field; var old = field;
field = value; field = value;
using (directDelayedSetter.MarkNotifying(property)) using (DirectDelayedSetter.MarkNotifying(property))
{ {
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
} }
@ -598,14 +607,15 @@ namespace Avalonia
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
Action<T, Action<Action>> setterCallback, Action<T, Action<Action>> setterCallback,
T value, T value,
Predicate<T> pendingSetCondition = null) Predicate<T> pendingSetCondition)
{ {
Contract.Requires<ArgumentNullException>(setterCallback != null); Contract.Requires<ArgumentNullException>(setterCallback != null);
directDelayedSetter.SetAndNotify( Contract.Requires<ArgumentNullException>(pendingSetCondition != null);
DirectDelayedSetter.SetAndNotify(
property, property,
(val, notify) => setterCallback((T)val, notify), (val, notify) => setterCallback((T)val, notify),
value, value,
pendingSetCondition != null ? o => pendingSetCondition((T)o) : (Predicate<object>)null); o => pendingSetCondition((T)o));
} }
/// <summary> /// <summary>

24
src/Avalonia.Base/PriorityValue.cs

@ -28,9 +28,10 @@ namespace Avalonia
{ {
private readonly Type _valueType; private readonly Type _valueType;
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>(); private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private object _value;
private readonly Func<object, object> _validate; private readonly Func<object, object> _validate;
private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>(); private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
private (object value, int priority) _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
@ -47,9 +48,7 @@ namespace Avalonia
{ {
Owner = owner; Owner = owner;
Property = property; Property = property;
_valueType = valueType; _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_value = AvaloniaProperty.UnsetValue;
ValuePriority = int.MaxValue;
_validate = validate; _validate = validate;
} }
@ -66,16 +65,12 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the current value. /// Gets the current value.
/// </summary> /// </summary>
public object Value => _value; public object Value => _value.value;
/// <summary> /// <summary>
/// Gets the priority of the binding that is currently active. /// Gets the priority of the binding that is currently active.
/// </summary> /// </summary>
public int ValuePriority public int ValuePriority => _value.priority;
{
get;
private set;
}
/// <summary> /// <summary>
/// Adds a new binding. /// Adds a new binding.
@ -238,7 +233,7 @@ namespace Avalonia
delayedSetter.SetAndNotify(this, delayedSetter.SetAndNotify(this,
UpdateCore, UpdateCore,
(value, priority), (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) private void UpdateCore((object value, int priority) update, Action<Action> notify)
@ -254,15 +249,14 @@ namespace Avalonia
if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue)) if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
{ {
var old = _value; var old = this._value.value;
if (_validate != null && castValue != AvaloniaProperty.UnsetValue) if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{ {
castValue = _validate(castValue); castValue = _validate(castValue);
} }
ValuePriority = update.priority; this._value = (castValue, update.priority);
_value = castValue;
if (notification?.HasValue == true) if (notification?.HasValue == true)
{ {
@ -271,7 +265,7 @@ namespace Avalonia
if (notification == null || notification.HasValue) if (notification == null || notification.HasValue)
{ {
notify(() => Owner?.Changed(this, old, _value)); notify(() => Owner?.Changed(this, old, Value));
} }
if (notification != null) if (notification != null)

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

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

20
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -151,7 +151,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (_updateCount == 0) if (_updateCount == 0)
{ {
SetAndRaise(SelectedIndexProperty, (val, notifierWrapper) => SetAndRaise(SelectedIndexProperty, (val, notifyWrapper) =>
{ {
var old = SelectedIndex; var old = SelectedIndex;
var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1; var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
@ -159,10 +159,15 @@ namespace Avalonia.Controls.Primitives
if (old != effective) if (old != effective)
{ {
_selectedIndex = effective; _selectedIndex = effective;
notifierWrapper(() => RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue)); notifyWrapper(() =>
RaisePropertyChanged(
SelectedIndexProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedItem = ElementAt(Items, effective); SelectedItem = ElementAt(Items, effective);
} }
}, value); }, value, val => val != SelectedIndex);
} }
else else
{ {
@ -196,7 +201,12 @@ namespace Avalonia.Controls.Primitives
{ {
_selectedItem = effective; _selectedItem = effective;
notifyWrapper(() => RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue)); notifyWrapper(() =>
RaisePropertyChanged(
SelectedItemProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedIndex = index; SelectedIndex = index;
@ -215,7 +225,7 @@ namespace Avalonia.Controls.Primitives
SelectedItems.Clear(); SelectedItems.Clear();
} }
} }
}, value); }, value, val => val != SelectedItem);
} }
else else
{ {

2
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -223,7 +223,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, viewModel.SetterInvokedCount); 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]; target.SelectedItem = viewModel.Items[2];
Assert.Equal(viewModel.Items[1], target.SelectedItem); Assert.Equal(viewModel.Items[1], target.SelectedItem);

Loading…
Cancel
Save