7

数値にバインドするときにゼロを空の文字列に置き換えるなど、いくつかの単純なシナリオで値コンバーターの代わりに使用するなど、XAML で C# 6 文字列補間を進める方法を考えています。

その設計議論から:

補間された文字列は、文字列の「穴」を埋める式と共に文字列のテキストを書き込むことによって、型 String (または IFormattable) の値を構築する方法です。コンパイラは、補間された文字列からフォーマット文字列と一連の埋め込み値を構築します。

.g.i.csただし、私が疑ったように、別のコンパイラを使用して BAML を生成し、生成されたファイルに文字列の痕跡が見つからないため、XAML からは使用できないようです。

  • 文字列補間は XAML でサポートされていませんか?
  • どのような回避策がありますか? 文字列補間を動的にコンパイルするためにマークアップ拡張機能を使用している可能性がありますか?
4

2 に答える 2

4

これは、バインディングが WPF で機能する方法が原因で、サポートが難しいものです。C# コードの文字列補間は呼び出しに直接コンパイルできstring.Format、基本的に便利な構文糖衣を提供するだけです。ただし、バインディングでこれを機能させるには、実行時に何らかの作業を行う必要があります。

これを実行できる単純なクラスを作成しましたが、いくつかの制限があります。特に、すべてのバインド パラメーターの受け渡しをサポートしていません。また、中かっこをエスケープする必要があるため、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 に答える