Browse Source

Move fix up to SettableNode and ExpressionNode so all settable node types (i.e. PropertyAccessor and Indexer nodes) can get the fix.

pull/1643/head
Jeremy Koritzinsky 8 years ago
parent
commit
1c3b714a0e
  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. 44
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  6. 38
      src/Avalonia.Base/Data/Core/SettableNode.cs
  7. 21
      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)
{ {

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

@ -10,12 +10,10 @@ using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal class PropertyAccessorNode : ExpressionNode, ISettableNode internal class PropertyAccessorNode : SettableNode
{ {
private static readonly object CacheInvalid = new object();
private readonly bool _enableValidation; private readonly bool _enableValidation;
private IPropertyAccessor _accessor; private IPropertyAccessor _accessor;
private WeakReference _lastValue = null;
public PropertyAccessorNode(string propertyName, bool enableValidation) public PropertyAccessorNode(string propertyName, bool enableValidation)
{ {
@ -25,22 +23,15 @@ 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 try
{ {
if (ShouldNotSet(value)) return _accessor.SetValue(value, priority);
{
return true;
}
else
{
return _accessor.SetValue(value, priority);
}
} }
catch { } catch { }
} }
@ -48,15 +39,6 @@ namespace Avalonia.Data.Core
return false; return false;
} }
private bool ShouldNotSet(object value)
{
if (PropertyType.IsValueType)
{
return _lastValue?.Target.Equals(value) ?? false;
}
return Object.ReferenceEquals(_lastValue?.Target ?? CacheInvalid, value);
}
protected override IObservable<object> StartListeningCore(WeakReference reference) protected override IObservable<object> StartListeningCore(WeakReference reference)
{ {
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName)); var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
@ -78,20 +60,12 @@ namespace Avalonia.Data.Core
() => () =>
{ {
_accessor = accessor; _accessor = accessor;
return Disposable.Create(() => _accessor = null); return Disposable.Create(() =>
},
_ => accessor).Select(value =>
{
if (value is BindingNotification notification)
{ {
_lastValue = notification.HasValue ? new WeakReference(notification.Value) : null; _accessor = null;
} });
else },
{ _ => accessor);
_lastValue = new WeakReference(value);
}
return value;
});
} }
} }
} }

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.Equals(value) ?? false;
}
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
}
protected abstract bool SetTargetValueCore(object value, BindingPriority priority);
public abstract Type PropertyType { get; }
}
}

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

@ -468,6 +468,17 @@ namespace Avalonia.Base.UnitTests
Assert.False(source.SetterCalled); 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>
@ -571,6 +582,16 @@ namespace Avalonia.Base.UnitTests
} }
} }
public double this[int index]
{
get => _value;
set
{
_value = value;
SetterCalled = true;
}
}
public bool SetterCalled { get; private set; } public bool SetterCalled { get; private set; }
} }
} }

Loading…
Cancel
Save