Browse Source
These are used to extensibly handle special values like `Task` and `IObservable<>`. Previously this was baked into the expression observer architecture with a TODO comment saying that it needs to be extensible.pull/691/head
9 changed files with 323 additions and 22 deletions
@ -0,0 +1,29 @@ |
|||
// 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; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Defines how values are observed by an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public interface IValuePlugin |
|||
{ |
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </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); |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
IObservable<object> Start(WeakReference reference); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// 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.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using System.Windows.Input; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="IObservable{T}"/>s in an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public class ObservableValuePlugin : IValuePlugin |
|||
{ |
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </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) |
|||
{ |
|||
var target = reference.Target; |
|||
|
|||
// ReactiveCommand is an IObservable but we want to bind to it, not its value.
|
|||
return target is IObservable<object> && !(target is ICommand); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object> Start(WeakReference reference) |
|||
{ |
|||
return reference.Target as IObservable<object>; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
// 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.Reactive.Concurrency; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
/// <summary>
|
|||
/// Handles binding to <see cref="Task"/>s in an <see cref="ExpressionObserver"/>.
|
|||
/// </summary>
|
|||
public class TaskValuePlugin : IValuePlugin |
|||
{ |
|||
/// <summary>
|
|||
/// Checks whether this plugin handles the specified value.
|
|||
/// </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; |
|||
|
|||
/// <summary>
|
|||
/// Starts producing output based on the specified value.
|
|||
/// </summary>
|
|||
/// <param name="reference">A weak reference to the object.</param>
|
|||
/// <returns>
|
|||
/// An observable that produces the output for the value.
|
|||
/// </returns>
|
|||
public virtual IObservable<object> Start(WeakReference reference) |
|||
{ |
|||
var task = reference.Target as Task; |
|||
|
|||
if (task != null) |
|||
{ |
|||
var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
case TaskStatus.Faulted: |
|||
return HandleCompleted(task); |
|||
default: |
|||
var subject = new Subject<object>(); |
|||
task.ContinueWith( |
|||
x => HandleCompleted(task).Subscribe(subject), |
|||
TaskScheduler.FromCurrentSynchronizationContext()) |
|||
.ConfigureAwait(false); |
|||
return subject; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Observable.Empty<object>(); |
|||
} |
|||
|
|||
protected IObservable<object> HandleCompleted(Task task) |
|||
{ |
|||
var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result"); |
|||
|
|||
if (resultProperty != null) |
|||
{ |
|||
switch (task.Status) |
|||
{ |
|||
case TaskStatus.RanToCompletion: |
|||
return Observable.Return(resultProperty.GetValue(task)); |
|||
case TaskStatus.Faulted: |
|||
return Observable.Return(new BindingNotification(task.Exception, BindingErrorType.Error)); |
|||
default: |
|||
throw new AvaloniaInternalException("HandleCompleted called for non-completed Task."); |
|||
} |
|||
} |
|||
|
|||
return Observable.Empty<object>(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue