csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.7 KiB
164 lines
4.7 KiB
// Copyright (c) The Perspex 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.Reactive.Linq;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Input;
|
|
|
|
namespace Perspex.Markup.Binding
|
|
{
|
|
internal class PropertyAccessorNode : ExpressionNode
|
|
{
|
|
private PropertyInfo _propertyInfo;
|
|
private IDisposable _subscription;
|
|
|
|
public PropertyAccessorNode(string propertyName)
|
|
{
|
|
PropertyName = propertyName;
|
|
}
|
|
|
|
public string PropertyName { get; }
|
|
|
|
public override bool SetValue(object value)
|
|
{
|
|
if (Next != null)
|
|
{
|
|
return Next.SetValue(value);
|
|
}
|
|
else
|
|
{
|
|
if (_propertyInfo != null && _propertyInfo.CanWrite)
|
|
{
|
|
_propertyInfo.SetValue(Target, value);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected override void SubscribeAndUpdate(object target)
|
|
{
|
|
bool set = false;
|
|
|
|
if (target != null)
|
|
{
|
|
_propertyInfo = FindProperty(target, PropertyName);
|
|
|
|
if (_propertyInfo != null)
|
|
{
|
|
ReadValue(target);
|
|
set = true;
|
|
|
|
var inpc = target as INotifyPropertyChanged;
|
|
|
|
if (inpc != null)
|
|
{
|
|
inpc.PropertyChanged += PropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_propertyInfo = null;
|
|
}
|
|
|
|
if (!set)
|
|
{
|
|
CurrentValue = ExpressionValue.None;
|
|
}
|
|
}
|
|
|
|
protected override void Unsubscribe(object target)
|
|
{
|
|
var inpc = target as INotifyPropertyChanged;
|
|
|
|
if (inpc != null)
|
|
{
|
|
inpc.PropertyChanged -= PropertyChanged;
|
|
}
|
|
}
|
|
|
|
private static PropertyInfo FindProperty(object target, string propertyName)
|
|
{
|
|
var typeInfo = target.GetType().GetTypeInfo();
|
|
|
|
do
|
|
{
|
|
var result = typeInfo.GetDeclaredProperty(propertyName);
|
|
|
|
if (result != null)
|
|
{
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
typeInfo = typeInfo.BaseType?.GetTypeInfo();
|
|
}
|
|
} while (typeInfo != null);
|
|
|
|
return null;
|
|
}
|
|
|
|
private void ReadValue(object target)
|
|
{
|
|
var value = _propertyInfo.GetValue(target);
|
|
var observable = value as IObservable<object>;
|
|
var command = value as ICommand;
|
|
var task = value as Task;
|
|
bool set = false;
|
|
|
|
// ReactiveCommand is an IObservable but we want to bind to it, not its value.
|
|
if (observable != null && command == null)
|
|
{
|
|
CurrentValue = ExpressionValue.None;
|
|
set = true;
|
|
_subscription = observable
|
|
.ObserveOn(SynchronizationContext.Current)
|
|
.Subscribe(x => CurrentValue = new ExpressionValue(x));
|
|
}
|
|
else if (task != null)
|
|
{
|
|
var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result");
|
|
|
|
if (resultProperty != null)
|
|
{
|
|
if (task.Status == TaskStatus.RanToCompletion)
|
|
{
|
|
CurrentValue = new ExpressionValue(resultProperty.GetValue(task));
|
|
set = true;
|
|
}
|
|
else
|
|
{
|
|
task.ContinueWith(
|
|
x => CurrentValue = new ExpressionValue(resultProperty.GetValue(task)),
|
|
TaskScheduler.FromCurrentSynchronizationContext())
|
|
.ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CurrentValue = new ExpressionValue(value);
|
|
set = true;
|
|
}
|
|
|
|
if (!set)
|
|
{
|
|
CurrentValue = ExpressionValue.None;
|
|
}
|
|
}
|
|
|
|
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == PropertyName)
|
|
{
|
|
ReadValue(sender);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|