Browse Source

Handle BindingNotifications in ExpressionSubject.

Steven Kirk 9 years ago
parent
commit
f4c57b169b

+ 2 - 2
src/Avalonia.Base/Data/BindingNotification.cs

@@ -97,12 +97,12 @@ namespace Avalonia.Data
         /// <summary>
         /// Gets the error that occurred on the source, if any.
         /// </summary>
-        public Exception Error { get; private set; }
+        public Exception Error { get; set; }
 
         /// <summary>
         /// Gets the type of error that <see cref="Error"/> represents, if any.
         /// </summary>
-        public BindingErrorType ErrorType { get; private set; }
+        public BindingErrorType ErrorType { get; set; }
 
         /// <summary>
         /// Compares two instances of <see cref="BindingNotification"/> for equality.

+ 38 - 17
src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs

@@ -21,6 +21,7 @@ namespace Avalonia.Markup.Data
         private readonly Type _targetType;
         private readonly object _fallbackValue;
         private readonly BindingPriority _priority;
+        private readonly Subject<object> _errors = new Subject<object>();
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -130,14 +131,18 @@ namespace Avalonia.Markup.Data
                     }
                     else if (converted is BindingNotification)
                     {
-                        var error = converted as BindingNotification;
+                        var notification = converted as BindingNotification;
 
-                        Logger.Error(
-                            LogArea.Binding,
-                            this,
-                            "Error binding to {Expression}: {Message}",
-                            _inner.Expression,
-                            error.Error.Message);
+                        if (notification.ErrorType == BindingErrorType.None)
+                        {
+                            throw new AvaloniaInternalException(
+                                "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)
                         {
@@ -171,19 +176,23 @@ namespace Avalonia.Markup.Data
         /// <inheritdoc/>
         public IDisposable Subscribe(IObserver<object> observer)
         {
-            return _inner.Select(ConvertValue).Subscribe(observer);
+            return _inner.Select(ConvertValue).Merge(_errors).Subscribe(observer);
         }
 
         private object ConvertValue(object value)
         {
-            var converted = 
-                value as BindingNotification ??
-                ////value as IValidationStatus ??
-                Converter.Convert(
-                    value,
-                    _targetType,
-                    ConverterParameter,
-                    CultureInfo.CurrentUICulture);
+            var notification = value as BindingNotification;
+
+            if (notification?.HasValue == true)
+            {
+                value = notification.Value;
+            }
+
+            var converted = Converter.Convert(
+                value,
+                _targetType,
+                ConverterParameter,
+                CultureInfo.CurrentUICulture);
 
             if (_fallbackValue != AvaloniaProperty.UnsetValue &&
                 (converted == AvaloniaProperty.UnsetValue ||
@@ -211,7 +220,19 @@ namespace Avalonia.Markup.Data
                 }
             }
 
-            return converted;
+            if (notification == null)
+            {
+                return converted;
+            }
+            else
+            {
+                if (notification.HasValue)
+                {
+                    notification.Value = converted;
+                }
+
+                return notification;
+            }
         }
     }
 }

+ 5 - 4
src/Markup/Avalonia.Markup/IValueConverter.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Globalization;
+using Avalonia.Data;
 
 namespace Avalonia.Markup
 {
@@ -21,8 +22,8 @@ namespace Avalonia.Markup
         /// <returns>The converted value.</returns>
         /// <remarks>
         /// This method should not throw exceptions. If the value is not convertible, return
-        /// <see cref="AvaloniaProperty.UnsetValue"/>. Any exception thrown will be treated as
-        /// an application exception.
+        /// a <see cref="BindingNotification"/> in an error state. Any exceptions thrown will be
+        /// treated as an application exception.
         /// </remarks>
         object Convert(object value, Type targetType, object parameter, CultureInfo culture);
 
@@ -36,8 +37,8 @@ namespace Avalonia.Markup
         /// <returns>The converted value.</returns>
         /// <remarks>
         /// This method should not throw exceptions. If the value is not convertible, return
-        /// <see cref="AvaloniaProperty.UnsetValue"/>. Any exception thrown will be treated as
-        /// an application exception.
+        /// a <see cref="BindingNotification"/> in an error state. Any exceptions thrown will be
+        /// treated as an application exception.
         /// </remarks>
         object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
     }

+ 41 - 4
tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

@@ -10,6 +10,8 @@ using Avalonia.Data;
 using Avalonia.Markup.Data;
 using Xunit;
 using System.Threading;
+using System.Collections.Generic;
+using Avalonia.UnitTests;
 
 namespace Avalonia.Markup.UnitTests.Data
 {
@@ -186,13 +188,48 @@ namespace Avalonia.Markup.UnitTests.Data
             converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentUICulture));
         }
 
-        private class Class1 : INotifyPropertyChanged
+        [Fact]
+        public void Should_Handle_DataValidation()
         {
-            public event PropertyChangedEventHandler PropertyChanged;
+            var data = new Class1 { DoubleValue = 5.6 };
+            var converter = new Mock<IValueConverter>();
+            var target = new ExpressionSubject(new ExpressionObserver(data, "DoubleValue", true), typeof(string));
+            var result = new List<object>();
 
-            public string StringValue { get; set; }
+            target.Subscribe(x => result.Add(x));
+            target.OnNext(1.2);
+            target.OnNext("3.4");
+            target.OnNext("bar");
 
-            public double DoubleValue { get; set; }
+            Assert.Equal(
+                new[]
+                {
+                    new BindingNotification("5.6"),
+                    new BindingNotification("1.2"),
+                    new BindingNotification("3.4"),
+                    new BindingNotification(
+                        new InvalidCastException("Error setting 'DoubleValue': Could not convert 'bar' to 'System.Double'"),
+                        BindingErrorType.Error)
+                },
+                result);
+        }
+
+        private class Class1 : NotifyingBase
+        {
+            private string _stringValue;
+            private double _doubleValue;
+
+            public string StringValue
+            {
+                get { return _stringValue; }
+                set { _stringValue = value; RaisePropertyChanged(); }
+            }
+
+            public double DoubleValue
+            {
+                get { return _doubleValue; }
+                set { _doubleValue = value; RaisePropertyChanged(); }
+            }
         }
     }
 }