Browse Source

Merge branch 'master' into maxlen

pull/2864/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
2d49d59fe1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 229
      src/Avalonia.Base/Collections/AvaloniaList.cs
  2. 6
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  3. 26
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  4. 8
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  5. 11
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  6. 5
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  7. 4
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  8. 23
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  9. 8
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  10. 5
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  11. 3
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  12. 4
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  13. 39
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  14. 23
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  15. 21
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  16. 12
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  17. 13
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  18. 6
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  19. 18
      src/Avalonia.Base/Data/Core/SettableNode.cs
  20. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  21. 21
      src/Avalonia.Base/Data/DataValidationException.cs
  22. 24
      src/Avalonia.Controls/DataValidationErrors.cs
  23. 12
      src/Avalonia.Input/Gestures.cs
  24. 2
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  25. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs
  26. 26
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs
  27. 17
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  28. 4
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs
  29. 2
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  30. 14
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
  31. 4
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs
  32. 12
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs
  33. 2
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs
  34. 132
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

229
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -55,15 +55,15 @@ namespace Avalonia.Collections
/// </remarks>
public class AvaloniaList<T> : IAvaloniaList<T>, IList, INotifyCollectionChangedDebug
{
private List<T> _inner;
private readonly List<T> _inner;
private NotifyCollectionChangedEventHandler _collectionChanged;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
/// </summary>
public AvaloniaList()
: this(Enumerable.Empty<T>())
{
_inner = new List<T>();
}
/// <summary>
@ -89,8 +89,8 @@ namespace Avalonia.Collections
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { _collectionChanged += value; }
remove { _collectionChanged -= value; }
add => _collectionChanged += value;
remove => _collectionChanged -= value;
}
/// <summary>
@ -150,7 +150,7 @@ namespace Avalonia.Collections
T old = _inner[index];
if (!object.Equals(old, value))
if (!EqualityComparer<T>.Default.Equals(old, value))
{
_inner[index] = value;
@ -187,45 +187,38 @@ namespace Avalonia.Collections
Validate?.Invoke(item);
int index = _inner.Count;
_inner.Add(item);
NotifyAdd(new[] { item }, index);
NotifyAdd(item, index);
}
/// <summary>
/// Adds multiple items to the collection.
/// </summary>
/// <param name="items">The items.</param>
public virtual void AddRange(IEnumerable<T> items)
{
Contract.Requires<ArgumentNullException>(items != null);
var list = (items as IList) ?? items.ToList();
if (list.Count > 0)
{
if (Validate != null)
{
foreach (var item in list)
{
Validate((T)item);
}
}
int index = _inner.Count;
_inner.AddRange(items);
NotifyAdd(list, index);
}
}
public virtual void AddRange(IEnumerable<T> items) => InsertRange(_inner.Count, items);
/// <summary>
/// Removes all items from the collection.
/// </summary>
public virtual void Clear()
{
if (this.Count > 0)
if (Count > 0)
{
var old = _inner;
_inner = new List<T>();
NotifyReset(old);
if (_collectionChanged != null)
{
var e = ResetBehavior == ResetBehavior.Reset ?
EventArgsCache.ResetCollectionChanged :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToList(), 0);
_inner.Clear();
_collectionChanged(this, e);
}
else
{
_inner.Clear();
}
NotifyCountChanged();
}
}
@ -253,9 +246,20 @@ namespace Avalonia.Collections
/// Returns an enumerator that enumerates the items in the collection.
/// </summary>
/// <returns>An <see cref="IEnumerator{T}"/>.</returns>
public IEnumerator<T> GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _inner.GetEnumerator();
return new Enumerator(_inner);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(_inner);
}
public Enumerator GetEnumerator()
{
return new Enumerator(_inner);
}
/// <summary>
@ -289,7 +293,7 @@ namespace Avalonia.Collections
{
Validate?.Invoke(item);
_inner.Insert(index, item);
NotifyAdd(new[] { item }, index);
NotifyAdd(item, index);
}
/// <summary>
@ -301,20 +305,83 @@ namespace Avalonia.Collections
{
Contract.Requires<ArgumentNullException>(items != null);
var list = (items as IList) ?? items.ToList();
bool willRaiseCollectionChanged = _collectionChanged != null;
bool hasValidation = Validate != null;
if (list.Count > 0)
if (items is IList list)
{
if (Validate != null)
if (list.Count > 0)
{
foreach (var item in list)
if (list is ICollection<T> collection)
{
Validate((T)item);
if (hasValidation)
{
foreach (T item in collection)
{
Validate(item);
}
}
_inner.InsertRange(index, collection);
NotifyAdd(list, index);
}
else
{
using (IEnumerator<T> en = items.GetEnumerator())
{
int insertIndex = index;
while (en.MoveNext())
{
T item = en.Current;
if (hasValidation)
{
Validate(item);
}
_inner.Insert(insertIndex++, item);
}
}
NotifyAdd(list, index);
}
}
}
else
{
using (IEnumerator<T> en = items.GetEnumerator())
{
if (en.MoveNext())
{
// Avoid allocating list for collection notification if there is no event subscriptions.
List<T> notificationItems = willRaiseCollectionChanged ?
new List<T>() :
null;
int insertIndex = index;
do
{
T item = en.Current;
if (hasValidation)
{
Validate(item);
}
_inner.InsertRange(index, items);
NotifyAdd((items as IList) ?? items.ToList(), index);
_inner.Insert(insertIndex++, item);
if (willRaiseCollectionChanged)
{
notificationItems.Add(item);
}
} while (en.MoveNext());
NotifyAdd(notificationItems, index);
}
}
}
}
@ -382,7 +449,7 @@ namespace Avalonia.Collections
if (index != -1)
{
_inner.RemoveAt(index);
NotifyRemove(new[] { item }, index);
NotifyRemove(item , index);
return true;
}
@ -412,7 +479,7 @@ namespace Avalonia.Collections
{
T item = _inner[index];
_inner.RemoveAt(index);
NotifyRemove(new[] { item }, index);
NotifyRemove(item , index);
}
/// <summary>
@ -480,12 +547,6 @@ namespace Avalonia.Collections
_inner.CopyTo((T[])array, index);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return _inner.GetEnumerator();
}
/// <inheritdoc/>
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
@ -505,13 +566,29 @@ namespace Avalonia.Collections
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a add action.
/// </summary>
/// <param name="item">The item that was added.</param>
/// <param name="index">The starting index.</param>
private void NotifyAdd(T item, int index)
{
if (_collectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event when the <see cref="Count"/> property
/// changes.
/// </summary>
private void NotifyCountChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
}
/// <summary>
@ -531,23 +608,57 @@ namespace Avalonia.Collections
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a reset action.
/// Raises the <see cref="CollectionChanged"/> event with a remove action.
/// </summary>
/// <param name="t">The items that were removed.</param>
private void NotifyReset(IList t)
/// <param name="item">The item that was removed.</param>
/// <param name="index">The starting index.</param>
private void NotifyRemove(T item, int index)
{
if (_collectionChanged != null)
{
NotifyCollectionChangedEventArgs e;
e = ResetBehavior == ResetBehavior.Reset ?
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Enumerates the elements of a <see cref="AvaloniaList{T}"/>.
/// </summary>
public struct Enumerator : IEnumerator<T>
{
private List<T>.Enumerator _innerEnumerator;
public Enumerator(List<T> inner)
{
_innerEnumerator = inner.GetEnumerator();
}
public bool MoveNext()
{
return _innerEnumerator.MoveNext();
}
void IEnumerator.Reset()
{
((IEnumerator)_innerEnumerator).Reset();
}
public T Current => _innerEnumerator.Current;
object IEnumerator.Current => Current;
public void Dispose()
{
_innerEnumerator.Dispose();
}
}
}
internal static class EventArgsCache
{
internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList<object>.Count));
internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
}
}

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

