Browse Source

Refactor AvaloniaObject.SetAndRaise and comment DelayedSetter.

pull/856/head
Jeremy Koritzinsky 9 years ago
parent
commit
d775d2384e
  1. 14
      src/Avalonia.Base/AvaloniaObject.cs
  2. 61
      src/Avalonia.Base/Utilities/DelayedSetter.cs

14
src/Avalonia.Base/AvaloniaObject.cs

@ -561,16 +561,20 @@ namespace Avalonia
VerifyAccess();
if (!directDelayedSetter.IsNotifying(property))
{
var valueChanged = false;
if (!object.Equals(field, value))
{
SetAndRaiseCore(property, ref field, value);
while (directDelayedSetter.HasPendingSet(property))
{
SetAndRaiseCore(property, ref field, (T)directDelayedSetter.GetFirstPendingSet(property));
}
return true;
valueChanged = true;
}
while (directDelayedSetter.HasPendingSet(property))
{
SetAndRaiseCore(property, ref field, (T)directDelayedSetter.GetFirstPendingSet(property));
valueChanged = true;
}
return valueChanged;
}
else if(!object.Equals(field, value))
{

61
src/Avalonia.Base/Utilities/DelayedSetter.cs

@ -5,8 +5,16 @@ using System.Text;
namespace Avalonia.Utilities
{
class DelayedSetter<T, TValue>
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// </summary>
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TValue">The type of value with which to track the delayed assignment.</typeparam>
class DelayedSetter<TProperty, TValue>
{
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
private class SettingStatus
{
public bool Notifying { get; set; }
@ -22,9 +30,14 @@ namespace Avalonia.Utilities
}
}
private readonly Dictionary<T, SettingStatus> setRecords = new Dictionary<T, SettingStatus>();
private readonly Dictionary<TProperty, SettingStatus> setRecords = new Dictionary<TProperty, SettingStatus>();
public IDisposable MarkNotifying(T property)
/// <summary>
/// Mark the property as currently notifying.
/// </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)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
@ -37,10 +50,22 @@ namespace Avalonia.Utilities
return Disposable.Create(() => setRecords[property].Notifying = false);
}
public bool IsNotifying(T property) => setRecords.TryGetValue(property, out var value) && value.Notifying;
/// <summary>
/// Check if the property is currently notifying listeners.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
internal bool IsNotifying(TProperty property)
=> setRecords.TryGetValue(property, out var value) && value.Notifying;
public void AddPendingSet(T property, TValue value)
/// <summary>
/// Add a pending assignment for the property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
internal void AddPendingSet(TProperty property, TValue value)
{
Contract.Requires<InvalidOperationException>(IsNotifying(property));
if (!setRecords.ContainsKey(property))
{
setRecords[property] = new SettingStatus();
@ -48,17 +73,37 @@ namespace Avalonia.Utilities
setRecords[property].PendingValues.Enqueue(value);
}
public bool HasPendingSet(T property)
/// <summary>
/// Checks if there are any pending assignments for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
internal bool HasPendingSet(TProperty property)
{
return setRecords.ContainsKey(property) && setRecords[property].PendingValues.Count != 0;
}
public TValue GetFirstPendingSet(T property)
/// <summary>
/// Gets the first pending assignment for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
internal TValue GetFirstPendingSet(TProperty property)
{
return setRecords[property].PendingValues.Dequeue();
}
public void SetAndNotify(T property, Action<TValue, Action<Action>> setterCallback, TValue value, Predicate<TValue> pendingSetCondition)
/// <summary>
/// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="setterCallback">
/// A callback that actually sets the property.
/// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
/// </param>
/// <param name="value">The value to try to set.</param>
/// <param name="pendingSetCondition">A predicate to filter what possible values should be added as pending sets (i.e. only values not equal to the current value).</param>
public void SetAndNotify(TProperty property, Action<TValue, Action<Action>> setterCallback, TValue value, Predicate<TValue> pendingSetCondition)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
if (!IsNotifying(property))

Loading…
Cancel
Save