Browse Source
A little more complex than just a list of delegates, but allows us to listen for property change notifications for an individual property. Also made much of the API internal. You must go via factory methods to create TypedBindings/Expressions.feature/typed-binding-2
11 changed files with 416 additions and 237 deletions
@ -0,0 +1,116 @@ |
|||
using System; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins |
|||
{ |
|||
internal class AvaloniaPropertyBindingTrigger<TIn> : TypedBindingTrigger<TIn>, |
|||
IWeakEventSubscriber<AvaloniaPropertyChangedEventArgs> |
|||
{ |
|||
private readonly Func<TIn, object?> _read; |
|||
private readonly AvaloniaProperty _property; |
|||
private WeakReference<AvaloniaObject?>? _source; |
|||
|
|||
public AvaloniaPropertyBindingTrigger( |
|||
int index, |
|||
Func<TIn, object?> read, |
|||
AvaloniaProperty property) |
|||
: base(index) |
|||
{ |
|||
_read = read; |
|||
_property = property; |
|||
} |
|||
|
|||
internal static TypedBindingTrigger<TIn>? TryCreate( |
|||
int index, |
|||
MemberExpression node, |
|||
LambdaExpression rootExpression) |
|||
{ |
|||
var type = node.Expression?.Type; |
|||
var member = node.Member; |
|||
|
|||
if (member.DeclaringType is null || |
|||
member.MemberType != MemberTypes.Property || |
|||
!typeof(AvaloniaObject).IsAssignableFrom(type)) |
|||
return null; |
|||
|
|||
var property = GetProperty(member); |
|||
|
|||
if (property is null) |
|||
return null; |
|||
|
|||
var lambda = Expression.Lambda<Func<TIn, object>>(node.Expression!, rootExpression.Parameters); |
|||
var read = lambda.Compile(); |
|||
|
|||
return new AvaloniaPropertyBindingTrigger<TIn>(index, read, property); |
|||
} |
|||
|
|||
internal static TypedBindingTrigger<TIn>? TryCreate( |
|||
int index, |
|||
MethodCallExpression node, |
|||
LambdaExpression rootExpression) |
|||
{ |
|||
var type = node.Object?.Type; |
|||
var method = node.Method; |
|||
|
|||
if (method.Name != "get_Item" || |
|||
method.DeclaringType is null || |
|||
node.Arguments.Count != 1 || |
|||
GetProperty(node.Arguments[0]) is not AvaloniaProperty property || |
|||
!typeof(AvaloniaObject).IsAssignableFrom(type)) |
|||
return null; |
|||
|
|||
var lambda = Expression.Lambda<Func<TIn, object>>(node.Object!, rootExpression.Parameters); |
|||
var read = lambda.Compile(); |
|||
|
|||
return new AvaloniaPropertyBindingTrigger<TIn>(index, read, property); |
|||
} |
|||
|
|||
void IWeakEventSubscriber<AvaloniaPropertyChangedEventArgs>.OnEvent( |
|||
object? sender, |
|||
WeakEvent ev, |
|||
AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
OnChanged(); |
|||
} |
|||
|
|||
protected override bool SubscribeCore(TIn root) |
|||
{ |
|||
var o = _read(root) as AvaloniaObject; |
|||
_source = new(o); |
|||
|
|||
if (o is null) |
|||
return false; |
|||
|
|||
WeakEvents.AvaloniaPropertyChanged.Subscribe(o, this); |
|||
return true; |
|||
} |
|||
|
|||
protected override void UnsubscribeCore() |
|||
{ |
|||
if (_source?.TryGetTarget(out var o) == true) |
|||
WeakEvents.AvaloniaPropertyChanged.Unsubscribe(o, this); |
|||
} |
|||
|
|||
private static AvaloniaProperty? GetProperty(Expression expression) |
|||
{ |
|||
if (expression is not MemberExpression member || |
|||
member.Member is not FieldInfo field || |
|||
!field.IsStatic) |
|||
return null; |
|||
|
|||
return field.GetValue(null) as AvaloniaProperty; |
|||
} |
|||
|
|||
private static AvaloniaProperty? GetProperty(MemberInfo member) |
|||
{ |
|||
var propertyName = member.Name; |
|||
var propertyField = member.DeclaringType?.GetField( |
|||
propertyName + "Property", |
|||
BindingFlags.Static); |
|||
return propertyField?.GetValue(null) as AvaloniaProperty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
using System; |
|||
using System.Collections.Specialized; |
|||
using System.Linq.Expressions; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins |
|||
{ |
|||
internal class InccBindingTrigger<TIn> : TypedBindingTrigger<TIn>, |
|||
IWeakEventSubscriber<NotifyCollectionChangedEventArgs> |
|||
{ |
|||
private readonly Func<TIn, object?> _read; |
|||
private WeakReference<INotifyCollectionChanged?>? _source; |
|||
|
|||
public InccBindingTrigger( |
|||
int index, |
|||
Func<TIn, object?> read) |
|||
: base(index) |
|||
{ |
|||
_read = read; |
|||
} |
|||
|
|||
internal static TypedBindingTrigger<TIn>? TryCreate( |
|||
int index, |
|||
MethodCallExpression node, |
|||
LambdaExpression rootExpression) |
|||
{ |
|||
var type = node.Object?.Type; |
|||
var method = node.Method; |
|||
|
|||
if (method.Name != "get_Item" || |
|||
!typeof(INotifyCollectionChanged).IsAssignableFrom(type)) |
|||
return null; |
|||
|
|||
var lambda = Expression.Lambda<Func<TIn, object>>(node.Object!, rootExpression.Parameters); |
|||
var read = lambda.Compile(); |
|||
|
|||
return new InccBindingTrigger<TIn>(index, read); |
|||
} |
|||
|
|||
void IWeakEventSubscriber<NotifyCollectionChangedEventArgs>.OnEvent( |
|||
object? sender, |
|||
WeakEvent ev, |
|||
NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
OnChanged(); |
|||
} |
|||
|
|||
protected override bool SubscribeCore(TIn root) |
|||
{ |
|||
var o = _read(root) as INotifyCollectionChanged; |
|||
_source = new(o); |
|||
|
|||
if (o is null) |
|||
return false; |
|||
|
|||
WeakEvents.CollectionChanged.Subscribe(o, this); |
|||
return true; |
|||
} |
|||
|
|||
protected override void UnsubscribeCore() |
|||
{ |
|||
if (_source?.TryGetTarget(out var o) == true) |
|||
WeakEvents.CollectionChanged.Unsubscribe(o, this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Data.Core.Plugins |
|||
{ |
|||
internal class InpcBindingTrigger<TIn> : TypedBindingTrigger<TIn>, |
|||
IWeakEventSubscriber<PropertyChangedEventArgs> |
|||
{ |
|||
private readonly Func<TIn, object?> _read; |
|||
private readonly string _propertyName; |
|||
private WeakReference<INotifyPropertyChanged?>? _source; |
|||
|
|||
public InpcBindingTrigger( |
|||
int index, |
|||
Func<TIn, object?> read, |
|||
string propertyName) |
|||
: base(index) |
|||
{ |
|||
_read = read; |
|||
_propertyName = propertyName; |
|||
} |
|||
|
|||
internal static TypedBindingTrigger<TIn>? TryCreate( |
|||
int index, |
|||
MemberExpression node, |
|||
LambdaExpression rootExpression) |
|||
{ |
|||
var type = node.Expression?.Type; |
|||
var member = node.Member; |
|||
|
|||
if (member.MemberType != MemberTypes.Property || |
|||
!typeof(INotifyPropertyChanged).IsAssignableFrom(type)) |
|||
return null; |
|||
|
|||
var lambda = Expression.Lambda<Func<TIn, object>>(node.Expression!, rootExpression.Parameters); |
|||
var read = lambda.Compile(); |
|||
|
|||
return new InpcBindingTrigger<TIn>(index, read, member.Name); |
|||
} |
|||
|
|||
internal static TypedBindingTrigger<TIn>? TryCreate( |
|||
int index, |
|||
MethodCallExpression node, |
|||
LambdaExpression rootExpression) |
|||
{ |
|||
var type = node.Object?.Type; |
|||
|
|||
if (node.Method.Name != "get_Item" || |
|||
!typeof(INotifyPropertyChanged).IsAssignableFrom(type) || |
|||
node.Arguments.Count != 1) |
|||
return null; |
|||
|
|||
var lambda = Expression.Lambda<Func<TIn, object>>(node.Object!, rootExpression.Parameters); |
|||
var read = lambda.Compile(); |
|||
|
|||
return new InpcBindingTrigger<TIn>(index, read, CommonPropertyNames.IndexerName); |
|||
} |
|||
|
|||
void IWeakEventSubscriber<PropertyChangedEventArgs>.OnEvent( |
|||
object? sender, |
|||
WeakEvent ev, |
|||
PropertyChangedEventArgs e) |
|||
{ |
|||
if (e.PropertyName == _propertyName || string.IsNullOrEmpty(e.PropertyName)) |
|||
OnChanged(); |
|||
} |
|||
|
|||
protected override bool SubscribeCore(TIn root) |
|||
{ |
|||
var o = _read(root) as INotifyPropertyChanged; |
|||
_source = new(o); |
|||
|
|||
if (o is null) |
|||
return false; |
|||
|
|||
WeakEvents.PropertyChanged.Subscribe(o, this); |
|||
return true; |
|||
} |
|||
|
|||
protected override void UnsubscribeCore() |
|||
{ |
|||
if (_source?.TryGetTarget(out var o) == true) |
|||
WeakEvents.PropertyChanged.Unsubscribe(o, this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
internal abstract class TypedBindingTrigger<TIn> |
|||
{ |
|||
private readonly int _index; |
|||
private Action<int>? _changed; |
|||
|
|||
public TypedBindingTrigger(int index) => _index = index; |
|||
|
|||
|
|||
public bool Subscribe(TIn root, Action<int> changed) |
|||
{ |
|||
if (_changed is not null) |
|||
throw new AvaloniaInternalException("Trigger is already subscribed."); |
|||
|
|||
try |
|||
{ |
|||
var result = SubscribeCore(root); |
|||
_changed = changed; |
|||
return result; |
|||
} |
|||
catch |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public void Unsubscribe() |
|||
{ |
|||
_changed = null; |
|||
UnsubscribeCore(); |
|||
} |
|||
|
|||
protected void OnChanged() => _changed?.Invoke(_index); |
|||
protected abstract bool SubscribeCore(TIn root); |
|||
protected abstract void UnsubscribeCore(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue