Строковая интерполяция в 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}}" />

Поскольку вы можете использовать любой из пользовательских форматов строк, здесь есть много возможностей. Или ты спрашиваешь что-то еще?

Другие вопросы по тегам