// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Reflection; using Avalonia.Utilities; namespace Avalonia.Data.Core.Plugins { /// /// Reads a property from a standard C# object that optionally supports the /// interface. /// public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin { /// public bool Match(object obj, string propertyName) => true; /// /// Starts monitoring the value of a property on an object. /// /// The object. /// The property name. /// /// An interface through which future interactions with the /// property will be made. /// public IPropertyAccessor Start(WeakReference reference, string propertyName) { Contract.Requires(reference != null); Contract.Requires(propertyName != null); reference.TryGetTarget(out object instance); var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName); if (p != null) { return new Accessor(reference, p); } else { var message = $"Could not find CLR property '{propertyName}' on '{instance}'"; var exception = new MissingMemberException(message); return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } } private class Accessor : PropertyAccessorBase, IWeakSubscriber { private readonly WeakReference _reference; private readonly PropertyInfo _property; private bool _eventRaised; public Accessor(WeakReference reference, PropertyInfo property) { Contract.Requires(reference != null); Contract.Requires(property != null); _reference = reference; _property = property; } public override Type PropertyType => _property.PropertyType; public override object Value { get { var o = GetReferenceTarget(); return (o != null) ? _property.GetValue(o) : null; } } public override bool SetValue(object value, BindingPriority priority) { if (_property.CanWrite) { _eventRaised = false; _property.SetValue(GetReferenceTarget(), value); if (!_eventRaised) { SendCurrentValue(); } return true; } return false; } void IWeakSubscriber.OnEvent(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { _eventRaised = true; SendCurrentValue(); } } protected override void SubscribeCore() { SubscribeToChanges(); SendCurrentValue(); } protected override void UnsubscribeCore() { var inpc = GetReferenceTarget() as INotifyPropertyChanged; if (inpc != null) { WeakSubscriptionManager.Unsubscribe( inpc, nameof(inpc.PropertyChanged), this); } } private object GetReferenceTarget() { _reference.TryGetTarget(out object target); return target; } private void SendCurrentValue() { try { var value = Value; PublishValue(value); } catch { } } private void SubscribeToChanges() { var inpc = GetReferenceTarget() as INotifyPropertyChanged; if (inpc != null) { WeakSubscriptionManager.Subscribe( inpc, nameof(inpc.PropertyChanged), this); } } } } }