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, }; } } }