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>
/// 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.

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

9
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);
}

45
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(); }
}
}
}
}

Loading…
Cancel
Save