Browse Source

Implemented TargetNullValue for bindings.

José Pedro 6 years ago
parent
commit
1ba4c68a35

+ 12 - 1
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -21,6 +21,7 @@ namespace Avalonia.Data.Core
         private readonly ExpressionObserver _inner;
         private readonly Type _targetType;
         private readonly object _fallbackValue;
+        private readonly object _targetNullValue;
         private readonly BindingPriority _priority;
         InnerListener _innerListener;
         WeakReference<object> _value;
@@ -51,7 +52,7 @@ namespace Avalonia.Data.Core
             IValueConverter converter,
             object converterParameter = null,
             BindingPriority priority = BindingPriority.LocalValue)
-            : this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
+            : this(inner, targetType, AvaloniaProperty.UnsetValue, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
         {
         }
 
@@ -63,6 +64,9 @@ namespace Avalonia.Data.Core
         /// <param name="fallbackValue">
         /// The value to use when the binding is unable to produce a value.
         /// </param>
+        /// <param name="targetNullValue">
+        /// The value to use when the binding result is null.
+        /// </param>
         /// <param name="converter">The value converter to use.</param>
         /// <param name="converterParameter">
         /// A parameter to pass to <paramref name="converter"/>.
@@ -72,6 +76,7 @@ namespace Avalonia.Data.Core
             ExpressionObserver inner, 
             Type targetType,
             object fallbackValue,
+            object targetNullValue,
             IValueConverter converter,
             object converterParameter = null,
             BindingPriority priority = BindingPriority.LocalValue)
@@ -85,6 +90,7 @@ namespace Avalonia.Data.Core
             Converter = converter;
             ConverterParameter = converterParameter;
             _fallbackValue = fallbackValue;
+            _targetNullValue = targetNullValue;
             _priority = priority;
         }
 
@@ -196,6 +202,11 @@ namespace Avalonia.Data.Core
         /// <inheritdoc/>
         private object ConvertValue(object value)
         {
+            if (value == null && _targetNullValue != AvaloniaProperty.UnsetValue)
+            {
+                return _targetNullValue;
+            }
+
             if (value == BindingOperations.DoNothing)
             {
                 return value;

+ 7 - 0
src/Markup/Avalonia.Markup/Data/Binding.cs

@@ -26,6 +26,7 @@ namespace Avalonia.Data
         public Binding()
         {
             FallbackValue = AvaloniaProperty.UnsetValue;
+            TargetNullValue = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>
@@ -60,6 +61,11 @@ namespace Avalonia.Data
         /// </summary>
         public object FallbackValue { get; set; }
 
+        /// <summary>
+        /// Gets or sets the value to use when the binding result is null.
+        /// </summary>
+        public object TargetNullValue { get; set; }
+
         /// <summary>
         /// Gets or sets the binding mode.
         /// </summary>
@@ -209,6 +215,7 @@ namespace Avalonia.Data
                 observer,
                 targetType,
                 fallback,
+                TargetNullValue,
                 converter ?? DefaultValueConverter.Instance,
                 ConverterParameter,
                 Priority);

+ 16 - 0
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@@ -37,6 +37,11 @@ namespace Avalonia.Data
         /// </summary>
         public object FallbackValue { get; set; }
 
+        /// <summary>
+        /// Gets or sets the value to use when the binding result is null.
+        /// </summary>
+        public object TargetNullValue { get; set; }
+
         /// <summary>
         /// Gets or sets the binding mode.
         /// </summary>
@@ -57,6 +62,12 @@ namespace Avalonia.Data
         /// </summary>
         public string StringFormat { get; set; }
 
+        public MultiBinding()
+        {
+            FallbackValue = AvaloniaProperty.UnsetValue;
+            TargetNullValue = AvaloniaProperty.UnsetValue;
+        }
+
         /// <inheritdoc/>
         public InstancedBinding Initiate(
             IAvaloniaObject target,
@@ -102,6 +113,11 @@ namespace Avalonia.Data
             var culture = CultureInfo.CurrentCulture;
             var converted = converter.Convert(values, targetType, ConverterParameter, culture);
 
+            if (converted == null)
+            {
+                converted = TargetNullValue;
+            }
+
             if (converted == AvaloniaProperty.UnsetValue)
             {
                 converted = FallbackValue;

+ 28 - 0
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@@ -139,6 +139,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
                 ExpressionObserver.Create(data, o => o.StringValue),
                 typeof(int),
                 42,
+                AvaloniaProperty.UnsetValue,
                 DefaultValueConverter.Instance);
             var result = await target.Take(1);
 
@@ -160,6 +161,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
                 ExpressionObserver.Create(data, o => o.StringValue, true),
                 typeof(int),
                 42,
+                AvaloniaProperty.UnsetValue,
                 DefaultValueConverter.Instance);
             var result = await target.Take(1);
 
@@ -181,6 +183,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
                 ExpressionObserver.Create(data, o => o.StringValue),
                 typeof(int),
                 "bar",
+                AvaloniaProperty.UnsetValue,
                 DefaultValueConverter.Instance);
             var result = await target.Take(1);
 
@@ -203,6 +206,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
                 ExpressionObserver.Create(data, o => o.StringValue, true),
                 typeof(int),
                 "bar",
+                AvaloniaProperty.UnsetValue,
                 DefaultValueConverter.Instance);
             var result = await target.Take(1);
 
