@ -10,10 +10,8 @@ namespace Avalonia.Utilities
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
/// </summary>
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter < TProperty , TSetRecord >
where TProperty : class
class DeferredSetter < TSetRecord >
{
private struct NotifyDisposable : IDisposable
{
@ -47,20 +45,37 @@ namespace Avalonia.Utilities
return pendingValues ? ? ( pendingValues = new SingleOrQueue < TSetRecord > ( ) ) ;
}
}
public bool IsSimpleSet = > pendingValues ? . HasTail ! = true ;
}
private readonly ConditionalWeakTable < TProperty , SettingStatus > setRecords = new ConditionalWeakTable < TProperty , SettingStatus > ( ) ;
private Dictionary < AvaloniaProperty , SettingStatus > _ setRecords ;
private Dictionary < AvaloniaProperty , SettingStatus > SetRecords
= > _ setRecords ? ? ( _ setRecords = new Dictionary < AvaloniaProperty , SettingStatus > ( ) ) ;
private SettingStatus GetOrCreateStatus ( AvaloniaProperty property )
{
if ( ! SetRecords . TryGetValue ( property , out var status ) )
{
status = new SettingStatus ( ) ;
SetRecords . Add ( property , status ) ;
}
return status ;
}
/// <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>
private NotifyDisposable MarkNotifying ( TProperty property )
private NotifyDisposable MarkNotifying ( Avalonia Property property )
{
Contract . Requires < InvalidOperationException > ( ! IsNotifying ( property ) ) ;
return new NotifyDisposable ( setRecords . GetOrCreateValue ( property ) ) ;
SettingStatus status = GetOrCreateStatus ( property ) ;
return new NotifyDisposable ( status ) ;
}
/// <summary>
@ -68,19 +83,19 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
private bool IsNotifying ( T Property property )
= > s etRecords. TryGetValue ( property , out var value ) & & value . Notifying ;
private bool IsNotifying ( Avalonia Property property )
= > S etRecords. TryGetValue ( property , out var value ) & & value . Notifying ;
/// <summary>
/// Add a pending assignment for the property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
private void AddPendingSet ( T Property property , TSetRecord value )
private void AddPendingSet ( Avalonia Property property , TSetRecord value )
{
Contract . Requires < InvalidOperationException > ( IsNotifying ( property ) ) ;
setRecords . GetOrCreateValue ( property ) . PendingValues . Enqueue ( value ) ;
GetOrCreateStatus ( property ) . PendingValues . Enqueue ( value ) ;
}
/// <summary>
@ -88,9 +103,9 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
private bool HasPendingSet ( T Property property )
private bool HasPendingSet ( Avalonia Property property )
{
return s etRecords. TryGetValue ( property , out var status ) & & ! status . PendingValues . Empty ;
return S etRecords. TryGetValue ( property , out var status ) & & ! status . PendingValues . Empty ;
}
/// <summary>
@ -98,9 +113,17 @@ namespace Avalonia.Utilities
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
private TSetRecord GetFirstPendingSet ( TProperty property )
private TSetRecord GetFirstPendingSet ( AvaloniaProperty property )
{
return GetOrCreateStatus ( property ) . PendingValues . Dequeue ( ) ;
}
private void CleanupSetStatus ( AvaloniaProperty property )
{
return setRecords . GetOrCreateValue ( property ) . PendingValues . Dequeue ( ) ;
if ( SetRecords . TryGetValue ( property , out var status ) & & status . IsSimpleSet )
{
SetRecords . Remove ( property ) ;
}
}
public delegate bool SetterDelegate < TValue > ( TSetRecord record , ref TValue backing , Action < Action > notifyCallback ) ;
@ -116,7 +139,7 @@ namespace Avalonia.Utilities
/// </param>
/// <param name="value">The value to try to set.</param>
public bool SetAndNotify < TValue > (
T Property property ,
Avalonia Property property ,
ref TValue backing ,
SetterDelegate < TValue > setterCallback ,
TSetRecord value )
@ -145,6 +168,9 @@ namespace Avalonia.Utilities
}
} ) ;
}
CleanupSetStatus ( property ) ;
return updated ;
}
else if ( ! object . Equals ( value , backing ) )