diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 75b5b9da29..fd5aaf97aa 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -43,7 +43,7 @@
Properties\SharedAssemblyInfo.cs
-
+
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index e6b88c71f8..35dae50d27 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -379,12 +379,11 @@ namespace Avalonia
}
subscription = source
- .Where(x => !(x is IValidationStatus))
.Select(x => CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => _directBindings.Remove(subscription))
.Subscribe(x => DirectBindingSet(property, x));
validationSubcription = source
- .OfType()
+ .OfType()
.Subscribe(x => DataValidationChanged(property, x));
_directBindings.Add(subscription);
@@ -487,7 +486,7 @@ namespace Avalonia
}
///
- void IPriorityValueOwner.DataValidationChanged(PriorityValue sender, IValidationStatus status)
+ void IPriorityValueOwner.DataValidationChanged(PriorityValue sender, BindingNotification status)
{
var property = sender.Property;
DataValidationChanged(property, status);
@@ -623,14 +622,14 @@ namespace Avalonia
///
/// Tries to cast a value to a type, taking into account that the value may be a
- /// .
+ /// .
///
/// The value.
/// The type.
- /// The cast value, or a .
+ /// The cast value, or a .
private static object CastOrDefault(object value, Type type)
{
- var error = value as BindingError;
+ var error = value as BindingNotification;
if (error == null)
{
@@ -674,26 +673,29 @@ namespace Avalonia
///
private void DirectBindingSet(AvaloniaProperty property, object value)
{
- var error = value as BindingError;
+ var notification = value as BindingNotification;
- if (error == null)
+ if (notification == null)
{
SetValue(property, value);
}
else
{
- if (error.UseFallbackValue)
+ if (notification.HasValue)
{
- SetValue(property, error.FallbackValue);
+ SetValue(property, notification.Value);
}
- Logger.Error(
- LogArea.Binding,
- this,
- "Error binding to {Target}.{Property}: {Message}",
- this,
- property,
- error.Exception.Message);
+ if (notification.ErrorType == BindingErrorType.Error)
+ {
+ Logger.Error(
+ LogArea.Binding,
+ this,
+ "Error binding to {Target}.{Property}: {Message}",
+ this,
+ property,
+ notification.Error.Message);
+ }
}
}
diff --git a/src/Avalonia.Base/Data/BindingError.cs b/src/Avalonia.Base/Data/BindingError.cs
deleted file mode 100644
index 5157586a49..0000000000
--- a/src/Avalonia.Base/Data/BindingError.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// 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.Data
-{
- ///
- /// Represents a recoverable binding error.
- ///
- ///
- /// When produced by a binding source observable, informs the binding system that an error
- /// occurred. It can also provide an optional fallback value to be pushed to the binding
- /// target.
- ///
- /// Instead of using , one could simply not push a value (in the
- /// case of a no fallback value) or push a fallback value, but BindingError also causes an
- /// error to be logged with the correct binding target.
- ///
- public class BindingError
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// An exception describing the binding error.
- public BindingError(Exception exception)
- {
- Exception = exception;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// An exception describing the binding error.
- /// The fallback value.
- public BindingError(Exception exception, object fallbackValue)
- {
- Exception = exception;
- FallbackValue = fallbackValue;
- UseFallbackValue = true;
- }
-
- ///
- /// Gets the exception describing the binding error.
- ///
- public Exception Exception { get; }
-
- ///
- /// Get the fallback value.
- ///
- public object FallbackValue { get; }
-
- ///
- /// Get a value indicating whether the fallback value should be pushed to the binding
- /// target.
- ///
- public bool UseFallbackValue { get; }
- }
-}
diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs
new file mode 100644
index 0000000000..60387778fc
--- /dev/null
+++ b/src/Avalonia.Base/Data/BindingNotification.cs
@@ -0,0 +1,169 @@
+// 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.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.
+ ///
+ public class BindingNotification : IValidationStatus
+ {
+ ///
+ /// 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);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The binding value.
+ public BindingNotification(object value)
+ {
+ Value = value;
+ HasValue = true;
+ }
+
+ ///
+ /// 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;
+ }
+
+ ///
+ /// 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)
+ {
+ Value = fallbackValue;
+ HasValue = true;
+ }
+
+ ///
+ /// Gets the value that should be passed to the target when
+ /// is true.
+ ///
+ public object Value { get; }
+
+ ///
+ /// Gets a value indicating whether should be pushed to the target.
+ ///
+ public bool HasValue { get; }
+
+ ///
+ /// Gets the error that occurred on the source, if any.
+ ///
+ public Exception Error { get; }
+
+ ///
+ /// Gets the type of error that represents, if any.
+ ///
+ public BindingErrorType ErrorType { get; }
+
+ bool IValidationStatus.IsValid => ErrorType == BindingErrorType.None;
+
+ public static bool operator ==(BindingNotification a, BindingNotification b)
+ {
+ if (object.ReferenceEquals(a, b))
+ {
+ return true;
+ }
+
+ if ((object)a == null || (object)b == null)
+ {
+ return false;
+ }
+
+ return a.HasValue == b.HasValue &&
+ a.ErrorType == b.ErrorType &&
+ (!a.HasValue || object.Equals(a.Value, b.Value)) &&
+ (a.ErrorType == BindingErrorType.None || object.Equals(a.Error, b.Error));
+ }
+
+ public static bool operator !=(BindingNotification a, BindingNotification b)
+ {
+ return !(a == b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as BindingNotification);
+ }
+
+ public bool Equals(BindingNotification other)
+ {
+ return this == other;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ public BindingNotification WithError(Exception e)
+ {
+ if (e == null)
+ {
+ return this;
+ }
+
+ if (Error != null)
+ {
+ e = new AggregateException(Error, e);
+ }
+
+ if (HasValue)
+ {
+ return new BindingNotification(e, BindingErrorType.Error, Value);
+ }
+ else
+ {
+ return new BindingNotification(e, BindingErrorType.Error, Value);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/IPriorityValueOwner.cs b/src/Avalonia.Base/IPriorityValueOwner.cs
index 743eba4453..b5a0c0abc1 100644
--- a/src/Avalonia.Base/IPriorityValueOwner.cs
+++ b/src/Avalonia.Base/IPriorityValueOwner.cs
@@ -23,6 +23,6 @@ namespace Avalonia
///
/// The source of the change.
/// The validation status.
- void DataValidationChanged(PriorityValue sender, IValidationStatus status);
+ void DataValidationChanged(PriorityValue sender, BindingNotification status);
}
}
diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs
index 3368f45b82..85c8c7e55c 100644
--- a/src/Avalonia.Base/PriorityBindingEntry.cs
+++ b/src/Avalonia.Base/PriorityBindingEntry.cs
@@ -93,22 +93,28 @@ namespace Avalonia
private void ValueChanged(object value)
{
- var bindingError = value as BindingError;
+ var notification = value as BindingNotification;
- if (bindingError != null)
+ if (notification != null)
{
- _owner.Error(this, bindingError);
+ if (notification.ErrorType == BindingErrorType.Error)
+ {
+ _owner.Error(this, notification);
+ }
+ else if (notification.ErrorType == BindingErrorType.DataValidationError)
+ {
+ _owner.Validation(this, notification);
+ }
+
+ if (notification.HasValue)
+ {
+ Value = notification.Value;
+ _owner.Changed(this);
+ }
}
-
- var validationStatus = value as IValidationStatus;
-
- if (validationStatus != null)
- {
- _owner.Validation(this, validationStatus);
- }
- else if (bindingError == null || bindingError.UseFallbackValue)
+ else
{
- Value = bindingError == null ? value : bindingError.FallbackValue;
+ Value = value;
_owner.Changed(this);
}
}
diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs
index c0460a775b..b056dd38b7 100644
--- a/src/Avalonia.Base/PriorityLevel.cs
+++ b/src/Avalonia.Base/PriorityLevel.cs
@@ -159,7 +159,7 @@ namespace Avalonia
///
/// The entry that completed.
/// The error.
- public void Error(PriorityBindingEntry entry, BindingError error)
+ public void Error(PriorityBindingEntry entry, BindingNotification error)
{
_owner.LevelError(this, error);
}
@@ -169,7 +169,7 @@ namespace Avalonia
///
/// The entry that completed.
/// The validation status.
- public void Validation(PriorityBindingEntry entry, IValidationStatus validationStatus)
+ public void Validation(PriorityBindingEntry entry, BindingNotification validationStatus)
{
_owner.LevelValidation(this, validationStatus);
}
diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs
index 20434bc97d..715fca3915 100644
--- a/src/Avalonia.Base/PriorityValue.cs
+++ b/src/Avalonia.Base/PriorityValue.cs
@@ -184,7 +184,7 @@ namespace Avalonia
///
/// The priority level of the changed entry.
/// The validation status.
- public void LevelValidation(PriorityLevel priorityLevel, IValidationStatus validationStatus)
+ public void LevelValidation(PriorityLevel priorityLevel, BindingNotification validationStatus)
{
_owner.DataValidationChanged(this, validationStatus);
}
@@ -194,7 +194,7 @@ namespace Avalonia
///
/// The priority level of the changed entry.
/// The binding error.
- public void LevelError(PriorityLevel level, BindingError error)
+ public void LevelError(PriorityLevel level, BindingNotification error)
{
Logger.Log(
LogEventLevel.Error,
@@ -203,7 +203,7 @@ namespace Avalonia
"Error binding to {Target}.{Property}: {Message}",
_owner,
Property,
- error.Exception.Message);
+ error.Error.Message);
}
///
diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
index be4287605c..84ba432753 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
@@ -57,7 +57,7 @@ namespace Avalonia.Markup.Xaml.Templates
{
return null;
}
- else if (result is BindingError)
+ else if (result is BindingNotification)
{
return null;
}
diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
index 90a654d9e6..b61261365b 100644
--- a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
+++ b/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs
@@ -108,14 +108,14 @@ namespace Avalonia.Markup.Data
protected virtual void SendValidationStatus(IValidationStatus status)
{
//Even if elements only bound to sub-values, send validation changes along so they will be surfaced to the UI level.
- if (_subject != null)
- {
- _subject.OnNext(status);
- }
- else
- {
- Next?.SendValidationStatus(status);
- }
+ //if (_subject != null)
+ //{
+ // _subject.OnNext(status);
+ //}
+ //else
+ //{
+ // Next?.SendValidationStatus(status);
+ //}
}
protected virtual void Unsubscribe(object target)
diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
index 0a3be26c18..49c959cbfe 100644
--- a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
+++ b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
@@ -126,16 +126,16 @@ namespace Avalonia.Markup.Data
converted = TypeUtilities.Default(type);
_inner.SetValue(converted, _priority);
}
- else if (converted is BindingError)
+ else if (converted is BindingNotification)
{
- var error = converted as BindingError;
+ var error = converted as BindingNotification;
Logger.Error(
LogArea.Binding,
this,
"Error binding to {Expression}: {Message}",
_inner.Expression,
- error.Exception.Message);
+ error.Error.Message);
if (_fallbackValue != AvaloniaProperty.UnsetValue)
{
@@ -174,7 +174,7 @@ namespace Avalonia.Markup.Data
private object ConvertValue(object value)
{
var converted =
- value as BindingError ??
+ value as BindingNotification ??
value as IValidationStatus ??
Converter.Convert(
value,
@@ -184,9 +184,9 @@ namespace Avalonia.Markup.Data
if (_fallbackValue != AvaloniaProperty.UnsetValue &&
(converted == AvaloniaProperty.UnsetValue ||
- converted is BindingError))
+ converted is BindingNotification))
{
- var error = converted as BindingError;
+ var error = converted as BindingNotification;
if (TypeUtilities.TryConvert(
_targetType,
@@ -196,14 +196,15 @@ namespace Avalonia.Markup.Data
{
if (error != null)
{
- converted = new BindingError(error.Exception, converted);
+ converted = new BindingNotification(error.Error, BindingErrorType.Error, converted);
}
}
else
{
- converted = new BindingError(
+ converted = new BindingNotification(
new InvalidCastException(
- $"Could not convert FallbackValue '{_fallbackValue}' to '{_targetType}'"));
+ $"Could not convert FallbackValue '{_fallbackValue}' to '{_targetType}'"),
+ BindingErrorType.Error);
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
index b2d73ab8fc..c46d3571dc 100644
--- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
+++ b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs
@@ -56,7 +56,7 @@ namespace Avalonia.Markup.Data.Plugins
{
var message = $"Could not find AvaloniaProperty '{propertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
- return new PropertyError(new BindingError(exception));
+ return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
else
{
diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
index 36046dd742..2596cc77a2 100644
--- a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Markup.Data.Plugins
{
var message = $"Could not find CLR property '{propertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
- return new PropertyError(new BindingError(exception));
+ return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs b/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs
index f73bb1fc94..f0c0e3fb1a 100644
--- a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs
+++ b/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs
@@ -8,13 +8,13 @@ namespace Avalonia.Markup.Data.Plugins
///
public class PropertyError : IPropertyAccessor
{
- private BindingError _error;
+ private BindingNotification _error;
///
/// Initializes a new instance of the class.
///
/// The error to report.
- public PropertyError(BindingError error)
+ public PropertyError(BindingNotification error)
{
_error = error;
}
diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs
index 469b00d3ad..cde73b67a1 100644
--- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs
+++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs
@@ -44,7 +44,7 @@ namespace Avalonia.Markup
if (value != null)
{
var message = $"Could not convert '{value}' to '{targetType}'";
- return new BindingError(new InvalidCastException(message));
+ return new BindingNotification(new InvalidCastException(message), BindingErrorType.Error);
}
return AvaloniaProperty.UnsetValue;
diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.v2.ncrunchproject b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.v2.ncrunchproject
index 30815b1937..b5cd70b13f 100644
--- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.v2.ncrunchproject
+++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.v2.ncrunchproject
@@ -17,10 +17,11 @@
true
true
60000
-
-
-
+
+
+
AutoDetect
STA
x86
+ LongTestTimesWithoutParallelExecution
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 2e7db2da05..5c3459e3db 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -273,26 +273,31 @@ namespace Avalonia.Base.UnitTests
target.Bind(Class1.QuxProperty, source);
source.OnNext(6.7);
- source.OnNext(new BindingError(new InvalidOperationException("Foo")));
+ source.OnNext(new BindingNotification(
+ new InvalidOperationException("Foo"),
+ BindingErrorType.Error));
Assert.Equal(6.7, target.GetValue(Class1.QuxProperty));
}
[Fact]
- public void BindingError_With_FallbackValue_Causes_Target_Update()
+ public void BindingNotification_With_FallbackValue_Causes_Target_Update()
{
var target = new Class1();
var source = new Subject