@ -10,11 +10,11 @@ namespace Avalonia.Utilities
/// 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 DeferredSetter < TProperty , TValue >
/// <typeparam name="TSetRecord ">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter < TProperty , TSetRecord >
where TProperty : class
{
internal struct NotifyDisposable : IDisposable
private struct NotifyDisposable : IDisposable
{
private readonly SettingStatus status ;
@ -33,17 +33,17 @@ namespace Avalonia.Utilities
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
internal class SettingStatus
private class SettingStatus
{
public bool Notifying { get ; set ; }
private Queue < TValue > pendingValues ;
private Queue < TSetRecord > pendingValues ;
public Queue < TValue > PendingValues
public Queue < TSetRecord > PendingValues
{
get
{
return pendingValues ? ? ( pendingValues = new Queue < TValue > ( ) ) ;
return pendingValues ? ? ( pendingValues = new Queue < TSetRecord > ( ) ) ;
}
}
}
@ -55,7 +55,7 @@ namespace Avalonia.Utilities
/// </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 NotifyDisposable MarkNotifying ( TProperty property )
private NotifyDisposable MarkNotifying ( TProperty property )
{
Contract . Requires < InvalidOperationException > ( ! IsNotifying ( property ) ) ;
@ -67,7 +67,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
internal bool IsNotifying ( TProperty property )
private bool IsNotifying ( TProperty property )
= > setRecords . TryGetValue ( property , out var value ) & & value . Notifying ;
/// <summary>
@ -75,7 +75,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
internal void AddPendingSet ( TProperty property , TValue value )
private void AddPendingSet ( TProperty property , TSetRecord value )
{
Contract . Requires < InvalidOperationException > ( IsNotifying ( property ) ) ;
@ -87,7 +87,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
internal bool HasPendingSet ( TProperty property )
private bool HasPendingSet ( TProperty property )
{
return setRecords . TryGetValue ( property , out var status ) & & status . PendingValues . Count ! = 0 ;
}
@ -97,41 +97,50 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
internal TValue GetFirstPendingSet ( TProperty property )
private TSetRecord GetFirstPendingSet ( TProperty property )
{
return setRecords . GetOrCreateValue ( property ) . PendingValues . Dequeue ( ) ;
}
public delegate bool SetterDelegate < TValue > ( TSetRecord record , ref TValue backing , Action < Action > notifyCallback ) ;
public delegate bool PendingSetPredicate < TValue > ( TSetRecord record , ref TValue backing ) ;
/// <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="backing">The backing field for the property</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 (
public bool SetAndNotify < TValue > (
TProperty property ,
Action < TValue , Action < Action > > setterCallback ,
TValue value ,
Predicate < TValue > pendingSetCondition )
ref TValue backing ,
SetterDelegate < TValue > setterCallback ,
TSetRecord value ,
PendingSetPredicate < TValue > pendingSetCondition )
{
Contract . Requires < ArgumentNullException > ( setterCallback ! = null ) ;
Contract . Requires < ArgumentNullException > ( pendingSetCondition ! = null ) ;
if ( ! IsNotifying ( property ) )
{
setterCallback ( value , notification = >
bool updated = false ;
if ( pendingSetCondition ( value , ref backing ) )
{
using ( MarkNotifying ( property ) )
updated = setterCallback ( value , ref backing , notification = >
{
notification ( ) ;
}
} ) ;
using ( MarkNotifying ( property ) )
{
notification ( ) ;
}
} ) ;
}
while ( HasPendingSet ( property ) )
{
setterCallback ( GetFirstPendingSet ( property ) , notification = >
updated | = setterCallback ( GetFirstPendingSet ( property ) , ref backing , notification = >
{
using ( MarkNotifying ( property ) )
{
@ -139,11 +148,13 @@ namespace Avalonia.Utilities
}
} ) ;
}
return updated ;
}
else if ( pendingSetCondition ( value ) )
else if ( pendingSetCondition ( value , ref backing ) )
{
AddPendingSet ( property , value ) ;
}
return false ;
}
}
}