Browse Source

Correctly convert BindingNotifications.

In ExpressionSubject.
pull/691/head
Steven Kirk 10 years ago
parent
commit
74e870333b
  1. 2
      src/Avalonia.Base/Data/BindingNotification.cs
  2. 135
      src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
  3. 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  4. 86
      tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

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

@ -92,7 +92,7 @@ namespace Avalonia.Data
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="Value"/> should be pushed to the target. /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
/// </summary> /// </summary>
public bool HasValue { get; } public bool HasValue { get; set; }
/// <summary> /// <summary>
/// Gets the error that occurred on the source, if any. /// Gets the error that occurred on the source, if any.

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

@ -183,56 +183,121 @@ namespace Avalonia.Markup.Data
{ {
var notification = value as BindingNotification; var notification = value as BindingNotification;
if (notification?.HasValue == true) if (notification == null)
{ {
value = notification.Value; var converted = Converter.Convert(
} value,
_targetType,
ConverterParameter,
CultureInfo.CurrentUICulture);
var converted = Converter.Convert( notification = converted as BindingNotification;
value,
_targetType,
ConverterParameter,
CultureInfo.CurrentUICulture);
if (_fallbackValue != AvaloniaProperty.UnsetValue && if (notification?.ErrorType == BindingErrorType.None)
(converted == AvaloniaProperty.UnsetValue ||
converted is BindingNotification))
{
var error = converted as BindingNotification;
if (TypeUtilities.TryConvert(
_targetType,
_fallbackValue,
CultureInfo.InvariantCulture,
out converted))
{ {
if (error != null) converted = notification.Value;
{
converted = new BindingNotification(error.Error, BindingErrorType.Error, converted);
}
} }
else
if (_fallbackValue != AvaloniaProperty.UnsetValue &&
(converted == AvaloniaProperty.UnsetValue || converted is BindingNotification))
{ {
converted = new BindingNotification( var fallback = ConvertFallback();
new InvalidCastException( converted = Merge(converted, fallback);
$"Could not convert FallbackValue '{_fallbackValue}' to '{_targetType}'"),
BindingErrorType.Error);
} }
return converted;
} }
else
{
return ConvertValue(notification);
}
}
if (notification == null) private BindingNotification ConvertValue(BindingNotification notification)
{
if (notification.HasValue)
{ {
return converted; var converted = ConvertValue(notification.Value);
notification = Merge(notification, converted);
}
else if (_fallbackValue != AvaloniaProperty.UnsetValue)
{
var fallback = ConvertFallback();
notification = Merge(notification, fallback);
}
return notification;
}
private BindingNotification ConvertFallback()
{
object converted;
if (_fallbackValue == AvaloniaProperty.UnsetValue)
{
throw new AvaloniaInternalException("Cannot call ConvertFallback with no fallback value");
}
if (TypeUtilities.TryConvert(
_targetType,
_fallbackValue,
CultureInfo.InvariantCulture,
out converted))
{
return new BindingNotification(converted);
} }
else else
{
return new BindingNotification(
new InvalidCastException(
$"Could not convert FallbackValue '{_fallbackValue}' to '{_targetType}'"),
BindingErrorType.Error);
}
}
private BindingNotification Merge(object a, BindingNotification b)
{
var an = a as BindingNotification;
if (an != null)
{ {
if (notification.HasValue) Merge(an, b);
{ return an;
notification.Value = converted; }
} else
{
return b;
}
}
return notification; private BindingNotification Merge(BindingNotification a, object b)
{
var bn = b as BindingNotification;
if (bn != null)
{
Merge(a, bn);
} }
else
{
a.Value = b;
a.HasValue = true;
}
return a;
}
private BindingNotification Merge(BindingNotification a, BindingNotification b)
{
a.Value = b.Value;
a.HasValue = b.HasValue;
if (b.Error != null)
{
a.AddError(b.Error, b.ErrorType);
}
return a;
} }
} }
} }

1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -284,7 +284,6 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("newvalue", target.Foo); Assert.Equal("newvalue", target.Foo);
} }
[Fact] [Fact]
public void UnsetValue_Is_Used_On_AddOwnered_Property() public void UnsetValue_Is_Used_On_AddOwnered_Property()
{ {

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

@ -108,6 +108,92 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(6.7, data.DoubleValue); Assert.Equal(6.7, data.DoubleValue);
} }
[Fact]
public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value()
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var data = new Class1 { StringValue = "foo" };
var target = new ExpressionSubject(
new ExpressionObserver(data, "StringValue"),
typeof(int),
42,
DefaultValueConverter.Instance);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
BindingErrorType.Error,
42),
result);
}
[Fact]
public async void Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var data = new Class1 { StringValue = "foo" };
var target = new ExpressionSubject(
new ExpressionObserver(data, "StringValue", true),
typeof(int),
42,
DefaultValueConverter.Instance);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
BindingErrorType.Error,
42),
result);
}
[Fact]
public async void Should_Return_BindingNotification_For_Invalid_FallbackValue()
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var data = new Class1 { StringValue = "foo" };
var target = new ExpressionSubject(
new ExpressionObserver(data, "StringValue"),
typeof(int),
"bar",
DefaultValueConverter.Instance);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new AggregateException(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
BindingErrorType.Error),
result);
}
[Fact]
public async void Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var data = new Class1 { StringValue = "foo" };
var target = new ExpressionSubject(
new ExpressionObserver(data, "StringValue", true),
typeof(int),
"bar",
DefaultValueConverter.Instance);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new AggregateException(
new InvalidCastException("Could not convert 'foo' to 'System.Int32'"),
new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
BindingErrorType.Error),
result);
}
[Fact] [Fact]
public void Setting_Invalid_Double_String_Should_Not_Change_Target() public void Setting_Invalid_Double_String_Should_Not_Change_Target()
{ {

Loading…
Cancel
Save