diff --git a/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs
new file mode 100644
index 0000000000..b190f06be5
--- /dev/null
+++ b/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Avalonia.Data.Converters
+{
+ ///
+ /// A multi-value converter which calls
+ ///
+ public class StringFormatMultiValueConverter : IMultiValueConverter
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The format string.
+ ///
+ /// An optional inner converter to be called before the format takes place.
+ ///
+ public StringFormatMultiValueConverter(string format, IMultiValueConverter inner)
+ {
+ Contract.Requires(format != null);
+
+ Format = format;
+ Inner = inner;
+ }
+
+ ///
+ /// Gets an inner value converter which will be called before the string format takes place.
+ ///
+ public IMultiValueConverter Inner { get; }
+
+ ///
+ /// Gets the format string.
+ ///
+ public string Format { get; }
+
+ ///
+ public object Convert(IList values, Type targetType, object parameter, CultureInfo culture)
+ {
+ return Inner == null
+ ? string.Format(culture, Format, values.ToArray())
+ : string.Format(culture, Format, Inner.Convert(values, targetType, parameter, culture));
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
index eb2a8df2eb..45e4739eb0 100644
--- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
+++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs
@@ -64,16 +64,20 @@ namespace Avalonia.Data
object anchor = null,
bool enableDataValidation = false)
{
- if (Converter == null)
- {
- throw new NotSupportedException("MultiBinding without Converter not currently supported.");
- }
-
var targetType = targetProperty?.PropertyType ?? typeof(object);
var children = Bindings.Select(x => x.Initiate(target, null));
var input = children.Select(x => x.Observable).CombineLatest().Select(x => ConvertValue(x, targetType));
var mode = Mode == BindingMode.Default ?
targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
+
+ // 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 StringFormatMultiValueConverter(StringFormat, Converter);
+ }
switch (mode)
{
@@ -97,15 +101,6 @@ namespace Avalonia.Data
converted = FallbackValue;
}
- // 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)))
- {
- converted = string.Format(culture, StringFormat, converted);
- }
-
return converted;
}
}
diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs
index bd4b5b9d04..d3e3ce5507 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests_Converters.cs
@@ -5,11 +5,10 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;
-using Avalonia.Data.Core;
+using Avalonia.Layout;
using Xunit;
namespace Avalonia.Markup.UnitTests.Data
@@ -21,7 +20,30 @@ namespace Avalonia.Markup.UnitTests.Data
{
var textBlock = new TextBlock
{
- DataContext = new MultiBindingTests_Converters.Class1(),
+ DataContext = new Class1(),
+ };
+
+ var target = new MultiBinding
+ {
+ StringFormat = "{0:0.0} + {1:00}",
+ Bindings =
+ {
+ new Binding(nameof(Class1.Foo)),
+ new Binding(nameof(Class1.Bar)),
+ }
+ };
+
+ textBlock.Bind(TextBlock.TextProperty, target);
+
+ Assert.Equal("1.0 + 02", textBlock.Text);
+ }
+
+ [Fact]
+ public void StringFormat_Should_Be_Applied_After_Converter()
+ {
+ var textBlock = new TextBlock
+ {
+ DataContext = new Class1(),
};
var target = new MultiBinding
@@ -30,8 +52,8 @@ namespace Avalonia.Markup.UnitTests.Data
Converter = new SumOfDoublesConverter(),
Bindings =
{
- new Binding(nameof(MultiBindingTests_Converters.Class1.Foo)),
- new Binding(nameof(MultiBindingTests_Converters.Class1.Bar)),
+ new Binding(nameof(Class1.Foo)),
+ new Binding(nameof(Class1.Bar)),
}
};
@@ -45,7 +67,7 @@ namespace Avalonia.Markup.UnitTests.Data
{
var textBlock = new TextBlock
{
- DataContext = new MultiBindingTests_Converters.Class1(),
+ DataContext = new Class1(),
};
var target = new MultiBinding
@@ -54,12 +76,12 @@ namespace Avalonia.Markup.UnitTests.Data
Converter = new SumOfDoublesConverter(),
Bindings =
{
- new Binding(nameof(MultiBindingTests_Converters.Class1.Foo)),
- new Binding(nameof(MultiBindingTests_Converters.Class1.Bar)),
+ new Binding(nameof(Class1.Foo)),
+ new Binding(nameof(Class1.Bar)),
}
};
- textBlock.Bind(TextBlock.WidthProperty, target);
+ textBlock.Bind(Layoutable.WidthProperty, target);
Assert.Equal(3.0, textBlock.Width);
}