using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Data
{
///
/// Describes the type of a .
///
[Flags]
public enum BindingValueType
{
///
/// An unset value: the target property will revert to its unbound state until a new
/// binding value is produced.
///
UnsetValue = 0,
///
/// Do nothing: the binding value will be ignored.
///
DoNothing = 1,
///
/// A simple value.
///
Value = 2 | HasValue,
///
/// A binding error, such as a missing source property.
///
BindingError = 3 | HasError,
///
/// A data validation error.
///
DataValidationError = 4 | HasError,
///
/// A binding error with a fallback value.
///
BindingErrorWithFallback = BindingError | HasValue,
///
/// A data validation error with a fallback value.
///
DataValidationErrorWithFallback = DataValidationError | HasValue,
TypeMask = 0x00ff,
HasValue = 0x0100,
HasError = 0x0200,
}
///
/// A value passed into a binding.
///
/// The value type.
///
/// The avalonia binding system is typed, and as such additional state is stored in this
/// structure. A binding value can be in a number of states, described by the
/// property:
///
/// - : a simple value
/// - : the target property will revert to its unbound
/// state until a new binding value is produced. Represented by
/// in an untyped context
/// - : the binding value will be ignored. Represented
/// by in an untyped context
/// - : a binding error, such as a missing source
/// property, with an optional fallback value
/// - : a data validation error, with an
/// optional fallback value
///
/// To create a new binding value you can:
///
/// - For a simple value, call the constructor or use an implicit
/// conversion from
/// - For an unset value, use or simply `default`
/// - For other types, call one of the static factory methods
///
public readonly struct BindingValue
{
private readonly T _value;
///
/// Initializes a new instance of the struct with a type of
///
///
/// The value.
public BindingValue(T value)
{
ValidateValue(value);
_value = value;
Type = BindingValueType.Value;
Error = null;
}
private BindingValue(BindingValueType type, T? value, Exception? error)
{
_value = value!;
Type = type;
Error = error;
}
///
/// Gets a value indicating whether the binding value represents either a binding or data
/// validation error.
///
public bool HasError => Type.HasAllFlags(BindingValueType.HasError);
///
/// Gets a value indicating whether the binding value has a value.
///
public bool HasValue => Type.HasAllFlags(BindingValueType.HasValue);
///
/// Gets the type of the binding value.
///
public BindingValueType Type { get; }
///
/// Gets the binding value or fallback value.
///
///
/// is false.
///
public T Value => HasValue ? _value! : throw new InvalidOperationException("BindingValue has no value.");
///
/// Gets the binding or data validation error.
///
public Exception? Error { get; }
///
/// Converts the binding value to an .
///
///
public Optional ToOptional() => HasValue ? new Optional(_value) : default;
///
public override string ToString() => HasError ? $"Error: {Error!.Message}" : _value?.ToString() ?? "(null)";
///
/// Converts the value to untyped representation, using ,
/// and where
/// appropriate.
///
/// The untyped representation of the binding value.
public object? ToUntyped()
{
return Type switch
{
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
BindingValueType.DoNothing => BindingOperations.DoNothing,
BindingValueType.Value => _value,
BindingValueType.BindingError =>
new BindingNotification(Error!, BindingErrorType.Error),
BindingValueType.BindingErrorWithFallback =>
new BindingNotification(Error!, BindingErrorType.Error, Value),
BindingValueType.DataValidationError =>
new BindingNotification(Error!, BindingErrorType.DataValidationError),
BindingValueType.DataValidationErrorWithFallback =>
new BindingNotification(Error!, BindingErrorType.DataValidationError, Value),
_ => throw new NotSupportedException("Invalid BindingValueType."),
};
}
///
/// Returns a new binding value with the specified value.
///
/// The new value.
/// The new binding value.
///
/// The binding type is or
/// .
///
public BindingValue WithValue(T value)
{
if (Type == BindingValueType.DoNothing)
{
throw new InvalidOperationException("Cannot add value to DoNothing binding value.");
}
var type = Type == BindingValueType.UnsetValue ? BindingValueType.Value : Type;
return new BindingValue(type | BindingValueType.HasValue, value, Error);
}
///
/// Gets the value of the binding value if present, otherwise the default value.
///
/// The value.
public T? GetValueOrDefault() => HasValue ? _value : default;
///
/// Gets the value of the binding value if present, otherwise a default value.
///
/// The default value.
/// The value.
public T? GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
///
/// Gets the value if present, otherwise the default value.
///
///
/// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type.
///
public TResult? GetValueOrDefault()
{
return HasValue ?
_value is TResult result ? result : default
: default;
}
///
/// Gets the value of the binding value if present, otherwise a default value.
///
/// The default value.
///
/// The value if present and of the correct type, `default(TResult)` if the value is
/// present but not of the correct type or null, or if the
/// value is not present.
///
public TResult? GetValueOrDefault(TResult defaultValue)
{
return HasValue ?
_value is TResult result ? result : default
: defaultValue;
}
///
/// Creates a from an object, handling the special values
/// , and
/// .
///
/// The untyped value.
/// The typed binding value.
public static BindingValue FromUntyped(object? value)
{
return FromUntyped(value, typeof(T));
}
///
/// Creates a from an object, handling the special values
/// , and
/// .
///
/// The untyped value.
/// The runtime target type.
/// The typed binding value.
public static BindingValue FromUntyped(object? value, Type targetType)
{
if (value == AvaloniaProperty.UnsetValue)
return Unset;
else if (value == BindingOperations.DoNothing)
return DoNothing;
var type = BindingValueType.Value;
T? v = default;
Exception? error = null;
List? errors = null;
if (value is BindingNotification n)
{
error = n.Error;
type = n.ErrorType switch
{
BindingErrorType.Error => BindingValueType.BindingError,
BindingErrorType.DataValidationError => BindingValueType.DataValidationError,
_ => BindingValueType.Value,
};
if (n.HasValue)
type |= BindingValueType.HasValue;
value = n.Value;
}
if ((type & BindingValueType.HasValue) != 0)
{
if (TypeUtilities.TryConvertImplicit(targetType, value, out var typed))
v = (T)typed!;
else
{
var e = new InvalidCastException(
$"Unable to convert object '{value ?? "(null)"}' " +
$"of type '{value?.GetType()}' to type '{targetType}'.");
if (error is null)
error = e;
else
{
errors ??= new List() { error };
errors.Add(e);
}
type = BindingValueType.BindingError;
}
}
if (errors is not null)
error = new AggregateException(errors);
return new BindingValue(type, v, error);
}
///
/// Creates a binding value from an instance of the underlying value type.
///
/// The value.
public static implicit operator BindingValue(T value) => new BindingValue(value);
///
/// Creates a binding value from an .
///
/// The optional value.
public static implicit operator BindingValue(Optional optional)
{
return optional.HasValue ? optional.Value : Unset;
}
///
/// Returns a binding value with a type of .
///
public static BindingValue Unset => new BindingValue(BindingValueType.UnsetValue, default, null);
///
/// Returns a binding value with a type of .
///
public static BindingValue DoNothing => new BindingValue(BindingValueType.DoNothing, default, null);
///
/// Returns a binding value with a type of .
///
/// The binding error.
public static BindingValue BindingError(Exception e)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(BindingValueType.BindingError, default, e);
}
///
/// Returns a binding value with a type of .
///
/// The binding error.
/// The fallback value.
public static BindingValue BindingError(Exception e, T fallbackValue)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(BindingValueType.BindingErrorWithFallback, fallbackValue, e);
}
///
/// Returns a binding value with a type of or
/// .
///
/// The binding error.
/// The fallback value.
public static BindingValue BindingError(Exception e, Optional fallbackValue)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(
fallbackValue.HasValue ?
BindingValueType.BindingErrorWithFallback :
BindingValueType.BindingError,
fallbackValue.HasValue ? fallbackValue.Value : default,
e);
}
///
/// Returns a binding value with a type of .
///
/// The data validation error.
public static BindingValue DataValidationError(Exception e)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(BindingValueType.DataValidationError, default, e);
}
///
/// Returns a binding value with a type of .
///
/// The data validation error.
/// The fallback value.
public static BindingValue DataValidationError(Exception e, T fallbackValue)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(BindingValueType.DataValidationErrorWithFallback, fallbackValue, e);
}
///
/// Returns a binding value with a type of or
/// .
///
/// The binding error.
/// The fallback value.
public static BindingValue DataValidationError(Exception e, Optional fallbackValue)
{
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue(
fallbackValue.HasValue ?
BindingValueType.DataValidationErrorWithFallback :
BindingValueType.DataValidationError,
fallbackValue.HasValue ? fallbackValue.Value : default,
e);
}
[Conditional("DEBUG")]
private static void ValidateValue(T value)
{
if (value is UnsetValueType)
{
throw new InvalidOperationException("AvaloniaValue.UnsetValue is not a valid value for BindingValue<>.");
}
if (value is DoNothingType)
{
throw new InvalidOperationException("BindingOperations.DoNothing is not a valid value for BindingValue<>.");
}
if (value is BindingValue