7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

やりたいこと

今まで、ビューモデルに作った単体のプロパティを画面のコントロールにバインドして、表示させたり入力を受けたりしていたが、例えば複数の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>
7
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?