Browse Source

Merge pull request #1643 from jkoritzinsky/fixes/1289

Dont call setter when initializing two way bindings
pull/1579/merge
Steven Kirk 8 years ago
committed by GitHub
parent
commit
214d7e780f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  2. 4
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  3. 15
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  4. 6
      src/Avalonia.Base/Data/Core/IndexerNode.cs
  5. 17
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  6. 38
      src/Avalonia.Base/Data/Core/SettableNode.cs
  7. 49
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

6
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -11,6 +11,7 @@ namespace Avalonia.Data.Core
{ {
internal abstract class ExpressionNode : ISubject<object> internal abstract class ExpressionNode : ISubject<object>
{ {
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference = protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue); new WeakReference(AvaloniaProperty.UnsetValue);
@ -18,6 +19,8 @@ namespace Avalonia.Data.Core
private IDisposable _valueSubscription; private IDisposable _valueSubscription;
private IObserver<object> _observer; private IObserver<object> _observer;
protected WeakReference LastValue { get; private set; }
public abstract string Description { get; } public abstract string Description { get; }
public ExpressionNode Next { get; set; } public ExpressionNode Next { get; set; }
@ -61,6 +64,7 @@ namespace Avalonia.Data.Core
{ {
_valueSubscription?.Dispose(); _valueSubscription?.Dispose();
_valueSubscription = null; _valueSubscription = null;
LastValue = null;
nextSubscription?.Dispose(); nextSubscription?.Dispose();
_observer = null; _observer = null;
}); });
@ -120,6 +124,7 @@ namespace Avalonia.Data.Core
if (notification == null) if (notification == null)
{ {
LastValue = new WeakReference(value);
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(value); Next.Target = new WeakReference(value);
@ -131,6 +136,7 @@ namespace Avalonia.Data.Core
} }
else else
{ {
LastValue = new WeakReference(notification.Value);
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(notification.Value); Next.Target = new WeakReference(notification.Value);

4
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -154,7 +154,7 @@ namespace Avalonia.Data.Core
/// </returns> /// </returns>
public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
{ {
if (Leaf is ISettableNode settable) if (Leaf is SettableNode settable)
{ {
var node = _node; var node = _node;
while (node != null) while (node != null)
@ -188,7 +188,7 @@ namespace Avalonia.Data.Core
/// Gets the type of the expression result or null if the expression could not be /// Gets the type of the expression result or null if the expression could not be
/// evaluated. /// evaluated.
/// </summary> /// </summary>
public Type ResultType => (Leaf as ISettableNode)?.PropertyType; public Type ResultType => (Leaf as SettableNode)?.PropertyType;
/// <summary> /// <summary>
/// Gets the leaf node. /// Gets the leaf node.

15
src/Avalonia.Base/Data/Core/ISettableNode.cs

@ -1,15 +0,0 @@
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Data.Core
{
interface ISettableNode
{
bool SetTargetValue(object value, BindingPriority priority);
Type PropertyType { get; }
}
}

6
src/Avalonia.Base/Data/Core/IndexerNode.cs

@ -15,7 +15,7 @@ using Avalonia.Data;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal class IndexerNode : ExpressionNode, ISettableNode internal class IndexerNode : SettableNode
{ {
public IndexerNode(IList<string> arguments) public IndexerNode(IList<string> arguments)
{ {
@ -52,7 +52,7 @@ namespace Avalonia.Data.Core
return Observable.Merge(inputs).StartWith(GetValue(target)); return Observable.Merge(inputs).StartWith(GetValue(target));
} }
public bool SetTargetValue(object value, BindingPriority priority) protected override bool SetTargetValueCore(object value, BindingPriority priority)
{ {
var typeInfo = Target.Target.GetType().GetTypeInfo(); var typeInfo = Target.Target.GetType().GetTypeInfo();
var list = Target.Target as IList; var list = Target.Target as IList;
@ -154,7 +154,7 @@ namespace Avalonia.Data.Core
public IList<string> Arguments { get; } public IList<string> Arguments { get; }
public Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType; public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
private object GetValue(object target) private object GetValue(object target)
{ {

17
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -10,7 +10,7 @@ using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal class PropertyAccessorNode : ExpressionNode, ISettableNode internal class PropertyAccessorNode : SettableNode
{ {
private readonly bool _enableValidation; private readonly bool _enableValidation;
private IPropertyAccessor _accessor; private IPropertyAccessor _accessor;
@ -23,13 +23,17 @@ namespace Avalonia.Data.Core
public override string Description => PropertyName; public override string Description => PropertyName;
public string PropertyName { get; } public string PropertyName { get; }
public Type PropertyType => _accessor?.PropertyType; public override Type PropertyType => _accessor?.PropertyType;
public bool SetTargetValue(object value, BindingPriority priority) protected override bool SetTargetValueCore(object value, BindingPriority priority)
{ {
if (_accessor != null) if (_accessor != null)
{ {
try { return _accessor.SetValue(value, priority); } catch { } try
{
return _accessor.SetValue(value, priority);
}
catch { }
} }
return false; return false;
@ -56,7 +60,10 @@ namespace Avalonia.Data.Core
() => () =>
{ {
_accessor = accessor; _accessor = accessor;
return Disposable.Create(() => _accessor = null); return Disposable.Create(() =>
{
_accessor = null;
});
}, },
_ => accessor); _ => accessor);
} }

38
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -0,0 +1,38 @@
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Data.Core
{
internal abstract class SettableNode : ExpressionNode
{
public bool SetTargetValue(object value, BindingPriority priority)
{
if (ShouldNotSet(value))
{
return true;
}
return SetTargetValueCore(value, priority);
}
private bool ShouldNotSet(object value)
{
if (PropertyType == null)
{
return false;
}
if (PropertyType.IsValueType)
{
return LastValue?.Target != null && LastValue.Target.Equals(value);
}
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
}
protected abstract bool SetTargetValueCore(object value, BindingPriority priority);
public abstract Type PropertyType { get; }
}
}

49
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -457,6 +457,28 @@ namespace Avalonia.Base.UnitTests
Assert.True(target.IsAnimating(Class1.FooProperty)); Assert.True(target.IsAnimating(Class1.FooProperty));
} }
[Fact]
public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation()
{
var target = new Class1();
var source = new TestTwoWayBindingViewModel();
target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled);
}
[Fact]
public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_Indexer()
{
var target = new Class1();
var source = new TestTwoWayBindingViewModel();
target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source });
Assert.False(source.SetterCalled);
}
/// <summary> /// <summary>
/// Returns an observable that returns a single value but does not complete. /// Returns an observable that returns a single value but does not complete.
/// </summary> /// </summary>
@ -545,5 +567,32 @@ namespace Avalonia.Base.UnitTests
} }
} }
} }
private class TestTwoWayBindingViewModel
{
private double _value;
public double Value
{
get => _value;
set
{
_value = value;
SetterCalled = true;
}
}
public double this[int index]
{
get => _value;
set
{
_value = value;
SetterCalled = true;
}
}
public bool SetterCalled { get; private set; }
}
} }
} }
Loading…
Cancel
Save