Browse Source

Fix SelectedItemsControl and RangeBase Stackoverflow bugs.

pull/856/head
Jeremy Koritzinsky 9 years ago
parent
commit
440f2cafc5
  1. 26
      src/Avalonia.Base/AvaloniaObject.cs
  2. 44
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  3. 2
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

26
src/Avalonia.Base/AvaloniaObject.cs

@ -54,7 +54,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Delayed setter helper for direct properties. Used to fix #855. /// Delayed setter helper for direct properties. Used to fix #855.
/// </summary> /// </summary>
protected readonly DelayedSetter<AvaloniaProperty, object> directDelayedSetter = new DelayedSetter<AvaloniaProperty, object>(); private readonly DelayedSetter<AvaloniaProperty, object> directDelayedSetter = new DelayedSetter<AvaloniaProperty, object>();
/// <summary> /// <summary>
@ -589,6 +589,30 @@ namespace Avalonia
} }
} }
protected void SetAndRaise<T>(AvaloniaProperty<T> property, Action<T, Action<Action>> setterCallback, T value, Action<T> delayedSet)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
Contract.Requires<ArgumentNullException>(delayedSet != null);
if (!directDelayedSetter.IsNotifying(property))
{
setterCallback(value, notification =>
{
using (directDelayedSetter.MarkNotifying(property))
{
notification();
}
});
if (directDelayedSetter.HasPendingSet(property))
{
delayedSet((T)directDelayedSetter.GetFirstPendingSet(property));
}
}
else
{
directDelayedSetter.AddPendingSet(property, value);
}
}
/// <summary> /// <summary>
/// Tries to cast a value to a type, taking into account that the value may be a /// Tries to cast a value to a type, taking into account that the value may be a
/// <see cref="BindingNotification"/>. /// <see cref="BindingNotification"/>.

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

@ -151,15 +151,18 @@ namespace Avalonia.Controls.Primitives
{ {
if (_updateCount == 0) if (_updateCount == 0)
{ {
var old = SelectedIndex; SetAndRaise(SelectedIndexProperty, (val, notifierWrapper) =>
var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
if (old != effective)
{ {
_selectedIndex = effective; var old = SelectedIndex;
RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue); var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
SelectedItem = ElementAt(Items, effective);
} if (old != effective)
{
_selectedIndex = effective;
notifierWrapper(() => RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue));
SelectedItem = ElementAt(Items, effective);
}
}, value, val => SelectedIndex = val);
} }
else else
{ {
@ -183,19 +186,17 @@ namespace Avalonia.Controls.Primitives
{ {
if (_updateCount == 0) if (_updateCount == 0)
{ {
if (!directDelayedSetter.IsNotifying(SelectedItemProperty)) SetAndRaise(SelectedItemProperty, (val, notifyWrapper) =>
{ {
var old = SelectedItem; var old = SelectedItem;
var index = IndexOf(Items, value); var index = IndexOf(Items, val);
var effective = index != -1 ? value : null; var effective = index != -1 ? val : null;
if (!object.Equals(effective, old)) if (!object.Equals(effective, old))
{ {
_selectedItem = effective; _selectedItem = effective;
using (directDelayedSetter.MarkNotifying(SelectedItemProperty))
{ notifyWrapper(() => RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue));
RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue);
}
SelectedIndex = index; SelectedIndex = index;
@ -213,17 +214,8 @@ namespace Avalonia.Controls.Primitives
{ {
SelectedItems.Clear(); SelectedItems.Clear();
} }
}
if (directDelayedSetter.HasPendingSet(SelectedItemProperty)) }, value, val => SelectedItem = val);
{
SelectedItem = directDelayedSetter.GetFirstPendingSet(SelectedItemProperty);
}
}
}
else
{
directDelayedSetter.AddPendingSet(SelectedItemProperty, value);
}
} }
else else
{ {

2
tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs

@ -151,7 +151,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
//here in real life stack overflow exception is thrown issue #855 and #824 //here in real life stack overflow exception is thrown issue #855 and #824
target.Value = 51.001; target.Value = 51.001;
Assert.Equal(2, viewModel.SetterInvokedCount); Assert.Equal(3, viewModel.SetterInvokedCount);
double expected = 51; double expected = 51;

Loading…
Cancel
Save