diff --git a/src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs b/src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs index 20d9b07daf..e295292ba0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs index 4b812e12f0..ec60695374 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs @@ -20,6 +20,7 @@ namespace Avalonia.Markup.Xaml.Data /// public Binding() { + FallbackValue = AvaloniaProperty.UnsetValue; } /// @@ -27,6 +28,7 @@ namespace Avalonia.Markup.Xaml.Data /// /// The binding path. 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); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs index a58a8614ee..70d3f7d161 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/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; diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs index 05bf818aad..0a3be26c18 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs +++ b/src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs @@ -41,16 +41,36 @@ namespace Avalonia.Markup.Data /// /// A parameter to pass to . /// + /// The binding priority. + public ExpressionSubject( + ExpressionObserver inner, + Type targetType, + IValueConverter converter, + object converterParameter = null, + BindingPriority priority = BindingPriority.LocalValue) + : this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The type to convert the value to. /// /// The value to use when the binding is unable to produce a value. /// + /// The value converter to use. + /// + /// A parameter to pass to . + /// /// The binding priority. public ExpressionSubject( ExpressionObserver inner, - Type targetType, + Type targetType, + object fallbackValue, IValueConverter converter, object converterParameter = null, - object fallbackValue = null, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(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)) { diff --git a/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs b/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs index 9ee32149e0..8b763e7fb9 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs +++ b/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"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs index ca65b763f5..1174b4ceb5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/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(); + + 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 BazProperty = + AvaloniaProperty.Register("Baz", defaultValue: 6, inherits: true); + + public int Baz + { + get { return GetValue(BazProperty); } + set { SetValue(BazProperty, value); } + } + } } }