7

我正在考虑在 XAML 中推进 C# 6 字符串插值的方法,例如在一些简单的场景中使用它们而不是值转换器,例如在绑定到数字时用空字符串替换零。

从其设计讨论中

内插字符串是一种构造字符串(或 IFormattable)类型值的方法,方法是编写字符串的文本以及将填充字符串中“孔”的表达式。编译器从插值字符串构造格式字符串和填充值序列。

但是,正如我所怀疑的那样,它们似乎不能从 XAML 中使用,因为它使用不同的编译器来生成 BAML,而且我在生成的.g.i.cs文件中找不到字符串的痕迹。

  • XAML 不支持字符串插值吗?
  • 可能有什么解决方法?也许使用标记扩展来动态编译字符串插值?
4

2 回答 2

6

这听起来很像 .Net 3.5 中引入的 StringFormat 属性。正如您所引用的,“编写字符串的文本以及将填充字符串中的“孔”的表达式”,这可以在 XAML 绑定中执行,如下所示:

<TextBlock Text="{Binding Amount, StringFormat=Total: {0:C}}" />

由于您可以使用任何自定义字符串格式,因此这里有很多强大的功能。还是你在问别的?

于 2015-05-26T16:10:31.693 回答
4

由于 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;
        }
    }
}

我做了非常有限的测试,所以我建议在生产中使用它之前进行更彻底的测试和强化。不过,应该希望是某人做出有用的东西的一个很好的起点。

于 2015-11-18T16:41:56.657 回答