Browse Source

Merge branch 'binding-refactor' into item-virtualization-avalonia

Steven Kirk 9 years ago
parent
commit
d9baead785

+ 8 - 1
src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs

@@ -136,7 +136,14 @@ namespace Avalonia.Markup.Xaml.Context
 
             if (control != null)
             {
-                DelayedBinding.Add(control, property, binding);
+                if (property != Control.DataContextProperty)
+                {
+                    DelayedBinding.Add(control, property, binding);
+                }
+                else
+                {
+                    control.Bind(property, binding);
+                }
             }
             else
             {

+ 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");
 

+ 35 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@@ -1,11 +1,13 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
-using Moq;
+using System;
+using System.Collections.Generic;
 using Avalonia.Controls;
 using Avalonia.Data;
 using Avalonia.Markup.Data;
 using Avalonia.Markup.Xaml.Data;
+using Moq;
 using ReactiveUI;
 using Xunit;
 
@@ -159,6 +161,26 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
             Assert.Equal("foo", child.DataContext);
         }
 
+        [Fact]
+        public void DataContext_Binding_Should_Produce_Correct_Results()
+        {
+            var root = new Decorator
+            {
+                DataContext = new { Foo = "bar" },
+            };
+
+            var child = new Control();
+            var dataContextBinding = new Binding("Foo");
+            var values = new List<object>();
+
+            child.GetObservable(Border.DataContextProperty).Subscribe(x => values.Add(x));
+            child.Bind(ContentControl.DataContextProperty, dataContextBinding);
+
+            root.Child = child;
+
+            Assert.Equal(new[] { null, "bar" }, values);
+        }
+
         [Fact]
         public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
         {
@@ -337,5 +359,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
                 Bind(BarProperty, this.GetObservable(FooProperty));
             }
         }
+
+        private class InheritanceTest : Decorator
+        {
+            public static readonly StyledProperty<int> BazProperty =
+                AvaloniaProperty.Register<InheritanceTest, int>("Baz", defaultValue: 6, inherits: true);
+
+            public int Baz
+            {
+                get { return GetValue(BazProperty); }
+                set { SetValue(BazProperty, value); }
+            }
+        }
     }
 }