Browse Source

Merge pull request #1684 from AvaloniaUI/feature/stringformat

Add StringFormat to Binding.
pull/1813/head
Jeremy Koritzinsky 8 years ago
committed by GitHub
parent
commit
5289ddd423
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 49
      src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs
  2. 5
      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

49
src/Avalonia.Base/Data/Converters/StringFormatValueConverter.cs

@ -0,0 +1,49 @@
using System;
using System.Globalization;
namespace Avalonia.Data.Converters
{
/// <summary>
/// A value converter which calls <see cref="string.Format(string, object)"/>
/// </summary>
public class StringFormatValueConverter : IValueConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="StringFormatValueConverter"/> class.
/// </summary>
/// <param name="format">The format string.</param>
/// <param name="inner">
/// An optional inner converter to be called before the format takes place.
/// </param>
public StringFormatValueConverter(string format, IValueConverter inner)
{
Contract.Requires<ArgumentNullException>(format != null);
Format = format;
Inner = inner;
}
/// <summary>
/// Gets an inner value converter which will be called before the string format takes place.
/// </summary>
public IValueConverter Inner { get; }
/// <summary>
/// Gets the format string.
/// </summary>
public string Format { get; }
/// <inheritdoc/>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
value = Inner?.Convert(value, targetType, parameter, culture) ?? value;
return string.Format(culture, Format, value);
}
/// <inheritdoc/>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Two way bindings are not supported with a string format");
}
}
}

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

@ -43,8 +43,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Path = Path,
Priority = Priority,
Source = Source,
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext))
};
}
@ -79,6 +80,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; }
/// <summary>
@ -181,11 +186,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 StringFormatValueConverter(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_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.TextProperty).Observable;
Assert.IsType<StringFormatValueConverter>(expressionObserver.Converter);
}
}
public class When_Binding_To_Object
{
[Fact]
public void StringFormatConverter_Should_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.TagProperty).Observable;
Assert.IsType<StringFormatValueConverter>(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

@ -308,5 +308,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