Browse Source

Allow binding to CLR properties on AvaloniaObject.

`AvaloniaPropertyAccessorPlugin` now checks that the requested property is available as a `AvaloniaProperty` on the target, and if not returns a `false` match. This means that the binding will be passed on to the `InpcPropertyAccessorPlugin`.

There was a problem in `WeakSubscriptionManager` in that an `AvaloniaObject` exposes 2 `PropertyChanged` events and `WeakSubscriptionManager` was picking up the `AvaloniaObject` one. Had to make `WeakSubscriptionManager.Subscribe` generic on the target parameter so that the requested event (`INotifyPropertyChanged.PropertyChanged`) was found instead.
pull/1045/head
Steven Kirk 9 years ago
parent
commit
24acd4cd24
  1. 5
      src/Avalonia.Base/Utilities/WeakObservable.cs
  2. 23
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  3. 8
      src/Markup/Avalonia.Markup/Data/IndexerNode.cs
  4. 10
      src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
  5. 7
      src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs
  6. 6
      src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
  7. 2
      src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs

5
src/Avalonia.Base/Utilities/WeakObservable.cs

@ -16,12 +16,13 @@ namespace Avalonia.Utilities
/// Converts a .NET event conforming to the standard .NET event pattern into an observable
/// sequence, subscribing weakly.
/// </summary>
/// <typeparam name="TTarget">The type of target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event args.</typeparam>
/// <param name="target">Object instance that exposes the event to convert.</param>
/// <param name="eventName">Name of the event to convert.</param>
/// <returns></returns>
public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TEventArgs>(
object target,
public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
TTarget target,
string eventName)
where TEventArgs : EventArgs
{

23
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@ -21,18 +21,20 @@ namespace Avalonia.Utilities
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
public static void Subscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber)
where T : EventArgs
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event args.</typeparam>
public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
where TEventArgs : EventArgs
{
var dic = SubscriptionTypeStorage<T>.Subscribers.GetOrCreateValue(target);
Subscription<T> sub;
var dic = SubscriptionTypeStorage<TEventArgs>.Subscribers.GetOrCreateValue(target);
Subscription<TEventArgs> sub;
if (!dic.TryGetValue(eventName, out sub))
{
dic[eventName] = sub = new Subscription<T>(dic, target, eventName);
dic[eventName] = sub = new Subscription<TEventArgs>(dic, typeof(TTarget), target, eventName);
}
sub.Add(new WeakReference<IWeakSubscriber<T>>(subscriber));
sub.Add(new WeakReference<IWeakSubscriber<TEventArgs>>(subscriber));
}
/// <summary>
@ -84,19 +86,18 @@ namespace Avalonia.Utilities
private WeakReference<IWeakSubscriber<T>>[] _data = new WeakReference<IWeakSubscriber<T>>[16];
private int _count = 0;
public Subscription(SubscriptionDic<T> sdic, object target, string eventName)
public Subscription(SubscriptionDic<T> sdic, Type targetType, object target, string eventName)
{
_sdic = sdic;
_target = target;
_eventName = eventName;
var t = target.GetType();
Dictionary<string, EventInfo> evDic;
if (!Accessors.TryGetValue(t, out evDic))
Accessors[t] = evDic = new Dictionary<string, EventInfo>();
if (!Accessors.TryGetValue(targetType, out evDic))
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
if (!evDic.TryGetValue(eventName, out _info))
{
var ev = t.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
if (ev == null)
{

8
src/Markup/Avalonia.Markup/Data/IndexerNode.cs

@ -33,8 +33,8 @@ namespace Avalonia.Markup.Data
if (incc != null)
{
inputs.Add(WeakObservable.FromEventPattern<NotifyCollectionChangedEventArgs>(
target,
inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
incc,
nameof(incc.CollectionChanged))
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
@ -42,8 +42,8 @@ namespace Avalonia.Markup.Data
if (inpc != null)
{
inputs.Add(WeakObservable.FromEventPattern<PropertyChangedEventArgs>(
target,
inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>(
inpc,
nameof(inpc.PropertyChanged))
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));

10
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -13,7 +13,15 @@ namespace Avalonia.Markup.Data.Plugins
public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference) => reference.Target is AvaloniaObject;
public bool Match(object obj, string propertyName)
{
if (obj is AvaloniaObject a)
{
return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null;
}
return false;
}
/// <summary>
/// Starts monitoring the value of a property on an object.

7
src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs

@ -14,9 +14,10 @@ namespace Avalonia.Markup.Data.Plugins
/// <summary>
/// Checks whether this plugin can handle accessing the properties of the specified object.
/// </summary>
/// <param name="reference">A weak reference to the object.</param>
/// <returns>True if the plugin can handle the object; otherwise false.</returns>
bool Match(WeakReference reference);
/// <param name="obj">The object.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>True if the plugin can handle the property on the object; otherwise false.</returns>
bool Match(object obj, string propertyName);
/// <summary>
/// Starts monitoring the value of a property on an object.

6
src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs

@ -19,7 +19,7 @@ namespace Avalonia.Markup.Data.Plugins
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference) => true;
public bool Match(object obj, string propertyName) => true;
/// <summary>
/// Starts monitoring the value of a property on an object.
@ -36,7 +36,7 @@ namespace Avalonia.Markup.Data.Plugins
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(_ => _.Name == propertyName);
var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName);
if (p != null)
{
@ -138,7 +138,7 @@ namespace Avalonia.Markup.Data.Plugins
if (inpc != null)
{
WeakSubscriptionManager.Subscribe<PropertyChangedEventArgs>(
WeakSubscriptionManager.Subscribe(
inpc,
nameof(inpc.PropertyChanged),
this);

2
src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs

@ -37,7 +37,7 @@ namespace Avalonia.Markup.Data
protected override IObservable<object> StartListeningCore(WeakReference reference)
{
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference));
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null)

Loading…
Cancel
Save