@ -24,7 +24,7 @@ namespace Avalonia.Data.Core
{
try
{
if (Target.IsAlive && Target.Target is IAvaloniaObject obj)
if (Target.TryGetTarget(out object target) && target is IAvaloniaObject obj)
{
obj.SetValue(_property, value, priority);
return true;
@ -37,9 +37,9 @@ namespace Avalonia.Data.Core
}
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
if (reference.Target is IAvaloniaObject obj)
if (reference.TryGetTarget(out object target) && target is IAvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
}

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

@ -8,27 +8,27 @@ namespace Avalonia.Data.Core
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue);
private WeakReference _target = UnsetReference;
private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber;
private bool _listening;
protected WeakReference LastValue { get; private set; }
protected WeakReference<object> LastValue { get; private set; }
public abstract string Description { get; }
public ExpressionNode Next { get; set; }
public WeakReference Target
public WeakReference<object> Target
{
get { return _target; }
set
{
Contract.Requires<ArgumentNullException>(value != null);
var oldTarget = _target?.Target;
var newTarget = value.Target;
_target.TryGetTarget(out var oldTarget);
value.TryGetTarget(out object newTarget);
if (!ReferenceEquals(oldTarget, newTarget))
{
@ -72,9 +72,11 @@ namespace Avalonia.Data.Core
_subscriber = null;
}
protected virtual void StartListeningCore(WeakReference reference)
protected virtual void StartListeningCore(WeakReference<object> reference)
{
ValueChanged(reference.Target);
reference.TryGetTarget(out object target);
ValueChanged(target);
}
protected virtual void StopListeningCore()
@ -96,7 +98,7 @@ namespace Avalonia.Data.Core
if (notification == null)
{
LastValue = new WeakReference(value);
LastValue = new WeakReference<object>(value);
if (Next != null)
{
@ -109,7 +111,7 @@ namespace Avalonia.Data.Core
}
else
{
LastValue = new WeakReference(notification.Value);
LastValue = new WeakReference<object>(notification.Value);
if (Next != null)
{
@ -125,7 +127,7 @@ namespace Avalonia.Data.Core
private void StartListening()
{
var target = _target.Target;
_target.TryGetTarget(out object target);
if (target == null)
{

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

@ -78,7 +78,7 @@ namespace Avalonia.Data.Core
_node = node;
Description = description;
_root = new WeakReference(root);
_root = new WeakReference<object>(root);
}
/// <summary>
@ -120,7 +120,7 @@ namespace Avalonia.Data.Core
Contract.Requires<ArgumentNullException>(update != null);
Description = description;
_node = node;
_node.Target = new WeakReference(rootGetter());
_node.Target = new WeakReference<object>(rootGetter());
_root = update.Select(x => rootGetter());
}
@ -285,13 +285,13 @@ namespace Avalonia.Data.Core
if (_root is IObservable<object> observable)
{
_rootSubscription = observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
x => _node.Target = new WeakReference<object>(x != AvaloniaProperty.UnsetValue ? x : null),
x => PublishCompleted(),
() => PublishCompleted());
}
else
{
_node.Target = (WeakReference)_root;
_node.Target = (WeakReference<object>)_root;
}
}

11
src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs

@ -36,7 +36,9 @@ namespace Avalonia.Data.Core
{
try
{
_setDelegate.DynamicInvoke(Target.Target, value);
Target.TryGetTarget(out object target);
_setDelegate.DynamicInvoke(target, value);
return true;
}
catch (Exception)
@ -64,6 +66,11 @@ namespace Avalonia.Data.Core
return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
}
protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
protected override int? TryGetFirstArgumentAsInt()
{
Target.TryGetTarget(out object target);
return _firstArgumentDelegate.DynamicInvoke(target) as int?;
}
}
}

5
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -13,9 +13,10 @@ namespace Avalonia.Data.Core
{
private IDisposable _subscription;
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
var target = reference.Target;
reference.TryGetTarget(out object target);
var incc = target as INotifyCollectionChanged;
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object>>();

4
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -31,12 +31,12 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
public IPropertyAccessor Start(WeakReference reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var o = (AvaloniaObject)instance;
var p = LookupProperty(o, propertyName);

23
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -15,9 +15,11 @@ namespace Avalonia.Data.Core.Plugins
public class DataAnnotationsValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName)
public bool Match(WeakReference<object> reference, string memberName)
{
return reference.Target?
reference.TryGetTarget(out object target);
return target?
.GetType()
.GetRuntimeProperty(memberName)?
.GetCustomAttributes<ValidationAttribute>()
@ -25,25 +27,22 @@ namespace Avalonia.Data.Core.Plugins
}
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
{
return new Accessor(reference, name, inner);
}
private class Accessor : DataValidationBase
private sealed class Accessor : DataValidationBase
{
private ValidationContext _context;
private readonly ValidationContext _context;
public Accessor(WeakReference reference, string name, IPropertyAccessor inner)
public Accessor(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
_context = new ValidationContext(reference.Target);
_context.MemberName = name;
}
reference.TryGetTarget(out object target);
public override bool SetValue(object value, BindingPriority priority)
{
return base.SetValue(value, priority);
_context = new ValidationContext(target);
_context.MemberName = name;
}
protected override void InnerValueChanged(object value)

8
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -12,17 +12,17 @@ namespace Avalonia.Data.Core.Plugins
public class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName) => true;
public bool Match(WeakReference<object> reference, string memberName) => true;
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
{
return new Validator(reference, name, inner);
}
private class Validator : DataValidationBase
private sealed class Validator : DataValidationBase
{
public Validator(WeakReference reference, string name, IPropertyAccessor inner)
public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
}

5
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -16,7 +16,7 @@ namespace Avalonia.Data.Core.Plugins
/// <param name="reference">A weak reference to the object.</param>
/// <param name="memberName">The name of the member to validate.</param>
/// <returns>True if the plugin can handle the object; otherwise false.</returns>
bool Match(WeakReference reference, string memberName);
bool Match(WeakReference<object> reference, string memberName);
/// <summary>
/// Starts monitoring the data validation state of a property on an object.
@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
IPropertyAccessor Start(
WeakReference reference,
IPropertyAccessor Start(WeakReference<object> reference,
string propertyName,
IPropertyAccessor inner);
}

3
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
IPropertyAccessor Start(
WeakReference reference,
IPropertyAccessor Start(WeakReference<object> reference,
string propertyName);
}
}

4
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -15,7 +15,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
bool Match(WeakReference reference);
bool Match(WeakReference<object> reference);
/// <summary>
/// Starts producing output based on the specified value.
@ -24,6 +24,6 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
IObservable<object> Start(WeakReference reference);
IObservable<object> Start(WeakReference<object> reference);
}
}