@@ -238,6 +242,7 @@ namespace Avalonia.Base.UnitTests.Data.Core
                 ExpressionObserver.Create(data, o => o.DoubleValue),
                 typeof(string),
                 "9.8",
+                AvaloniaProperty.UnsetValue,
                 DefaultValueConverter.Instance);
 
             target.OnNext("foo");
@@ -353,6 +358,29 @@ namespace Avalonia.Base.UnitTests.Data.Core
             GC.KeepAlive(data);
         }
 
+        [Fact]
+        public async Task Null_Value_Should_Use_TargetNullValue()
+        {
+            var data = new Class1 { StringValue = "foo" };
+
+            var target = new BindingExpression(
+                ExpressionObserver.Create(data, o => o.StringValue),
+                typeof(string),
+                AvaloniaProperty.UnsetValue,
+                "bar",
+                DefaultValueConverter.Instance);
+
+            object result = null;
+            target.Subscribe(x => result = x);
+
+            Assert.Equal("foo", result);
+            
+            data.StringValue = null;
+            Assert.Equal("bar", result);
+
+            GC.KeepAlive(data);
+        }
+
         private class Class1 : NotifyingBase
         {
             private string _stringValue;

+ 18 - 0
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@@ -405,6 +405,24 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal(42, target.Value);
         }
 
+        [Fact]
+        public void Should_Return_TargetNullValue_When_Value_Is_Null()
+        {
+            var target = new TextBlock();
+            var source = new Source { Foo = null };
+
+            var binding = new Binding
+            {
+                Source = source,
+                Path = "Foo",
+                TargetNullValue = "(null)",
+            };
+
+            target.Bind(TextBlock.TextProperty, binding);
+
+            Assert.Equal("(null)", target.Text);
+        }
+
         [Fact]
         public void Null_Path_Should_Bind_To_DataContext()
         {

+ 30 - 0
tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs

@@ -94,6 +94,28 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal("fallback", target.Text);
         }
 
+        [Fact]
+        public void Should_Return_TargetNullValue_When_Value_Is_Null()
+        {
+            var target = new TextBlock();
+
+            var binding = new MultiBinding
+            {
+                Converter = new NullValueConverter(),
+                Bindings = new[]
+                {
+                    new Binding { Path = "A" },
+                    new Binding { Path = "B" },
+                    new Binding { Path = "C" },
+                },
+                TargetNullValue = "(null)",
+            };
+
+            target.Bind(TextBlock.TextProperty, binding);
+
+            Assert.Equal("(null)", target.Text);
+        }
+
         private class ConcatConverter : IMultiValueConverter
         {
             public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
@@ -109,5 +131,13 @@ namespace Avalonia.Markup.UnitTests.Data
                 return AvaloniaProperty.UnsetValue;
             }
         }
+
+        private class NullValueConverter : IMultiValueConverter
+        {
+            public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
+            {
+                return null;
+            }
+        }
     }
 }