diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index f0bb169f3c..02c364346d 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -82,6 +82,7 @@ + diff --git a/samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs b/samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs index b622ee9e18..634498c165 100644 --- a/samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs +++ b/samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs @@ -10,5 +10,8 @@ namespace BindingTest.ViewModels [Phone] [MaxLength(10)] public string PhoneNumber { get; set; } + + [Range(0, 9)] + public int LessThan10 { get; set; } } } diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 0f2afc6474..eeaf782e83 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -667,7 +667,7 @@ namespace Avalonia Logger.Error( LogArea.Binding, this, - "Error binding to {Target}.{Property}: {Message}", + "Error in binding to {Target}.{Property}: {Message}", this, property, ExceptionUtilities.GetMessage(notification.Error)); diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 11bd6c61cd..a7eb4465b3 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -189,7 +189,7 @@ namespace Avalonia LogEventLevel.Error, LogArea.Binding, _owner, - "Error binding to {Target}.{Property}: {Message}", + "Error in binding to {Target}.{Property}: {Message}", _owner, Property, error.Error.Message); diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 2d4c911933..7295bfa7ab 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -27,6 +27,21 @@ namespace Avalonia.Utilities { typeof(short), new List { typeof(byte) } } }; + private static readonly Type[] NumericTypes = new[] + { + typeof(Byte), + typeof(Decimal), + typeof(Double), + typeof(Int16), + typeof(Int32), + typeof(Int64), + typeof(SByte), + typeof(Single), + typeof(UInt16), + typeof(UInt32), + typeof(UInt64), + }; + /// /// Returns a value indicating whether null can be assigned to the specified type. /// @@ -208,5 +223,31 @@ namespace Avalonia.Utilities return null; } } + + /// + /// Determines if a type is numeric. Nullable numeric types are considered numeric. + /// + /// + /// True if the type is numberic; otherwise false. + /// + /// + /// Boolean is not considered numeric. + /// + public static bool IsNumeric(Type type) + { + if (type == null) + { + return false; + } + + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return IsNumeric(Nullable.GetUnderlyingType(type)); + } + else + { + return NumericTypes.Contains(type); + } + } } } diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs index 85609c247c..cb587c683b 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs +++ b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs @@ -139,9 +139,6 @@ namespace Avalonia.Markup.Data "IValueConverter should not return non-errored BindingNotification."); } - notification.Error = new InvalidCastException( - $"Error setting '{_inner.Expression}': {notification.Error.Message}"); - notification.ErrorType = BindingErrorType.Error; _errors.OnNext(notification); if (_fallbackValue != AvaloniaProperty.UnsetValue) diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index cde73b67a1..86d37d8e13 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -43,7 +43,17 @@ namespace Avalonia.Markup if (value != null) { - var message = $"Could not convert '{value}' to '{targetType}'"; + string message; + + if (TypeUtilities.IsNumeric(targetType)) + { + message = $"'{value}' is not a valid number."; + } + else + { + message = $"Could not convert '{value}' to '{targetType.Name}'."; + } + return new BindingNotification(new InvalidCastException(message), BindingErrorType.Error); } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 1757550d4a..66fe3c7767 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -332,7 +332,7 @@ namespace Avalonia.Base.UnitTests var target = new Class1(); var source = new Subject(); var called = false; - var expectedMessageTemplate = "Error binding to {Target}.{Property}: {Message}"; + var expectedMessageTemplate = "Error in binding to {Target}.{Property}: {Message}"; LogCallback checkLogMessage = (level, area, src, mt, pv) => { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 51d5d19834..26e911f586 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -437,7 +437,7 @@ namespace Avalonia.Base.UnitTests { if (level == LogEventLevel.Error && area == LogArea.Binding && - mt == "Error binding to {Target}.{Property}: {Message}" && + mt == "Error in binding to {Target}.{Property}: {Message}" && pv.Length == 3 && pv[0] is Class1 && object.ReferenceEquals(pv[1], Class1.FooProperty) && diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs index 5498926fe4..1af35d692b 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs @@ -123,7 +123,7 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal( new BindingNotification( - new InvalidCastException("Could not convert 'foo' to 'System.Int32'"), + new InvalidCastException("'foo' is not a valid number."), BindingErrorType.Error, 42), result); @@ -144,7 +144,7 @@ namespace Avalonia.Markup.UnitTests.Data Assert.Equal( new BindingNotification( - new InvalidCastException("Could not convert 'foo' to 'System.Int32'"), + new InvalidCastException("'foo' is not a valid number."), BindingErrorType.Error, 42), result); @@ -294,7 +294,7 @@ namespace Avalonia.Markup.UnitTests.Data new BindingNotification("1.2"), new BindingNotification("3.4"), new BindingNotification( - new InvalidCastException("Error setting 'DoubleValue': Could not convert 'bar' to 'System.Double'"), + new InvalidCastException("'bar' is not a valid number."), BindingErrorType.Error) }, result); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs index df62a1ed41..0c2151850f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs @@ -39,7 +39,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml { if (level == LogEventLevel.Error && area == LogArea.Binding && - mt == "Error binding to {Target}.{Property}: {Message}" && + mt == "Error in binding to {Target}.{Property}: {Message}" && pv.Length == 3 && pv[0] is ProgressBar && object.ReferenceEquals(pv[1], ProgressBar.ValueProperty) &&