Browse Source

Merge pull request #10039 from Enscape/fixes/referencetype-binding-null-behaviour

Never convert null to UnsetValue in bindings
pull/10271/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
4d8102d9c0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  2. 2
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  3. 25
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  4. 70
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters
{ {
if (value == null) if (value == null)
{ {
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; return null;
} }
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

2
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives
nameof(IsChecked), nameof(IsChecked),
o => o.IsChecked, o => o.IsChecked,
(o, v) => o.IsChecked = v, (o, v) => o.IsChecked = v,
unsetValue: null, unsetValue: false,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>

25
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@ -78,18 +78,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue()
{
var data = new Class1 { StringValue = null };
var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
var result = await target.Take(1);
Assert.Equal(AvaloniaProperty.UnsetValue, result);
GC.KeepAlive(data);
}
[Fact] [Fact]
public void Should_Convert_Set_String_To_Double() public void Should_Convert_Set_String_To_Double()
{ {
@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public void Should_Coerce_Setting_Null_Double_To_Default_Value()
{
var data = new Class1 { DoubleValue = 5.6 };
var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
target.OnNext(null);
Assert.Equal(0, data.DoubleValue);
GC.KeepAlive(data);
}
[Fact] [Fact]
public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value() public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
{ {

70
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@ -648,16 +648,69 @@ namespace Avalonia.Markup.UnitTests.Data
}; };
} }
[Fact]
public void Binding_Producing_Default_Value_Should_Result_In_Correct_Priority()
{
var defaultValue = StyledPropertyClass.NullableDoubleProperty.GetDefaultValue(typeof(StyledPropertyClass));
var vm = new NullableValuesViewModel() { NullableDouble = defaultValue };
var target = new StyledPropertyClass();
target.Bind(StyledPropertyClass.NullableDoubleProperty, new Binding(nameof(NullableValuesViewModel.NullableDouble)) { Source = vm });
Assert.Equal(BindingPriority.LocalValue, target.GetDiagnosticInternal(StyledPropertyClass.NullableDoubleProperty).Priority);
Assert.Equal(defaultValue, target.GetValue(StyledPropertyClass.NullableDoubleProperty));
}
[Fact]
public void Binding_Non_Nullable_ValueType_To_Null_Reverts_To_Default_Value()
{
var source = new NullableValuesViewModel { NullableDouble = 42 };
var target = new StyledPropertyClass();
var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
target.Bind(StyledPropertyClass.DoubleValueProperty, binding);
Assert.Equal(42, target.DoubleValue);
source.NullableDouble = null;
Assert.Equal(12.3, target.DoubleValue);
}
[Fact]
public void Binding_Nullable_ValueType_To_Null_Sets_Value_To_Null()
{
var source = new NullableValuesViewModel { NullableDouble = 42 };
var target = new StyledPropertyClass();
var binding = new Binding(nameof(source.NullableDouble)) { Source = source };
target.Bind(StyledPropertyClass.NullableDoubleProperty, binding);
Assert.Equal(42, target.NullableDouble);
source.NullableDouble = null;
Assert.Null(target.NullableDouble);
}
private class StyledPropertyClass : AvaloniaObject private class StyledPropertyClass : AvaloniaObject
{ {
public static readonly StyledProperty<double> DoubleValueProperty = public static readonly StyledProperty<double> DoubleValueProperty =
AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue)); AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue), 12.3);
public double DoubleValue public double DoubleValue
{ {
get { return GetValue(DoubleValueProperty); } get { return GetValue(DoubleValueProperty); }
set { SetValue(DoubleValueProperty, value); } set { SetValue(DoubleValueProperty, value); }
} }
public static StyledProperty<double?> NullableDoubleProperty =
AvaloniaProperty.Register<StyledPropertyClass, double?>(nameof(NullableDoubleProperty), -1);
public double? NullableDouble
{
get => GetValue(NullableDoubleProperty);
set => SetValue(NullableDoubleProperty, value);
}
} }
private class DirectPropertyClass : AvaloniaObject private class DirectPropertyClass : AvaloniaObject
@ -676,6 +729,21 @@ namespace Avalonia.Markup.UnitTests.Data
} }
} }
private class NullableValuesViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double? _nullableDouble;
public double? NullableDouble
{
get => _nullableDouble; set
{
_nullableDouble = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NullableDouble)));
}
}
}
private class TestStackOverflowViewModel : INotifyPropertyChanged private class TestStackOverflowViewModel : INotifyPropertyChanged
{ {
public int SetterInvokedCount { get; private set; } public int SetterInvokedCount { get; private set; }

Loading…
Cancel
Save