39
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -15,20 +15,25 @@ namespace Avalonia.Data.Core.Plugins
public class IndeiValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName) => reference.Target is INotifyDataErrorInfo;
public bool Match(WeakReference<object> reference, string memberName)
{
reference.TryGetTarget(out object target);
return target is INotifyDataErrorInfo;
}
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor accessor)
{
return new Validator(reference, name, accessor);
}
private class Validator : DataValidationBase, IWeakSubscriber<DataErrorsChangedEventArgs>
{
WeakReference _reference;
string _name;
private readonly WeakReference<object> _reference;
private readonly string _name;
public Validator(WeakReference reference, string name, IPropertyAccessor inner)
public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
_reference = reference;
@ -45,7 +50,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
var target = _reference.Target as INotifyDataErrorInfo;
var target = GetReferenceTarget() as INotifyDataErrorInfo;
if (target != null)
{
@ -60,7 +65,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void UnsubscribeCore()
{
var target = _reference.Target as INotifyDataErrorInfo;
var target = GetReferenceTarget() as INotifyDataErrorInfo;
if (target != null)
{
@ -80,13 +85,14 @@ namespace Avalonia.Data.Core.Plugins
private BindingNotification CreateBindingNotification(object value)
{
var target = (INotifyDataErrorInfo)_reference.Target;
var target = (INotifyDataErrorInfo)GetReferenceTarget();
if (target != null)
{
var errors = target.GetErrors(_name)?
.Cast<String>()
.Where(x => x != null).ToList();
.Cast<object>()
.Where(x => x != null)
.ToList();
if (errors?.Count > 0)
{
@ -100,16 +106,23 @@ namespace Avalonia.Data.Core.Plugins
return new BindingNotification(value);
}
private Exception GenerateException(IList<string> errors)
private object GetReferenceTarget()
{
_reference.TryGetTarget(out object target);
return target;
}
private Exception GenerateException(IList<object> errors)
{
if (errors.Count == 1)
{
return new Exception(errors[0]);
return new DataValidationException(errors[0]);
}
else
{
return new AggregateException(
errors.Select(x => new Exception(x)));
errors.Select(x => new DataValidationException(x)));
}
}
}

23
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -28,12 +28,12 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
public IPropertyAccessor Start(WeakReference reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName);
if (p != null)
@ -50,11 +50,11 @@ namespace Avalonia.Data.Core.Plugins
private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>
{
private readonly WeakReference _reference;
private readonly WeakReference<object> _reference;
private readonly PropertyInfo _property;
private bool _eventRaised;
public Accessor(WeakReference reference, PropertyInfo property)
public Accessor(WeakReference<object> reference, PropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
@ -69,7 +69,7 @@ namespace Avalonia.Data.Core.Plugins
{
get
{
var o = _reference.Target;
var o = GetReferenceTarget();
return (o != null) ? _property.GetValue(o) : null;
}
}
@ -79,7 +79,7 @@ namespace Avalonia.Data.Core.Plugins
if (_property.CanWrite)
{
_eventRaised = false;
_property.SetValue(_reference.Target, value);
_property.SetValue(GetReferenceTarget(), value);
if (!_eventRaised)
{
@ -109,7 +109,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void UnsubscribeCore()
{
var inpc = _reference.Target as INotifyPropertyChanged;
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
{
@ -120,6 +120,13 @@ namespace Avalonia.Data.Core.Plugins
}
}
private object GetReferenceTarget()
{
_reference.TryGetTarget(out object target);
return target;
}
private void SendCurrentValue()
{
try
@ -132,7 +139,7 @@ namespace Avalonia.Data.Core.Plugins
private void SubscribeToChanges()
{
var inpc = _reference.Target as INotifyPropertyChanged;
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
{

21
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -9,12 +9,12 @@ namespace Avalonia.Data.Core.Plugins
public bool Match(object obj, string methodName)
=> obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName);
public IPropertyAccessor Start(WeakReference reference, string methodName)
public IPropertyAccessor Start(WeakReference<object> reference, string methodName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(methodName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName);
if (method != null)
@ -35,9 +35,9 @@ namespace Avalonia.Data.Core.Plugins
}
}
private class Accessor : PropertyAccessorBase
private sealed class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference reference, MethodInfo method)
public Accessor(WeakReference<object> reference, MethodInfo method)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(method != null);
@ -61,8 +61,17 @@ namespace Avalonia.Data.Core.Plugins
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray();
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
}
Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target);
if (method.IsStatic)
{
Value = method.CreateDelegate(PropertyType);
}
else
{
reference.TryGetTarget(out object target);
Value = method.CreateDelegate(PropertyType, target);
}
}
public override Type PropertyType { get; }

12
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -20,9 +20,11 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference)
public virtual bool Match(WeakReference<object> reference)
{
return reference.Target.GetType().GetInterfaces().Any(x =>
reference.TryGetTarget(out object target);
return target != null && target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
@ -34,9 +36,9 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
public virtual IObservable<object> Start(WeakReference<object> reference)
{
var target = reference.Target;
reference.TryGetTarget(out object target);
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object> result)
@ -46,7 +48,7 @@ namespace Avalonia.Data.Core.Plugins
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
var sourceType = target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];

13
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -19,7 +19,12 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference) => reference.Target is Task;
public virtual bool Match(WeakReference<object> reference)
{
reference.TryGetTarget(out object target);
return target is Task;
}
/// <summary>
/// Starts producing output based on the specified value.
@ -28,11 +33,11 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
public virtual IObservable<object> Start(WeakReference<object> reference)
{
var task = reference.Target as Task;
reference.TryGetTarget(out object target);
if (task != null)
if (target is Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");

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

@ -37,9 +37,11 @@ namespace Avalonia.Data.Core
return false;
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
reference.TryGetTarget(out object target);
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(target, PropertyName));
var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null)

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

@ -19,11 +19,25 @@ namespace Avalonia.Data.Core
{
return false;
}
if (LastValue == null)
{
return false;
}
bool isLastValueAlive = LastValue.TryGetTarget(out object lastValue);
if (!isLastValueAlive)
{
return false;
}
if (PropertyType.IsValueType)
{
return LastValue?.Target != null && LastValue.Target.Equals(value);
return lastValue.Equals(value);
}
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
return ReferenceEquals(lastValue, value);
}
protected abstract bool SetTargetValueCore(object value, BindingPriority priority);

2
src/Avalonia.Base/Data/Core/StreamNode.cs

@ -12,7 +12,7 @@ namespace Avalonia.Data.Core
public override string Description => "^";
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
foreach (var plugin in ExpressionObserver.StreamHandlers)
{

21
src/Avalonia.Base/Data/DataValidationException.cs

@ -0,0 +1,21 @@
using System;
namespace Avalonia.Data
{
/// <summary>
/// Exception, which wrap validation errors.
/// </summary>
public class DataValidationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="DataValidationException"/> class.
/// </summary>
/// <param name="errorData">Data of validation error.</param>
public DataValidationException(object errorData) : base(errorData?.ToString())
{
ErrorData = errorData;
}
public object ErrorData { get; }
}
}

24
src/Avalonia.Controls/DataValidationErrors.cs

@ -22,8 +22,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the DataValidationErrors.Errors attached property.
/// </summary>
public static readonly AttachedProperty<IEnumerable<Exception>> ErrorsProperty =
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<Exception>>("Errors");
public static readonly AttachedProperty<IEnumerable<object>> ErrorsProperty =
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>>("Errors");
/// <summary>
/// Defines the DataValidationErrors.HasErrors attached property.
@ -76,7 +76,7 @@ namespace Avalonia.Controls
private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
var errors = (IEnumerable<Exception>)e.NewValue;
var errors = (IEnumerable<object>)e.NewValue;
var hasErrors = false;
if (errors != null && errors.Any())
@ -91,11 +91,11 @@ namespace Avalonia.Controls
classes.Set(":error", (bool)e.NewValue);
}
public static IEnumerable<Exception> GetErrors(Control control)
public static IEnumerable<object> GetErrors(Control control)
{
return control.GetValue(ErrorsProperty);
}
public static void SetErrors(Control control, IEnumerable<Exception> errors)
public static void SetErrors(Control control, IEnumerable<object> errors)
{
control.SetValue(ErrorsProperty, errors);
}
@ -112,14 +112,14 @@ namespace Avalonia.Controls
return control.GetValue(HasErrorsProperty);
}
private static IEnumerable<Exception> UnpackException(Exception exception)
private static IEnumerable<object> UnpackException(Exception exception)
{
if (exception != null)
{
var aggregate = exception as AggregateException;
var exceptions = aggregate == null ?
(IEnumerable<Exception>)new[] { exception } :
aggregate.InnerExceptions;
new[] { GetExceptionData(exception) } :
aggregate.InnerExceptions.Select(GetExceptionData).ToArray();
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
if (filtered.Count > 0)
@ -130,5 +130,13 @@ namespace Avalonia.Controls
return null;
}
private static object GetExceptionData(Exception exception)
{
if (exception is DataValidationException dataValidationException)
return dataValidationException.ErrorData;
return exception;
}
}
}

