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);
VerifyAccess();
delayedSetter.SetNotifying(property, true);
AvaloniaPropertyChangedEventArgs e = new AvaloniaPropertyChangedEventArgs(
this,
@ -536,9 +537,12 @@ namespace Avalonia
finally
{
property.Notifying?.Invoke(this, false);
delayedSetter.SetNotifying(property, false);
}
}
private DelayedSetter<AvaloniaProperty> delayedSetter = new DelayedSetter<AvaloniaProperty>();
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <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)
{
VerifyAccess();
if (!object.Equals(field, value))
if (!delayedSetter.IsNotifying(property))
{
var old = field;
field = value;
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
return true;
if (!object.Equals(field, value))
{
var old = field;
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
{
delayedSetter.RecordPendingSet(property, value);
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
target.DoubleValue = 51.001;
Assert.Equal(2, viewModel.SetterInvokedCount);
Assert.Equal(3, viewModel.SetterInvokedCount);
double expected = 51;

Loading…
Cancel
Save