Browse Source

Fix direct properties by delaying setting values until after any currently running property changed notifications for this property finish running.

pull/856/head
Jeremy Koritzinsky 9 years ago
parent
commit
b5ee3077bc
  1. 27
      src/Avalonia.Base/AvaloniaObject.cs
  2. 56
      src/Avalonia.Base/DelayedSetter.cs
  3. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

27
src/Avalonia.Base/AvaloniaObject.cs

@ -510,6 +510,7 @@ namespace Avalonia
{ {
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess(); VerifyAccess();
delayedSetter.SetNotifying(property, true);
AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs( AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs(
this, this,
@ -536,9 +537,12 @@ namespace Avalonia
finally finally
{ {
property.Notifying?.Invoke(this, false); property.Notifying?.Invoke(this, false);
delayedSetter.SetNotifying(property, false);
} }
} }
private DelayedSetter<AvaloniaProperty> delayedSetter = new DelayedSetter<AvaloniaProperty>();
/// <summary> /// <summary>
/// Sets the backing field for a direct avalonia property, raising the /// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed. /// <see cref="PropertyChanged"/> event if the value has changed.
@ -553,15 +557,28 @@ 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 (!object.Equals(field, value)) if (!delayedSetter.IsNotifying(property))
{ {
var old = field; if (!object.Equals(field, value))
field = value; {
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); var old = field;
return true; field = value;
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
if (delayedSetter.HasPendingSet(property))
{
SetAndRaise(property, ref field, (T)delayedSetter.GetFirstPendingSet(property));
}
return true;
}
else
{
return false;
}
} }
else else
{ {
delayedSetter.RecordPendingSet(property, value);
return false; return false;
} }
} }

56
src/Avalonia.Base/DelayedSetter.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia
{
class DelayedSetter<T>
{
private class SettingStatus
{
public bool Notifying { get; set; }
private Queue<object> pendingValues;
public Queue<object> PendingValues
{
get
{
return pendingValues ?? (pendingValues = new Queue<object>());
}
}
}
private readonly Dictionary<T, SettingStatus> setRecords = new Dictionary<T, SettingStatus>();
public void SetNotifying(T property, bool notifying)
{
if (!setRecords.ContainsKey(property))
{
setRecords[property] = new SettingStatus();
}
setRecords[property].Notifying = notifying;
}
public bool IsNotifying(T property) => setRecords.TryGetValue(property, out var value) && value.Notifying;
public void RecordPendingSet(T property, object value)
{
if (!setRecords.ContainsKey(property))
{
setRecords[property] = new SettingStatus();
}
setRecords[property].PendingValues.Enqueue(value);
}
public bool HasPendingSet(T property)
{
return setRecords.ContainsKey(property) && setRecords[property].PendingValues.Count != 0;
}
public object GetFirstPendingSet(T property)
{
return setRecords[property].PendingValues.Dequeue();
}
}
}

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -485,7 +485,7 @@ namespace Avalonia.Base.UnitTests
//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.DoubleValue = 51.001; target.DoubleValue = 51.001;
Assert.Equal(2, viewModel.SetterInvokedCount); Assert.Equal(3, viewModel.SetterInvokedCount);
double expected = 51; double expected = 51;

Loading…
Cancel
Save