12
src/Avalonia.Input/Gestures.cs

@ -31,7 +31,7 @@ namespace Avalonia.Input
RoutedEvent.Register<ScrollGestureEventArgs>(
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
private static WeakReference s_lastPress;
private static WeakReference<IInteractive> s_lastPress;
static Gestures()
{
@ -47,11 +47,11 @@ namespace Avalonia.Input
if (e.ClickCount <= 1)
{
s_lastPress = new WeakReference(e.Source);
s_lastPress = new WeakReference<IInteractive>(e.Source);
}
else if (s_lastPress?.IsAlive == true && e.ClickCount == 2 && s_lastPress.Target == e.Source)
else if (s_lastPress != null && e.ClickCount == 2 && e.MouseButton != MouseButton.Right)
{
if (e.MouseButton != MouseButton.Right)
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
}
@ -65,10 +65,10 @@ namespace Avalonia.Input
{
var e = (PointerReleasedEventArgs)ev;
if (s_lastPress?.IsAlive == true && s_lastPress.Target == e.Source)
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
var et = e.MouseButton != MouseButton.Right ? TappedEvent : RightTappedEvent;
((IInteractive)s_lastPress.Target).RaiseEvent(new RoutedEventArgs(et));
e.Source.RaiseEvent(new RoutedEventArgs(et));
}
}
}

