C#
WPF
Xaml
MultiBinding

[WPF]マルチバインディングで複数プロパティを繋げて、一つのTextBlockに表示する


やりたいこと

今まで、ビューモデルに作った単体のプロパティを画面のコントロールにバインドして、表示させたり入力を受けたりしていたが、例えば複数のstringのプロパティをつなげて、一つの文字列にして表示したいときに、複数のプロパティを一つのコントロールにバインドするようなことがしたい。

その他にも、下記のような場合に対応したい。


  • 複数のstringのプロパティをつなげて、一つの文字列にして表示したい

  • 複数のboolのプロパティを見て、コントロールの有効・無効や表示・非表示を判定したい

  • など...


実験内容

image.png

このような画面にして、以下のようなことを試す。


  • ListBoxの中身をItemTemplateでカスタムする。

  • ListBoxの中身は、マルチバインディングで作成したテキストを表示する。

  • そのテキストは、マルチバインディングした複数の項目を、コンバーターでつなげたものにする。

  • マルチバインディングするのは、ListBoxのItemSourceにバインドされたリストと、ビューモデルのプロパティ、にする。(イメージとしては、リストの中身個別の値に、全体で持ってる情報をくっつける、というイメージ)


やり方(全体コード)


画面xaml(View)


MainWindows.xaml

<Window x:Class="WpfApp1.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Name="Root">

<!-- ViewModelを関連付けする -->
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>

<!-- コンバーター -->
<Window.Resources>
<local:MyMultiStringConverter x:Key="MyMultiStringConverter"/>
</Window.Resources>

<!-- 画面表示 -->
<Grid>
<ListBox ItemsSource="{Binding ItemList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyMultiStringConverter}">
<!-- ★values[0] : リストのItemSourceの項目にバインド -->
<Binding/>
<!-- ★values[1] : ViewModelのプロパティにバインド -->
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}" Path="DataContext.Unit"/>
<!--<Binding Path="DataContext.Unit" ElementName="Root"/>--><!-- これでもOK -->
<!-- ★values[2] : コードビハインドのプロパティにバインド -->
<Binding Path="CodeBehindProp" ElementName="Root"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>



画面コードビハインド(View)


MainWindows.xaml.cs

using System.Windows;

namespace WpfApp1
{
public partial class MainWindow : Window
{
public string CodeBehindProp { get; } = "★";

public MainWindow()
{
InitializeComponent();
}
}
}



コンバーター(View)


Converter.cs

using System;

using System.Globalization;
using System.Windows.Data;

namespace WpfApp1
{
internal class MyMultiStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int val = (int)values[0]; // ★xamlのMultiBindingで、1つ目に入れたもの
string unit = (string)values[1]; // ★xamlのMultiBindingで、2つ目に入れたもの
string behind = (string)values[2]; // ★xamlのMultiBindingで、3つ目に入れたもの

return val.ToString() + unit + behind;
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}



ViewModel


ViewModel.cs

using System.Collections.ObjectModel;

namespace WpfApp1
{
class ViewModel : BindingBase
{
public ObservableCollection<int> ItemList { get => _itemList; set => _itemList = value; }
private ObservableCollection<int> _itemList = new ObservableCollection<int>();

public string Unit { get; } = "メートル";

public ViewModel()
{
// 実験用にリストを作成
ItemList.Add(1);
ItemList.Add(2);
ItemList.Add(3);
ItemList.Add(4);
ItemList.Add(5);
}
}
}



画面のxamlで、以下のことをして、複数プロパティから一つの文字列を作っている。


  • TextBlockに、マルチバインディングに使うコンバーター(MyMultiStringConverter)を指定する

  • MultiBindingの中に、Bindingするものを順番に指定する(Binding・・・)

  • コンバーターの方で、Bindingしたものを受け取って、一つの文字列につなげる


MainWindow.xaml

<TextBlock.Text>

<MultiBinding Converter="{StaticResource MyMultiStringConverter}">
<!-- ★values[0] : リストのItemSourceの項目にバインド -->
<Binding/>
<!-- ★values[1] : ViewModelのプロパティにバインド -->
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}" Path="DataContext.Unit"/>
<!-- ★values[2] : コードビハインドのプロパティにバインド -->
<Binding Path="CodeBehindProp" ElementName="Root"/>
</MultiBinding>
</TextBlock.Text>

コンバーターの方では、上のxamlの<MultiBinding>の中に並べた<Binding...>が、valuesの配列として順番に渡されてくる。

今回は、それを順番に受けてつなげてから、一つの文字列として返している。

(それが、MultiBindingした結果として、TextBlockに表示される)


Converters.cs

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

{
int val = (int)values[0]; // ★xamlのMultiBindingで、1つ目に入れたもの
string unit = (string)values[1]; // ★xamlのMultiBindingで、2つ目に入れたもの
string behind = (string)values[2]; // ★xamlのMultiBindingで、3つ目に入れたもの

return val.ToString() + unit + behind;// ★これが、TextBlockに表示される
}



備考

今回は、MultiBindingしたものを一つの文字列につなげる、ということをしたが、

TextBlockではなく、例えば<Button>のIsEnabledにMultiBindingして、複数のboolを受けてorとかandをとって、一個でもフラグ立ってたら/全部のフラグが立ってたらボタン有効、などできそう。

また、今回やってることは、マルチバインディングを使わなくても、下記のxamlのように普通のBindingでできてしまうが、ここにConverterの中でなにか計算したいとかになったときに、MultiBindingが便利なのではないかと思う。


同じことが普通のBindingでできる例.xaml

<Grid>

<ListBox ItemsSource="{Binding ItemList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=DataContext.Unit}"/>
<TextBlock Text="{Binding CodeBehindProp, ElementName=Root}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>