Browse Source

Merge pull request #3258 from AvaloniaUI/fixes/binding-stack-overflow

Alternative fix for binding stack overflow
pull/3287/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
c5acd37ea7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  2. 9
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  3. 128
      src/Avalonia.Base/Utilities/DeferredSetter.cs

11
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
#nullable enable
@ -47,8 +48,14 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
_value = (T)e.NewValue;
PublishNext(new BindingValue<T>(_value));
var typedArgs = (AvaloniaPropertyChangedEventArgs<T>)e;
var newValue = e.Sender.GetValue(typedArgs.Property);
if (!typedArgs.OldValue.HasValue || !EqualityComparer<T>.Default.Equals(newValue, _value))
{
_value = newValue;
PublishNext(_value);
}
}
}
}

9
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -44,8 +44,13 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
_value = (T)e.NewValue;
PublishNext(_value);
var newValue = e.Sender.GetValue(e.Property);
if (!Equals(newValue, _value))
{
_value = (T)newValue;
PublishNext(_value);
}
}
}
}

128
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -1,128 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Utilities
{
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
/// </summary>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
internal sealed class DeferredSetter<TSetRecord>
{
private readonly SingleOrQueue<TSetRecord> _pendingValues;
private bool _isNotifying;
public DeferredSetter()
{
_pendingValues = new SingleOrQueue<TSetRecord>();
}
private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty<TSetRecord> property, ref TSetRecord backing, TSetRecord value)
{
var old = backing;
backing = value;
source.RaisePropertyChanged(property, old, value);
}
public bool SetAndNotify(
AvaloniaObject source,
AvaloniaProperty<TSetRecord> property,
ref TSetRecord backing,
TSetRecord value)
{
if (!_isNotifying)
{
using (new NotifyDisposable(this))
{
SetAndRaisePropertyChanged(source, property, ref backing, value);
}
if (!_pendingValues.Empty)
{
using (new NotifyDisposable(this))
{
while (!_pendingValues.Empty)
{
SetAndRaisePropertyChanged(source, property, ref backing, _pendingValues.Dequeue());
}
}
}
return true;
}
_pendingValues.Enqueue(value);
return false;
}
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, ISetAndNotifyHandler<TValue> setAndNotifyHandler, ref TValue backing, TValue value)
where TValue : TSetRecord
{
if (!_isNotifying)
{
using (new NotifyDisposable(this))
{
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, value);
}
if (!_pendingValues.Empty)
{
using (new NotifyDisposable(this))
{
while (!_pendingValues.Empty)
{
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, (TValue)_pendingValues.Dequeue());
}
}
}
return true;
}
_pendingValues.Enqueue(value);
return false;
}
/// <summary>
/// Disposable that marks the property as currently notifying.
/// When disposed, marks the property as done notifying.
/// </summary>
private readonly struct NotifyDisposable : IDisposable
{
private readonly DeferredSetter<TSetRecord> _setter;
internal NotifyDisposable(DeferredSetter<TSetRecord> setter)
{
_setter = setter;
_setter._isNotifying = true;
}
public void Dispose()
{
_setter._isNotifying = false;
}
}
}
/// <summary>
/// Handler for set and notify requests.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
internal interface ISetAndNotifyHandler<TValue>
{
/// <summary>
/// Handles deferred setter requests to set a value.
/// </summary>
/// <param name="property">Property being set.</param>
/// <param name="backing">Backing field reference.</param>
/// <param name="value">New value.</param>
void HandleSetAndNotify(AvaloniaProperty property, ref TValue backing, TValue value);
}
}
Loading…
Cancel
Save