2
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@ -19,7 +19,7 @@ namespace Avalonia.Markup.Parsers.Nodes
public override string Description => $"#{_name}";
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
if (_nameScope.TryGetTarget(out var scope))
_subscription = NameScopeLocator.Track(scope, _name).Subscribe(ValueChanged);

4
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs

@ -31,9 +31,9 @@ namespace Avalonia.Markup.Parsers.Nodes
}
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
if (reference.Target is ILogical logical)
if (reference.TryGetTarget(out object target) && target is ILogical logical)
{
_subscription = ControlLocator.Track(logical, _level, _ancestorType).Subscribe(ValueChanged);
}

26
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs

@ -26,9 +26,11 @@ namespace Avalonia.Markup.Parsers.Nodes
protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
var typeInfo = Target.Target.GetType().GetTypeInfo();
var list = Target.Target as IList;
var dictionary = Target.Target as IDictionary;
Target.TryGetTarget(out object target);
var typeInfo = target.GetType().GetTypeInfo();
var list = target as IList;
var dictionary = target as IDictionary;
var indexerProperty = GetIndexer(typeInfo);
var indexerParameters = indexerProperty?.GetIndexParameters();
@ -53,7 +55,7 @@ namespace Avalonia.Markup.Parsers.Nodes
// Try special cases where we can validate indices
if (typeInfo.IsArray)
{
return SetValueInArray((Array)Target.Target, intArgs, value);
return SetValueInArray((Array)target, intArgs, value);
}
else if (Arguments.Count == 1)
{
@ -83,14 +85,14 @@ namespace Avalonia.Markup.Parsers.Nodes
else
{
// Fallback to unchecked access
indexerProperty.SetValue(Target.Target, value, convertedObjectArray);
indexerProperty.SetValue(target, value, convertedObjectArray);
return true;
}
}
else
{
// Fallback to unchecked access
indexerProperty.SetValue(Target.Target, value, convertedObjectArray);
indexerProperty.SetValue(target, value, convertedObjectArray);
return true;
}
}
@ -98,7 +100,7 @@ namespace Avalonia.Markup.Parsers.Nodes
// multidimensional indexer, which doesn't take the same number of arguments
else if (typeInfo.IsArray)
{
SetValueInArray((Array)Target.Target, value);
SetValueInArray((Array)target, value);
return true;
}
return false;
@ -126,7 +128,15 @@ namespace Avalonia.Markup.Parsers.Nodes
public IList<string> Arguments { get; }
public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
public override Type PropertyType
{
get
{
Target.TryGetTarget(out object target);
return GetIndexer(target.GetType().GetTypeInfo())?.PropertyType;
}
}
protected override object GetValue(object target)
{

17
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@ -148,6 +148,23 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.True(raised);
}
[Fact]
public void AddRange_Items_Should_Raise_Correct_CollectionChanged()
{
var target = new AvaloniaList<object>();
var eventItems = new List<object>();
target.CollectionChanged += (sender, args) =>
{
eventItems.AddRange(args.NewItems.Cast<object>());
};
target.AddRange(Enumerable.Range(0,10).Select(i => new object()));
Assert.Equal(eventItems, target);
}
[Fact]
public void Replacing_Item_Should_Raise_CollectionChanged()
{

4
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs

@ -100,7 +100,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
// Value is first signalled without an error as validation hasn't been updated.
new BindingNotification(-5),
new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, -5),
new BindingNotification(new DataValidationException("Must be positive"), BindingErrorType.DataValidationError, -5),
// Exception is thrown by trying to set value to "foo".
new BindingNotification(
@ -108,7 +108,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
BindingErrorType.DataValidationError),
// Value is set then validation is updated.
new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5),
new BindingNotification(new DataValidationException("Must be positive"), BindingErrorType.DataValidationError, 5),
new BindingNotification(5),
}, result);

