XAML 中的字符串插值
String interpolation in XAML
我正在考虑在 XAML 中推进 C# 6 字符串插值的方法,例如在一些简单的场景中使用它们而不是值转换器,例如在绑定到数字时用空字符串替换零.
An interpolated string is a way to construct a value of type String
(or IFormattable) by writing the text of the string along with
expressions that will fill in "holes" in the string. The compiler
constructs a format string and a sequence of fill-in values from the
interpolated string.
然而,正如我所怀疑的那样,它们似乎不能从 XAML 使用,因为它使用不同的编译器生成 BAML,而且我在生成的 [=11] 中找不到字符串的踪迹=] 个文件。
- XAML 不支持字符串插值吗?
- 有什么解决方法?也许使用标记扩展来动态编译字符串插值?
这听起来很像 .Net 3.5 中引入的 StringFormat 属性。正如您引用的那样,"writing the text of the string along with expressions that will fill in 'holes' in the string",这可以在 XAML 绑定中执行,如下所示:
<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />
由于您可以使用任何自定义字符串格式,因此这里的功能非常强大。还是您在问其他问题?
由于绑定在 WPF 中的工作方式,这很难支持。 C# 代码中的字符串插值可以直接编译为 string.Format
调用,基本上只是提供一个方便的语法糖。不过,要使用 Binding 进行这项工作,有必要在运行时做一些工作。
我整理了一个简单的 class 可以做到这一点,尽管它有一些限制。特别是,它不支持传递所有绑定参数并且输入 XAML 很尴尬,因为您必须转义花括号(也许值得使用不同的字符?)它应该处理多路径绑定和任意复杂的格式字符串,只要它们被正确转义以用于 XAML.
关于您问题中的一个特定点,这不允许您像在内插字符串中那样嵌入任意表达式。如果你想这样做,你就必须变得更花哨一些,并根据绑定值做一些像即时代码编译这样的事情。您很可能需要发出一个采用参数值的函数调用,然后将其作为值转换器的委托调用并让它执行嵌入的表达式。应该是可以的,但可能不太容易实现。
用法如下:
<TextBlock Text="{local:InterpolatedBinding '\{TestString\}: \{TestDouble:0.0\}'}"/>
这是完成这项工作的标记扩展:
public sealed class InterpolatedBindingExtension : MarkupExtension
{
private static readonly Regex ExpressionRegex = new Regex(@"\{([^\{]+?)(?::(.+?))??\}", RegexOptions.Compiled);
public InterpolatedBindingExtension()
{
}
public InterpolatedBindingExtension(string expression)
{
Expression = expression;
}
public string Expression { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
//Parse out arguments captured in curly braces
//If none found, just return the raw string
var matches = ExpressionRegex.Matches(Expression);
if (matches.Count == 0)
return Expression;
if (matches.Count == 1)
{
var formatBuilder = new StringBuilder();
//If there is only one bound target, can use a simple binding
var varGroup = matches[0].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(0, varGroup.Index));
formatBuilder.Append('0');
formatBuilder.Append(Expression.Substring(varGroup.Index + varGroup.Length));
binding.Converter = new FormatStringConverter(formatBuilder.ToString());
return binding.ProvideValue(serviceProvider);
}
else
{
//Multiple bound targets, so we need a multi-binding
var multiBinding = new MultiBinding();
var formatBuilder = new StringBuilder();
int lastExpressionIndex = 0;
for (int i=0; i<matches.Count; i++)
{
var varGroup = matches[i].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(lastExpressionIndex, varGroup.Index - lastExpressionIndex));
formatBuilder.Append(i.ToString());
lastExpressionIndex = varGroup.Index + varGroup.Length;
multiBinding.Bindings.Add(binding);
}
formatBuilder.Append(Expression.Substring(lastExpressionIndex));
multiBinding.Converter = new FormatStringConverter(formatBuilder.ToString());
return multiBinding.ProvideValue(serviceProvider);
}
}
private sealed class FormatStringConverter : IMultiValueConverter, IValueConverter
{
private readonly string _formatString;
public FormatStringConverter(string formatString)
{
_formatString = formatString;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
我进行的测试非常有限,因此我建议在将其用于生产之前进行更彻底的测试和强化。不过,希望它能成为某人制作有用的东西的良好起点。
我正在考虑在 XAML 中推进 C# 6 字符串插值的方法,例如在一些简单的场景中使用它们而不是值转换器,例如在绑定到数字时用空字符串替换零.
An interpolated string is a way to construct a value of type String (or IFormattable) by writing the text of the string along with expressions that will fill in "holes" in the string. The compiler constructs a format string and a sequence of fill-in values from the interpolated string.
然而,正如我所怀疑的那样,它们似乎不能从 XAML 使用,因为它使用不同的编译器生成 BAML,而且我在生成的 [=11] 中找不到字符串的踪迹=] 个文件。
- XAML 不支持字符串插值吗?
- 有什么解决方法?也许使用标记扩展来动态编译字符串插值?
这听起来很像 .Net 3.5 中引入的 StringFormat 属性。正如您引用的那样,"writing the text of the string along with expressions that will fill in 'holes' in the string",这可以在 XAML 绑定中执行,如下所示:
<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />
由于您可以使用任何自定义字符串格式,因此这里的功能非常强大。还是您在问其他问题?
由于绑定在 WPF 中的工作方式,这很难支持。 C# 代码中的字符串插值可以直接编译为 string.Format
调用,基本上只是提供一个方便的语法糖。不过,要使用 Binding 进行这项工作,有必要在运行时做一些工作。
我整理了一个简单的 class 可以做到这一点,尽管它有一些限制。特别是,它不支持传递所有绑定参数并且输入 XAML 很尴尬,因为您必须转义花括号(也许值得使用不同的字符?)它应该处理多路径绑定和任意复杂的格式字符串,只要它们被正确转义以用于 XAML.
关于您问题中的一个特定点,这不允许您像在内插字符串中那样嵌入任意表达式。如果你想这样做,你就必须变得更花哨一些,并根据绑定值做一些像即时代码编译这样的事情。您很可能需要发出一个采用参数值的函数调用,然后将其作为值转换器的委托调用并让它执行嵌入的表达式。应该是可以的,但可能不太容易实现。
用法如下:
<TextBlock Text="{local:InterpolatedBinding '\{TestString\}: \{TestDouble:0.0\}'}"/>
这是完成这项工作的标记扩展:
public sealed class InterpolatedBindingExtension : MarkupExtension
{
private static readonly Regex ExpressionRegex = new Regex(@"\{([^\{]+?)(?::(.+?))??\}", RegexOptions.Compiled);
public InterpolatedBindingExtension()
{
}
public InterpolatedBindingExtension(string expression)
{
Expression = expression;
}
public string Expression { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
//Parse out arguments captured in curly braces
//If none found, just return the raw string
var matches = ExpressionRegex.Matches(Expression);
if (matches.Count == 0)
return Expression;
if (matches.Count == 1)
{
var formatBuilder = new StringBuilder();
//If there is only one bound target, can use a simple binding
var varGroup = matches[0].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(0, varGroup.Index));
formatBuilder.Append('0');
formatBuilder.Append(Expression.Substring(varGroup.Index + varGroup.Length));
binding.Converter = new FormatStringConverter(formatBuilder.ToString());
return binding.ProvideValue(serviceProvider);
}
else
{
//Multiple bound targets, so we need a multi-binding
var multiBinding = new MultiBinding();
var formatBuilder = new StringBuilder();
int lastExpressionIndex = 0;
for (int i=0; i<matches.Count; i++)
{
var varGroup = matches[i].Groups[1];
var binding = new Binding();
binding.Path = new PropertyPath(varGroup.Value);
binding.Mode = BindingMode.OneWay;
formatBuilder.Append(Expression.Substring(lastExpressionIndex, varGroup.Index - lastExpressionIndex));
formatBuilder.Append(i.ToString());
lastExpressionIndex = varGroup.Index + varGroup.Length;
multiBinding.Bindings.Add(binding);
}
formatBuilder.Append(Expression.Substring(lastExpressionIndex));
multiBinding.Converter = new FormatStringConverter(formatBuilder.ToString());
return multiBinding.ProvideValue(serviceProvider);
}
}
private sealed class FormatStringConverter : IMultiValueConverter, IValueConverter
{
private readonly string _formatString;
public FormatStringConverter(string formatString)
{
_formatString = formatString;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, values);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(string))
return null;
return string.Format(_formatString, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}
我进行的测试非常有限,因此我建议在将其用于生产之前进行更彻底的测试和强化。不过,希望它能成为某人制作有用的东西的良好起点。