using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Avalonia.Data
{
///
/// Defines the types of binding errors for a .
///
public enum BindingErrorType
{
///
/// There was no error.
///
None,
///
/// There was a binding error.
///
Error,
///
/// There was a data validation error.
///
DataValidationError,
}
///
/// Represents a binding notification that can be a valid binding value, or a binding or
/// data validation error.
///
///
/// This class is very similar to , but where
/// is used by typed bindings, this class is used to hold binding and data validation errors in
/// untyped bindings. As Avalonia moves towards using typed bindings by default we may want to remove
/// this class.
///
public class BindingNotification
{
///
/// A binding notification representing the null value.
///
public static readonly BindingNotification Null =
new BindingNotification(null);
///
/// A binding notification representing .
///
public static readonly BindingNotification UnsetValue =
new BindingNotification(AvaloniaProperty.UnsetValue);
private object? _value;
///
/// Initializes a new instance of the class.
///
/// The binding value.
public BindingNotification(object? value)
{
_value = value;
}
///
/// Initializes a new instance of the class.
///
/// The binding error.
/// The type of the binding error.
public BindingNotification(Exception error, BindingErrorType errorType)
{
if (errorType == BindingErrorType.None)
{
throw new ArgumentException($"'errorType' may not be None");
}
Error = error;
ErrorType = errorType;
_value = AvaloniaProperty.UnsetValue;
}
///
/// Initializes a new instance of the class.
///
/// The binding error.
/// The type of the binding error.
/// The fallback value.
public BindingNotification(Exception error, BindingErrorType errorType, object? fallbackValue)
: this(error, errorType)
{
_value = fallbackValue;
}
///
/// Gets the value that should be passed to the target when
/// is true.
///
///
/// If this property is read when is false then it will return
/// .
///
public object? Value => _value;
///
/// Gets a value indicating whether should be pushed to the target.
///
public bool HasValue => _value != AvaloniaProperty.UnsetValue;
///
/// Gets the error that occurred on the source, if any.
///
public Exception? Error { get; set; }
///
/// Gets the type of error that represents, if any.
///
public BindingErrorType ErrorType { get; set; }
///
/// Compares two instances of for equality.
///
/// The first instance.
/// The second instance.
/// true if the two instances are equal; otherwise false.
public static bool operator ==(BindingNotification? a, BindingNotification? b)
{
if (object.ReferenceEquals(a, b))
{
return true;
}
if (a is null || b is null)
{
return false;
}
return a.HasValue == b.HasValue &&
a.ErrorType == b.ErrorType &&
(!a.HasValue || object.Equals(a.Value, b.Value)) &&
(a.ErrorType == BindingErrorType.None || ExceptionEquals(a.Error, b.Error));
}
///
/// Compares two instances of for inequality.
///
/// The first instance.
/// The second instance.
/// true if the two instances are unequal; otherwise false.
public static bool operator !=(BindingNotification? a, BindingNotification? b)
{
return !(a == b);
}
///
/// Gets a value from an object that may be a .
///
/// The object.
/// The value.
///
/// If is a then returns the binding
/// notification's . If not, returns the object unchanged.
///
public static object? ExtractValue(object? o)
{
var notification = o as BindingNotification;
return notification is not null ? notification.Value : o;
}
///
/// Updates the value of an object that may be a .
///
/// The object that may be a binding notification.
/// The new value.
///
/// The updated binding notification if is a binding notification;
/// otherwise .
///
///
/// If is a then sets its value
/// to . If is a
/// then the value will first be extracted.
///
public static object? UpdateValue(object? o, object value)
{
if (o is BindingNotification n)
{
n.SetValue(ExtractValue(value));
return n;
}
return value;
}
///
/// Gets an exception from an object that may be a .
///
/// The object.
/// The value.
///
/// If is a then returns the binding
/// notification's . If not, returns the object unchanged.
///
public static object? ExtractError(object? o)
{
return o is BindingNotification notification ? notification.Error : o;
}
///
/// Compares an object to an instance of for equality.
///
/// The object to compare.
/// true if the two instances are equal; otherwise false.
public override bool Equals(object? obj)
{
return Equals(obj as BindingNotification);
}
///
/// Compares a value to an instance of for equality.
///
/// The value to compare.
/// true if the two instances are equal; otherwise false.
public bool Equals(BindingNotification? other)
{
return this == other;
}
///
/// Gets the hash code for this instance of .
///
/// A hash code.
public override int GetHashCode()
{
return base.GetHashCode();
}
///
/// Adds an error to the .
///
/// The error to add.
/// The error type.
public void AddError(Exception e, BindingErrorType type)
{
_ = e ?? throw new ArgumentNullException(nameof(e));
if (type == BindingErrorType.None)
throw new ArgumentException("BindingErrorType may not be None", nameof(type));
Error = Error != null ? new AggregateException(Error, e) : e;
if (type == BindingErrorType.Error || ErrorType == BindingErrorType.Error)
{
ErrorType = BindingErrorType.Error;
}
}
///
/// Removes the and makes return null.
///
public void ClearValue()
{
_value = AvaloniaProperty.UnsetValue;
}
///
/// Sets the .
///
public void SetValue(object? value)
{
_value = value;
}
///
public override string ToString()
{
switch (ErrorType)
{
case BindingErrorType.None:
return $"{{Value: {Value}}}";
default:
return HasValue ?
$"{{{ErrorType}: {Error}, Fallback: {Value}}}" :
$"{{{ErrorType}: {Error}}}";
}
}
private static bool ExceptionEquals(Exception? a, Exception? b)
{
return a?.GetType() == b?.GetType() &&
a?.Message == b?.Message;
}
}
internal static class BindingErrorTypeExtensions
{
public static BindingValueType ToBindingValueType(this BindingErrorType type)
{
return type switch
{
BindingErrorType.Error => BindingValueType.BindingError,
BindingErrorType.DataValidationError => BindingValueType.DataValidationError,
_ => BindingValueType.Value,
};
}
}
}