2
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

@ -574,7 +574,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
var source = new Class1 { Foo = "foo" };
var target = new PropertyAccessorNode("Foo", false);
Assert.NotNull(target);
target.Target = new WeakReference(source);
target.Target = new WeakReference<object>(source);
target.Subscribe(_ => { });
target.Unsubscribe();
target.Unsubscribe();

14
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs

@ -20,7 +20,7 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var target = new DataAnnotationsValidationPlugin();
var data = new Data();
Assert.True(target.Match(new WeakReference(data), nameof(Data.Between5And10)));
Assert.True(target.Match(new WeakReference<object>(data), nameof(Data.Between5And10)));
}
[Fact]
@ -29,7 +29,7 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var target = new DataAnnotationsValidationPlugin();
var data = new Data();
Assert.True(target.Match(new WeakReference(data), nameof(Data.PhoneNumber)));
Assert.True(target.Match(new WeakReference<object>(data), nameof(Data.PhoneNumber)));
}
[Fact]
@ -38,7 +38,7 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var target = new DataAnnotationsValidationPlugin();
var data = new Data();
Assert.False(target.Match(new WeakReference(data), nameof(Data.Unvalidated)));
Assert.False(target.Match(new WeakReference<object>(data), nameof(Data.Unvalidated)));
}
[Fact]
@ -47,8 +47,8 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new DataAnnotationsValidationPlugin();
var data = new Data();
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Between5And10));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Between5And10), accessor);
var accessor = inpcAccessorPlugin.Start(new WeakReference<object>(data), nameof(data.Between5And10));
var validator = validatorPlugin.Start(new WeakReference<object>(data), nameof(data.Between5And10), accessor);
var result = new List<object>();
var errmsg = new RangeAttribute(5, 10).FormatErrorMessage(nameof(Data.Between5And10));
@ -79,8 +79,8 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new DataAnnotationsValidationPlugin();
var data = new Data();
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.PhoneNumber));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.PhoneNumber), accessor);
var accessor = inpcAccessorPlugin.Start(new WeakReference<object>(data), nameof(data.PhoneNumber));
var validator = validatorPlugin.Start(new WeakReference<object>(data), nameof(data.PhoneNumber), accessor);
var result = new List<object>();
validator.Subscribe(x => result.Add(x));

