Browse Source

Make broken DataContext bindings produce null.

This prevents incorrect DataContexts cascading down to children when the
DataContext binding is invalid, e.g. when things are being set up.
Steven Kirk 9 years ago
parent
commit
0b28e10f21

+ 14 - 1
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@@ -20,6 +20,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// </summary>
         public Binding()
         {
+            FallbackValue = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>
@@ -27,6 +28,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// </summary>
         /// <param name="path">The binding path.</param>
         public Binding(string path)
+            : this()
         {
             Path = path;
         }
@@ -122,12 +124,23 @@ namespace Avalonia.Markup.Xaml.Data
                 throw new NotSupportedException();
             }
 
+            var fallback = FallbackValue;
+
+            // If we're binding to DataContext and our fallback is UnsetValue then override
+            // the fallback value to null, as broken bindings to DataContext must reset the
+            // DataContext in order to not propagate incorrect DataContexts to child controls.
+            // See Avalonia.Markup.Xaml.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
+            if (targetProperty == Control.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
+            {
+                fallback = null;
+            }
+
             var subject = new ExpressionSubject(
                 observer,
                 targetProperty?.PropertyType ?? typeof(object),
+                fallback,
                 Converter ?? DefaultValueConverter.Instance,
                 ConverterParameter,
-                FallbackValue,
                 Priority);
 
             return new InstancedBinding(subject, Mode, Priority);

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@@ -36,7 +36,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
         public IValueConverter Converter { get; set; }
         public object ConverterParameter { get; set; }
         public string ElementName { get; set; }
-        public object FallbackValue { get; set; }
+        public object FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
         public BindingMode Mode { get; set; }
         public string Path { get; set; }
         public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;

+ 24 - 4
src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs

@@ -41,16 +41,36 @@ namespace Avalonia.Markup.Data
         /// <param name="converterParameter">
         /// A parameter to pass to <paramref name="converter"/>.
         /// </param>
+        /// <param name="priority">The binding priority.</param>
+        public ExpressionSubject(
+            ExpressionObserver inner,
+            Type targetType,
+            IValueConverter converter,
+            object converterParameter = null,
+            BindingPriority priority = BindingPriority.LocalValue)
+            : this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
+        /// </summary>
+        /// <param name="inner">The <see cref="ExpressionObserver"/>.</param>
+        /// <param name="targetType">The type to convert the value to.</param>
         /// <param name="fallbackValue">
         /// The value to use when the binding is unable to produce a value.
         /// </param>
+        /// <param name="converter">The value converter to use.</param>
+        /// <param name="converterParameter">
+        /// A parameter to pass to <paramref name="converter"/>.
+        /// </param>
         /// <param name="priority">The binding priority.</param>
         public ExpressionSubject(
             ExpressionObserver inner, 
-            Type targetType, 
+            Type targetType,
+            object fallbackValue,
             IValueConverter converter,
             object converterParameter = null,
-            object fallbackValue = null,
             BindingPriority priority = BindingPriority.LocalValue)
         {
             Contract.Requires<ArgumentNullException>(inner != null);
@@ -117,7 +137,7 @@ namespace Avalonia.Markup.Data
                         _inner.Expression,
                         error.Exception.Message);
 
-                    if (_fallbackValue != null)
+                    if (_fallbackValue != AvaloniaProperty.UnsetValue)
                     {
                         if (TypeUtilities.TryConvert(
                             type, 
@@ -162,7 +182,7 @@ namespace Avalonia.Markup.Data
                     ConverterParameter,
                     CultureInfo.CurrentUICulture);
 
-            if (_fallbackValue != null &&
+            if (_fallbackValue != AvaloniaProperty.UnsetValue &&
                 (converted == AvaloniaProperty.UnsetValue ||
                  converted is BindingError))
             {

+ 4 - 4
tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

@@ -123,8 +123,8 @@ namespace Avalonia.Markup.UnitTests.Data
             var target = new ExpressionSubject(
                 new ExpressionObserver(data, "DoubleValue"),
                 typeof(string),
-                DefaultValueConverter.Instance,
-                fallbackValue: "9.8");
+                "9.8",
+                DefaultValueConverter.Instance);
 
             target.OnNext("foo");
 
@@ -162,7 +162,7 @@ namespace Avalonia.Markup.UnitTests.Data
                 new ExpressionObserver(data, "DoubleValue"),
                 typeof(string),
                 converter.Object,
-                "foo");
+                converterParameter: "foo");
 
             target.Subscribe(_ => { });
 
@@ -178,7 +178,7 @@ namespace Avalonia.Markup.UnitTests.Data
                 new ExpressionObserver(data, "DoubleValue"), 
                 typeof(string),
                 converter.Object,
-                "foo");
+                converterParameter: "foo");
 
             target.OnNext("bar");