Строковая интерполяция в XAML
Я думаю о том, как добиться ускорения интерполяции строк C# 6 в XAML, например, использовать их вместо преобразователей значений в некоторых простых сценариях, таких как замена нуля пустой строкой при привязке к числам.
Из его обсуждения дизайна:
Интерполированная строка - это способ создать значение типа String (или IFormattable), записав текст строки вместе с выражениями, которые заполнят "дыры" в строке. Компилятор создает строку формата и последовательность значений заполнения из интерполированной строки.
Однако, как я и подозревал, кажется, что они не могут быть использованы из XAML, так как он использует другой компилятор для генерации BAML, и я не нахожу никаких следов строк в сгенерированном .g.i.cs
файлы.
- Строковые интерполяции не поддерживаются в XAML?
- Какие обходные пути могут быть? Может быть, используя расширения разметки для динамической компиляции строковых интерполяций?
2 ответа
Это сложно поддерживать из-за того, как Binding работает в WPF. Строковые интерполяции в коде C# могут быть скомпилированы непосредственно в string.Format
звонки и в основном просто обеспечивают удобный синтаксический сахар. Однако, чтобы сделать эту работу с Binding, необходимо выполнить некоторую работу во время выполнения.
Я собрал простой класс, который может сделать это, хотя у него есть несколько ограничений. В частности, он не поддерживает прохождение всех параметров привязки и неудобно вводить в 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;
}
}
}
Я провел очень ограниченное тестирование, поэтому я рекомендую более тщательное тестирование и усиление, прежде чем использовать его в производстве. Надеюсь, это будет хорошей отправной точкой для кого-то, чтобы сделать что-то полезное.
Это очень похоже на атрибут StringFormat, представленный в.Net 3.5. Как вы цитируете, "записывая текст строки вместе с выражениями, которые будут заполнять" дыры "в строке", это можно выполнить в привязке XAML следующим образом:
<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />
Поскольку вы можете использовать любой из пользовательских форматов строк, здесь есть много возможностей. Или ты спрашиваешь что-то еще?