4
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/ExceptionValidationPluginTests.cs

@ -19,8 +19,8 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new ExceptionValidationPlugin();
var data = new Data();
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.MustBePositive), accessor);
var accessor = inpcAccessorPlugin.Start(new WeakReference<object>(data), nameof(data.MustBePositive));
var validator = validatorPlugin.Start(new WeakReference<object>(data), nameof(data.MustBePositive), accessor);
var result = new List<object>();
validator.Subscribe(x => result.Add(x));

12
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs

@ -18,8 +18,8 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new IndeiValidationPlugin();
var data = new Data { Maximum = 5 };
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Value));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Value), accessor);
var accessor = inpcAccessorPlugin.Start(new WeakReference<object>(data), nameof(data.Value));
var validator = validatorPlugin.Start(new WeakReference<object>(data), nameof(data.Value), accessor);
var result = new List<object>();
validator.Subscribe(x => result.Add(x));
@ -37,13 +37,13 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins
new BindingNotification(6),
// Then the ErrorsChanged event is fired.
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),
new BindingNotification(new DataValidationException("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),
// Maximum is changed to 10 so value is now valid.
new BindingNotification(6),
// And Maximum is changed back to 5.
new BindingNotification(new Exception("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),
new BindingNotification(new DataValidationException("Must be less than Maximum"), BindingErrorType.DataValidationError, 6),
}, result);
}
@ -53,8 +53,8 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins
var inpcAccessorPlugin = new InpcPropertyAccessorPlugin();
var validatorPlugin = new IndeiValidationPlugin();
var data = new Data { Maximum = 5 };
var accessor = inpcAccessorPlugin.Start(new WeakReference(data), nameof(data.Value));
var validator = validatorPlugin.Start(new WeakReference(data), nameof(data.Value), accessor);
var accessor = inpcAccessorPlugin.Start(new WeakReference<object>(data), nameof(data.Value));
var validator = validatorPlugin.Start(new WeakReference<object>(data), nameof(data.Value), accessor);
Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
validator.Subscribe(_ => { });

2
tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@ -58,7 +58,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(DataValidationErrors.GetErrors(target));
target.Text = "20";
IEnumerable<Exception> errors = DataValidationErrors.GetErrors(target);
IEnumerable<object> errors = DataValidationErrors.GetErrors(target);
Assert.Single(errors);
Assert.IsType<InvalidOperationException>(errors.Single());
target.Text = "1";

132
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -60,6 +60,80 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("baz", source.Foo);
}
[Fact]
public void TwoWay_Binding_Should_Be_Set_Up_GC_Collect()
{
var source = new WeakRefSource { Foo = null };
var target = new TestControl { DataContext = source };
var binding = new Binding
{
Path = "Foo",
Mode = BindingMode.TwoWay
};
target.Bind(TestControl.ValueProperty, binding);
var ref1 = AssignValue(target, "ref1");
Assert.Equal(ref1.Target, source.Foo);
GC.Collect();
GC.WaitForPendingFinalizers();
var ref2 = AssignValue(target, "ref2");
GC.Collect();
GC.WaitForPendingFinalizers();
target.Value = null;
Assert.Null(source.Foo);
}
private class DummyObject : ICloneable
{
private readonly string _val;
public DummyObject(string val)
{
_val = val;
}
public object Clone()
{
return new DummyObject(_val);
}
protected bool Equals(DummyObject other)
{
return string.Equals(_val, other._val);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((DummyObject) obj);
}
public override int GetHashCode()
{
return (_val != null ? _val.GetHashCode() : 0);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private WeakReference AssignValue(TestControl source, string val)
{
var obj = new DummyObject(val);
source.Value = obj;
return new WeakReference(obj);
}
[Fact]
public void OneTime_Binding_Should_Be_Set_Up()
{
@ -568,12 +642,70 @@ namespace Avalonia.Markup.UnitTests.Data
}
}
public class WeakRefSource : INotifyPropertyChanged
{
private WeakReference<object> _foo;
public object Foo
{
get
{
if (_foo == null)
{
return null;
}
if (_foo.TryGetTarget(out object target))
{
if (target is ICloneable cloneable)
{
return cloneable.Clone();
}
return target;
}
return null;
}
set
{
_foo = new WeakReference<object>(value);
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
private class OldDataContextViewModel
{
public int Foo { get; set; } = 1;
public int Bar { get; set; } = 2;
}
private class TestControl : Control
{
public static readonly DirectProperty<TestControl, object> ValueProperty =
AvaloniaProperty.RegisterDirect<TestControl, object>(
nameof(Value),
o => o.Value,
(o, v) => o.Value = v);
private object _value;
public object Value
{
get => _value;
set => SetAndRaise(ValueProperty, ref _value, value);
}
}
private class OldDataContextTest : Control
{
public static readonly StyledProperty<int> FooProperty =

Loading…
Cancel
Save