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) if (control != null)
{ {
DelayedBinding.Add(control, property, binding); if (property != Control.DataContextProperty)
{
DelayedBinding.Add(control, property, binding);
}
else
{
control.Bind(property, binding);
}
} }
else else
{ {

15
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -20,6 +20,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary> /// </summary>
public Binding() public Binding()
{ {
FallbackValue = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -27,6 +28,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary> /// </summary>
/// <param name="path">The binding path.</param> /// <param name="path">The binding path.</param>
public Binding(string path) public Binding(string path)
: this()
{ {
Path = path; Path = path;
} }
@ -122,12 +124,23 @@ namespace Avalonia.Markup.Xaml.Data
throw new NotSupportedException(); 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( var subject = new ExpressionSubject(
observer, observer,
targetProperty?.PropertyType ?? typeof(object), targetProperty?.PropertyType ?? typeof(object),
fallback,
Converter ?? DefaultValueConverter.Instance, Converter ?? DefaultValueConverter.Instance,
ConverterParameter, ConverterParameter,
FallbackValue,
Priority); Priority);
return new InstancedBinding(subject, Mode, 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 IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; } public object ConverterParameter { get; set; }
public string ElementName { 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 BindingMode Mode { get; set; }
public string Path { get; set; } public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue; 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"> /// <param name="converterParameter">
/// A parameter to pass to <paramref name="converter"/>. /// A parameter to pass to <paramref name="converter"/>.
/// </param> /// </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"> /// <param name="fallbackValue">
/// The value to use when the binding is unable to produce a value. /// The value to use when the binding is unable to produce a value.
/// </param> /// </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> /// <param name="priority">The binding priority.</param>
public ExpressionSubject( public ExpressionSubject(
ExpressionObserver inner, ExpressionObserver inner,
Type targetType, Type targetType,
object fallbackValue,
IValueConverter converter, IValueConverter converter,
object converterParameter = null, object converterParameter = null,
object fallbackValue = null,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
Contract.Requires<ArgumentNullException>(inner != null); Contract.Requires<ArgumentNullException>(inner != null);
@ -117,7 +137,7 @@ namespace Avalonia.Markup.Data
_inner.Expression, _inner.Expression,
error.Exception.Message); error.Exception.Message);
if (_fallbackValue != null) if (_fallbackValue != AvaloniaProperty.UnsetValue)
{ {
if (TypeUtilities.TryConvert( if (TypeUtilities.TryConvert(
type, type,
@ -162,7 +182,7 @@ namespace Avalonia.Markup.Data
ConverterParameter, ConverterParameter,
CultureInfo.CurrentUICulture); CultureInfo.CurrentUICulture);
if (_fallbackValue != null && if (_fallbackValue != AvaloniaProperty.UnsetValue &&
(converted == AvaloniaProperty.UnsetValue || (converted == AvaloniaProperty.UnsetValue ||
converted is BindingError)) converted is BindingError))
{ {

8
tests/Avalonia.Markup.UnitTests/Data/ExpressionSubjectTests.cs

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

36
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -1,11 +1,13 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Markup.Data; using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Data; using Avalonia.Markup.Xaml.Data;
using Moq;
using ReactiveUI; using ReactiveUI;
using Xunit; using Xunit;
@ -159,6 +161,26 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", child.DataContext); 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] [Fact]
public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
{ {
@ -337,5 +359,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Bind(BarProperty, this.GetObservable(FooProperty)); 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