Browse Source

Initial implementation of StringFormat.

XAML tests failing because of https://github.com/cwensley/Portable.Xaml/pull/106
pull/1684/head
Steven Kirk 8 years ago
parent
commit
70440517ca
  1. 30
      src/Avalonia.Base/Data/Converters/StringFormatConverter.cs
  2. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  3. 21
      src/Markup/Avalonia.Markup/Data/Binding.cs
  4. 146
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs
  5. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

30
src/Avalonia.Base/Data/Converters/StringFormatConverter.cs

@ -0,0 +1,30 @@
using System;
using System.Globalization;
namespace Avalonia.Data.Converters
{
public class StringFormatConverter : IValueConverter
{
public StringFormatConverter(string format, IValueConverter inner)
{
Contract.Requires<ArgumentNullException>(format != null);
Format = format;
Inner = inner;
}
public IValueConverter Inner { get; }
public string Format { get; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
value = Inner?.Convert(value, targetType, parameter, culture) ?? value;
return string.Format(Format, value, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Two way bindings are not supported with a string format");
}
}
}

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -45,6 +45,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Path = pathInfo.Path,
Priority = Priority,
Source = Source,
StringFormat = StringFormat,
RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
@ -226,6 +227,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object Source { get; set; }
public string StringFormat { get; set; }
public RelativeSource RelativeSource { get; set; }
}
}

21
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -84,6 +84,11 @@ namespace Avalonia.Data
/// </summary>
public object Source { get; set; }
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
/// <inheritdoc/>
@ -158,11 +163,23 @@ namespace Avalonia.Data
fallback = null;
}
var converter = Converter;
var targetType = targetProperty?.PropertyType ?? typeof(object);
// We only respect `StringFormat` if the type of the property we're assigning to will
// accept a string. Note that this is slightly different to WPF in that WPF only applies
// `StringFormat` for target type `string` (not `object`).
if (!string.IsNullOrWhiteSpace(StringFormat) &&
(targetType == typeof(string) || targetType == typeof(object)))
{
converter = new StringFormatConverter(StringFormat, converter);
}
var subject = new BindingExpression(
observer,
targetProperty?.PropertyType ?? typeof(object),
targetType,
fallback,
Converter ?? DefaultValueConverter.Instance,
converter ?? DefaultValueConverter.Instance,
ConverterParameter,
Priority);

146
tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs

@ -0,0 +1,146 @@
// 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 System;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Xunit;
namespace Avalonia.Markup.UnitTests.Data
{
public class BindingTests_Converters
{
[Fact]
public void Converter_Should_Be_Used()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
Converter = StringConverters.NullOrEmpty,
};
var expressionObserver = (BindingExpression)target.Initiate(
textBlock,
TextBlock.TextProperty).Observable;
Assert.Same(StringConverters.NullOrEmpty, expressionObserver.Converter);
}
public class When_Binding_To_String
{
[Fact]
public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
StringFormat = "Hello {0}",
};
var expressionObserver = (BindingExpression)target.Initiate(
textBlock,
TextBlock.TextProperty).Observable;
Assert.IsType<StringFormatConverter>(expressionObserver.Converter);
}
}
public class When_Binding_To_Object
{
[Fact]
public void StringFormatConverter_Should_Used_When_Binding_Has_StringFormat()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
StringFormat = "Hello {0}",
};
var expressionObserver = (BindingExpression)target.Initiate(
textBlock,
TextBlock.TagProperty).Observable;
Assert.IsType<StringFormatConverter>(expressionObserver.Converter);
}
}
public class When_Binding_To_Non_String_Or_Object
{
[Fact]
public void StringFormatConverter_Should_Not_Be_Used_When_Binding_Has_StringFormat()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
StringFormat = "Hello {0}",
};
var expressionObserver = (BindingExpression)target.Initiate(
textBlock,
TextBlock.MarginProperty).Observable;
Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter);
}
}
[Fact]
public void StringFormat_Should_Be_Applied()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
StringFormat = "Hello {0}",
};
textBlock.Bind(TextBlock.TextProperty, target);
Assert.Equal("Hello foo", textBlock.Text);
}
[Fact]
public void StringFormat_Should_Be_Applied_After_Converter()
{
var textBlock = new TextBlock
{
DataContext = new Class1(),
};
var target = new Binding(nameof(Class1.Foo))
{
Converter = StringConverters.NotNullOrEmpty,
StringFormat = "Hello {0}",
};
textBlock.Bind(TextBlock.TextProperty, target);
Assert.Equal("Hello True", textBlock.Text);
}
private class Class1
{
public string Foo { get; set; } = "foo";
}
}
}

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@ -281,5 +281,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
}
}
[Fact]
public void Binding_To_TextBlock_Text_With_StringConverter_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock Name='textBlock' Text='{Binding Foo, StringFormat=Hello {0}}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
textBlock.DataContext = new { Foo = "world" };
window.ApplyTemplate();
Assert.Equal("Hello world", textBlock.Text);
}
}
}
}
Loading…
Cancel
Save