やりたいこと
今まで、ビューモデルに作った単体のプロパティを画面のコントロールにバインドして、表示させたり入力を受けたりしていたが、例えば複数のstringのプロパティをつなげて、一つの文字列にして表示したいときに、複数のプロパティを一つのコントロールにバインドするようなことがしたい。
その他にも、下記のような場合に対応したい。
- 複数のstringのプロパティをつなげて、一つの文字列にして表示したい
- 複数のboolのプロパティを見て、コントロールの有効・無効や表示・非表示を判定したい
- など...
実験内容
このような画面にして、以下のようなことを試す。
- ListBoxの中身をItemTemplateでカスタムする。
- ListBoxの中身は、マルチバインディングで作成したテキストを表示する。
- そのテキストは、マルチバインディングした複数の項目を、コンバーターでつなげたものにする。
- マルチバインディングするのは、ListBoxのItemSourceにバインドされたリストと、ビューモデルのプロパティ、にする。(イメージとしては、リストの中身個別の値に、全体で持ってる情報をくっつける、というイメージ)
やり方(全体コード)
画面xaml(View)
<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)
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public string CodeBehindProp { get; } = "★";
public MainWindow()
{
InitializeComponent();
}
}
}
コンバーター(View)
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
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したものを受け取って、一つの文字列につなげる
<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に表示される)
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が便利なのではないかと思う。
<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>