Browse Source

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

pull/545/head
Steven Kirk 10 years ago
parent
commit
d9baead785
  1. 9
      src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs
  2. 15
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  3. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  4. 28
      src/Markup/Avalonia.Markup/Data/ExpressionSubject.cs
  5. 8
      tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs
  6. 36
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

9
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
{

15
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);

2
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;

28
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))
{

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

36
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); }
}
}
}
}

Loading…
Cancel
Save