WPF - TextBlockの語順を変更
TextBlockに表示されるメッセージ内の語順を変更したいことが時々あります。わかりやすいのは分数の表現です。日本語では「5分の1」と書き、分母が先に来ます。英語では「1 of 5」と書き、分子が先に来ます。
分子と分母がバインド先のプロパティにある場合、語順の変化を吸収するには何らかの工夫が必要です。両方のTextBlockを用意しておき言語によって切り替えれば簡単に実現できますが、それではどちらのTextBlockを使うかを決める別のプロパティが必要になります。できれば、そのような面倒で間違いやすいものに頼らず、言語対応で変更される文字列だけで制御したいものです。
以下では、直感的にわかりやすいMultiBindingを使った方法を紹介します。
目次
本来やりたいこと(でもできない)
上に掲げた分数の例では、本来なら、以下のように書きたいのです。
<TextBlock> <TextBlock.Text> <MultiBinding StringFormat="{Binding FractionFormat}"> // StringFormatではBindingが機能しない。 <Binding Path="Molecule"/> <Binding Path="Denominator"/> </MultiBinding> </TextBlock.Text> </TextBlock>
// バインドしたい書式文字列 public string FractionFormat { get; } = Fraction is {0} of {1}";
MultiBindingのStringFormatに書式文字列を収めたプロパティをバインドできれば、何もせずとも語順変更を実現できるのですが、StringFormatにはBindingを指定できません。つまり、上のような書き方をしてもダメなのです。
解決策
書式文字列FractionFormatをバインドできるのはMultiBindingの中身のBinding要素だけなので、そちらに移動してしまいます。
<Window x:Class="MultiBindExtension.MainWindow" ... xmlns:local="clr-namespace:MultiBindExtension" /> <Window.Resources> <local:MultiBindFormatConverter x:Key="MyConverter"/> // このコンバータがポイント。 </Window.Resources> <TextBlock> <TextBlock.Text> <MultiBinding Converter="{StaticResource MyConverter}"> <Binding Path="FractionFormat"/> // 書式をこちらに移動した。 <Binding Path="Molecule"/> <Binding Path="Denominator"/> </MultiBinding> </TextBlock.Text> </TextBlock> </Window>
移動しただけだと書式制御にならないので、コンバーターMultiBindFormatConverterを用いて、書式制御させます。このコンバーターのソースは以下のとおりです。
// コンバーターのソースコード namespace MultiBindExtension { public class MultiBindFormatConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) => string.Format((string)values[0], values.Skip(1).ToArray()); public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); } }
string.Format()に与える書式文字列として最初のBindingから得られた文字列を用いています。
この方法なら、FractionFormatを変更すれば、表示もそれに合わせて自動的に変わります。プログラム実行中に言語を切り替える場合には便利です。
例外処理を全くしていませんが、コンバーターの使い方に誤りがあれば、デバッグ段階でstring.Format()が例外を起こしてくれますので、ここでは特に処理しなくて大丈夫だと思い省略しました。