Browse Source

Handle BindingNotifications in ExpressionSubject.

pull/691/head
Steven Kirk 10 years ago
parent
commit
f4c57b169b
  1. 4
      src/Avalonia.Base/Data/BindingNotification.cs
  2. 55
      src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
  3. 9
      src/Markup/Avalonia.Markup/IValueConverter.cs
  4. 45
      tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

4
src/Avalonia.Base/Data/BindingNotification.cs

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

55
src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs

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

9
src/Markup/Avalonia.Markup/IValueConverter.cs

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

45
tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

@ -10,6 +10,8 @@ using Avalonia.Data;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Xunit; using Xunit;
using System.Threading; using System.Threading;
using System.Collections.Generic;
using Avalonia.UnitTests;
namespace Avalonia.Markup.UnitTests.Data 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)); 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(); }
}
} }
} }
} }

Loading…
Cancel
Save