@ -7,12 +7,14 @@ using Avalonia.Data.Core.Parsers;
using Avalonia.Data.Core.Plugins ;
using Avalonia.Data.Core.Plugins ;
using Avalonia.Reactive ;
using Avalonia.Reactive ;
# nullable enable
namespace Avalonia.Data.Core
namespace Avalonia.Data.Core
{
{
/// <summary>
/// <summary>
/// Observes and sets the value of an expression on an object.
/// Observes and sets the value of an expression on an object.
/// </summary>
/// </summary>
public class ExpressionObserver : LightweightObservableBase < object > , IDescription
public class ExpressionObserver : LightweightObservableBase < object? > , IDescription
{
{
/// <summary>
/// <summary>
/// An ordered collection of property accessor plugins that can be used to customize
/// An ordered collection of property accessor plugins that can be used to customize
@ -51,10 +53,10 @@ namespace Avalonia.Data.Core
private static readonly object UninitializedValue = new object ( ) ;
private static readonly object UninitializedValue = new object ( ) ;
private readonly ExpressionNode _ node ;
private readonly ExpressionNode _ node ;
private object _ root ;
private object? _ root ;
private IDisposable _ rootSubscription ;
private IDisposable ? _ rootSubscription ;
private WeakReference < object > _ value ;
private WeakReference < object? > ? _ value ;
private IReadOnlyList < ITransformNode > _ transformNodes ;
private IReadOnlyList < ITransformNode > ? _ transformNodes ;
/// <summary>
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -65,18 +67,13 @@ namespace Avalonia.Data.Core
/// A description of the expression.
/// A description of the expression.
/// </param>
/// </param>
public ExpressionObserver (
public ExpressionObserver (
object root ,
object? root ,
ExpressionNode node ,
ExpressionNode node ,
string description = null )
string? description = null )
{
{
if ( root = = AvaloniaProperty . UnsetValue )
{
root = null ;
}
_ node = node ;
_ node = node ;
Description = description ;
Description = description ;
_ root = new WeakReference < object > ( root ) ;
_ root = new WeakReference < object? > ( root = = AvaloniaProperty . UnsetValue ? null : root ) ;
}
}
/// <summary>
/// <summary>
@ -88,11 +85,11 @@ namespace Avalonia.Data.Core
/// A description of the expression.
/// A description of the expression.
/// </param>
/// </param>
public ExpressionObserver (
public ExpressionObserver (
IObservable < object > rootObservable ,
IObservable < object? > rootObservable ,
ExpressionNode node ,
ExpressionNode node ,
string description )
string? description )
{
{
Contract . Requires < ArgumentNullException > ( rootObservable ! = null ) ;
_ = rootObservable ? ? throw new ArgumentNullException ( nameof ( rootObservable ) ) ;
_ node = node ;
_ node = node ;
Description = description ;
Description = description ;
@ -109,16 +106,16 @@ namespace Avalonia.Data.Core
/// A description of the expression.
/// A description of the expression.
/// </param>
/// </param>
public ExpressionObserver (
public ExpressionObserver (
Func < object > rootGetter ,
Func < object? > rootGetter ,
ExpressionNode node ,
ExpressionNode node ,
IObservable < Unit > update ,
IObservable < Unit > update ,
string description )
string? description )
{
{
Contract . Requires < ArgumentNullException > ( rootGetter ! = null ) ;
_ = rootGetter ? ? throw new ArgumentNullException ( nameof ( rootGetter ) ) ;
Contract . Requires < ArgumentNullException > ( update ! = null ) ;
Description = description ;
Description = description ;
_ node = node ;
_ node = node ? ? throw new ArgumentNullException ( nameof ( rootGetter ) ) ;
_ node . Target = new WeakReference < object > ( rootGetter ( ) ) ;
_ node . Target = new WeakReference < object? > ( rootGetter ( ) ) ;
_ root = update . Select ( x = > rootGetter ( ) ) ;
_ root = update . Select ( x = > rootGetter ( ) ) ;
}
}
@ -133,10 +130,10 @@ namespace Avalonia.Data.Core
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
/// </param>
public static ExpressionObserver Create < T , U > (
public static ExpressionObserver Create < T , U > (
T root ,
T ? root ,
Expression < Func < T , U > > expression ,
Expression < Func < T , U > > expression ,
bool enableDataValidation = false ,
bool enableDataValidation = false ,
string description = null )
string? description = null )
{
{
return new ExpressionObserver ( root , Parse ( expression , enableDataValidation ) , description ? ? expression . ToString ( ) ) ;
return new ExpressionObserver ( root , Parse ( expression , enableDataValidation ) , description ? ? expression . ToString ( ) ) ;
}
}
@ -154,11 +151,12 @@ namespace Avalonia.Data.Core
IObservable < T > rootObservable ,
IObservable < T > rootObservable ,
Expression < Func < T , U > > expression ,
Expression < Func < T , U > > expression ,
bool enableDataValidation = false ,
bool enableDataValidation = false ,
string description = null )
string? description = null )
{
{
Contract . Requires < ArgumentNullException > ( rootObservable ! = null ) ;
_ = rootObservable ? ? throw new ArgumentNullException ( nameof ( rootObservable ) ) ;
return new ExpressionObserver (
return new ExpressionObserver (
rootObservable . Select ( o = > ( object ) o ) ,
rootObservable . Select ( o = > ( object? ) o ) ,
Parse ( expression , enableDataValidation ) ,
Parse ( expression , enableDataValidation ) ,
description ? ? expression . ToString ( ) ) ;
description ? ? expression . ToString ( ) ) ;
}
}
@ -178,9 +176,9 @@ namespace Avalonia.Data.Core
Expression < Func < T , U > > expression ,
Expression < Func < T , U > > expression ,
IObservable < Unit > update ,
IObservable < Unit > update ,
bool enableDataValidation = false ,
bool enableDataValidation = false ,
string description = null )
string? description = null )
{
{
Contract . Requires < ArgumentNullException > ( rootGetter ! = null ) ;
_ = rootGetter ? ? throw new ArgumentNullException ( nameof ( rootGetter ) ) ;
return new ExpressionObserver (
return new ExpressionObserver (
( ) = > rootGetter ( ) ,
( ) = > rootGetter ( ) ,
@ -218,7 +216,7 @@ namespace Avalonia.Data.Core
/// before setting the target value can work, as setting the value requires the
/// before setting the target value can work, as setting the value requires the
/// expression to be evaluated.
/// expression to be evaluated.
/// </returns>
/// </returns>
public bool SetValue ( object value , BindingPriority priority = BindingPriority . LocalValue )
public bool SetValue ( object? value , BindingPriority priority = BindingPriority . LocalValue )
{
{
if ( Leaf is SettableNode settable )
if ( Leaf is SettableNode settable )
{
{
@ -238,18 +236,18 @@ namespace Avalonia.Data.Core
/// <summary>
/// <summary>
/// Gets a description of the expression being observed.
/// Gets a description of the expression being observed.
/// </summary>
/// </summary>
public string Description { get ; }
public string? Description { get ; }
/// <summary>
/// <summary>
/// Gets the expression being observed.
/// Gets the expression being observed.
/// </summary>
/// </summary>
public string Expression { get ; }
public string? Expression { get ; }
/// <summary>
/// <summary>
/// Gets the type of the expression result or null if the expression could not be
/// Gets the type of the expression result or null if the expression could not be
/// evaluated.
/// evaluated.
/// </summary>
/// </summary>
public Type ResultType = > ( Leaf as SettableNode ) ? . PropertyType ;
public Type ? ResultType = > ( Leaf as SettableNode ) ? . PropertyType ;
/// <summary>
/// <summary>
/// Gets the leaf node.
/// Gets the leaf node.
@ -278,7 +276,7 @@ namespace Avalonia.Data.Core
_ node . Unsubscribe ( ) ;
_ node . Unsubscribe ( ) ;
}
}
protected override void Subscribed ( IObserver < object > observer , bool first )
protected override void Subscribed ( IObserver < object? > observer , bool first )
{
{
if ( ! first & & _ value ! = null & & _ value . TryGetTarget ( out var value ) )
if ( ! first & & _ value ! = null & & _ value . TryGetTarget ( out var value ) )
{
{
@ -296,21 +294,21 @@ namespace Avalonia.Data.Core
if ( _ root is IObservable < object > observable )
if ( _ root is IObservable < object > observable )
{
{
_ rootSubscription = observable . Subscribe (
_ rootSubscription = observable . Subscribe (
x = > _ node . Target = new WeakReference < object > ( x ! = AvaloniaProperty . UnsetValue ? x : null ) ,
x = > _ node . Target = new WeakReference < object? > ( x ! = AvaloniaProperty . UnsetValue ? x : null ) ,
x = > PublishCompleted ( ) ,
x = > PublishCompleted ( ) ,
( ) = > PublishCompleted ( ) ) ;
( ) = > PublishCompleted ( ) ) ;
}
}
else
else
{
{
_ node . Target = ( WeakReference < object > ) _ root ;
_ node . Target = ( WeakReference < object? > ) _ root ! ;
}
}
}
}
private void ValueChanged ( object value )
private void ValueChanged ( object? value )
{
{
var broken = BindingNotification . ExtractError ( value ) as MarkupBindingChainException ;
var broken = BindingNotification . ExtractError ( value ) as MarkupBindingChainException ;
broken ? . Commit ( Description ) ;
broken ? . Commit ( Description ? ? "{empty}" ) ;
_ value = new WeakReference < object > ( value ) ;
_ value = new WeakReference < object? > ( value ) ;
PublishNext ( value ) ;
PublishNext ( value ) ;
}
}
}
}