【C# WPF】 マルチバインディングでユーザー入力に応じた動的文字列生成

4/29　Codeを動作するように修正。Converterの追加。Git修正

※検証したらうまくいかなかったのでGitに上げます。

#  Github

https://github.com/Sheephuman/WPFMaltiBindingTest_bySheephuman

```slim
git clone https://github.com/Sheephuman/MutiBingingTest.git
```


WPF Sheep Calender5日目　巻き角プロジェクトみたいな名前に替えようかな

この記事を参考に実装（実を言うと再現出来なかったのでChatGPTを使った。時間もないんで）

https://qiita.com/tera1707/items/8a6ee07ffe2d6f4c05e1

前回の関連記事

https://qiita.com/EndOfData/items/2e8bbd04f78a9b6d27de

Ffmpegを使用したGUIフロントエンド「はるあこんば~た」を制作しています。


どうにも奥が深いので、原典でも漁るのがいいんでしょうかねえ。
実装は再現しやすいように出来るだけ単純化しています。

converterは使用せず、StringFormatだけで何とかします。



# 実装の概要
以下の実装はマルチバインディングを使用して、入力フォーマットを　``${"-b:v{}k"}``とし、  それをターゲット コントロールに割り当てます。


```xml
<Window x:Class="MutiBingingTest.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:MutiBingingTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <!-- ユーザー入力のためのテキストボックス -->
        <TextBox x:Name="textBoxInput" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Height="30"
                 Text="{Binding UserInput, UpdateSourceTrigger=PropertyChanged}"/>

        <!-- フォーマットされた文字列を表示するテキストブロック -->
        <TextBlock HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top" FontSize="16">
            <TextBlock.Text>
                <!-- 入力の順番を設定　{}：XAMLのエスケープシーケンスで、文字列のフォーマットを始めることを示します。
                {0}：最初のバインドソースの値を挿入する場所 -->
                <MultiBinding StringFormat="{}{1}">
                    <!-- ユーザー入力から更新されたStringBのバインド設定 -->
                    <Binding Path="UserInput" />
                    <!-- 何も表示しないが、更新トリガーのためにUserInputをバインド -->
                    <Binding Path="StringB"/>
                   
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>
</Window>

```


# コードビハインド側
ユーザー入力用変数とそれを反映させる変数を用意します。

```csharp:MainWindow.xaml
using System.ComponentModel;
using System.Windows;

namespace MutiBingingTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window,INotifyPropertyChanged
    {
        private string _userInput = string.Empty;



        //ユーザー入力用文字列
        public string UserInput
        {
            get => _userInput;
            set
            {
                if (_userInput != value)
                {
                    _userInput = value;
                    StringB = value;  // ここを変更
                    OnPropertyChanged(nameof(UserInput));
                  
                    //$"-b:v {value}"
                }
            }
        }


        // ビューモデルのプロパティを公開するためのプロパティ
        private string _stringB = string.Empty;
        // INotifyPropertyChangedのイベント
        public event PropertyChangedEventHandler? PropertyChanged;

        // StringBプロパティ
        public string StringB
        {
            get { return _stringB; }
            set
            {

                    _stringB = $"-b:v {value}k";
                    OnPropertyChanged(nameof(StringB));  // 変更通知

                
            }
        }


        public MainWindow()
        {
            InitializeComponent();
            // ビューモデルまたはコードビハインドのプロパティに初期値を設定

            DataContext = this;
         
        }
        // プロパティが変更された時にUIに通知するためのヘルパーメソッド
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
```




# 実行結果のGIF
![bandicam 2024-04-17 08-41-52-319_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/9a9c31f2-a6fd-7c86-c402-f9ec53823dfc.gif)

実装内容は100％完璧に再現出来ることを保証します。


# あとがき
気が向いたらもうちょっと高度な内容も書きます。

# MainWindowのDataContextと混在させるとき
参考Link

https://qiita.com/EndOfData/items/2e8bbd04f78a9b6d27de#mainwindow%E3%81%AEdatacontext%E3%81%A8%E5%85%B1%E5%AD%98%E3%81%95%E3%81%9B%E3%82%8B%E3%81%AB%E3%81%AF



# コードビハインドで個別にmultiBindingを行う方法

GPT-4に何とか吐き出させました。これがなかったら数か月は掛かっている(;^ω^)
要するに全てコードビハインドで書く必要がある


メインサンプルのBranchを作成しました
```slim
git clone https://github.com/Sheephuman/WPFMaltiBindingTest_bySheephuman.git
```

### 外観
```xojo
<Window x:Class="WPFMaltiBindingTest.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:WPFMaltiBindingTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <!-- Text box for user input -->
        <TextBox x:Name="textBoxInput" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Height="30"/>

        <!-- Text block displaying formatted string -->
        <TextBlock x:Name="textBlockOutput" HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top" FontSize="16"/>
    </Grid>
</Window>

```

### コードビハインド

```csharp
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WPFMaltiBindingTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _userInput = string.Empty;

        // ユーザー入力用文字列
        public string UserInput
        {
            get => _userInput;
            set
            {
                if (_userInput != value)
                {
                    _userInput = value;
                    StringB = $"-b:v {_userInput}k";
                    OnPropertyChanged(nameof(UserInput));
                }
            }
        }

        private string _stringB = string.Empty;

        // StringBプロパティ
        public string StringB
        {
            get => _stringB;
            set
            {
                if (_stringB != value)
                {
                    _stringB = value;
                    OnPropertyChanged(nameof(StringB));
                }
            }
        }

        // INotifyPropertyChangedのイベント
        public event PropertyChangedEventHandler? PropertyChanged;

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

        // バインディングのセットアップ
        private void SetupBindings()
        {
            // ユーザー入力バインディング
            Binding userInputBinding = new Binding("UserInput")
            {
                Source = this,
                UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
            };
            textBoxInput.SetBinding(TextBox.TextProperty, userInputBinding);

            // マルチバインディング設定
            MultiBinding multiBinding = new MultiBinding()
            {
                StringFormat = "{0}"
            };
            multiBinding.Bindings.Add(new Binding("StringB") { Source = this, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
            textBlockOutput.SetBinding(TextBlock.TextProperty, multiBinding);
        }

        // プロパティが変更された時にUIに通知するためのヘルパーメソッド
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

```


こんな風に個別に書かないと動きません。
つまりXAML側とはインスタンスが別なのだと考えられます（かなり多分）

こうも書けますが、管理が煩雑になるので```DataContext = this```;のままで実装する事をおすすめします。基本的にXAML実装とは混在出来ません。どちらかが上書きされて意図通りに動作しない。

# 参考　マルチバインディングを使わない例

テキストボックスを2つ配置し、文字列を組み合わせて表示。
マルチバインディングに拘らない方が上手くいくらしい。
実際の実装ではComboBoxを使う。

### github


```slim
git clone https://github.com/Sheephuman/WPFMaltiBindingTest_bySheephuman.git
```

### 実行結果
![bandicam 2024-04-22 09-47-27-655_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/32e28b18-0dd1-aa15-7d3d-61ac5d695fab.gif)



# Code

```csharp:QueryField.cs

using System.ComponentModel;

namespace WPFMaltiBindingTest
{
    public class QueryField : INotifyPropertyChanged
    {
        private string _userInputA = string.Empty;
        public string UserInputA
        {
            get => _userInputA;
            set
            {
                if (_userInputA != value)
                {
                    _userInputA = value;
                    OnPropertyChanged(nameof(UserInputA));
                    UpdateAllInput();
                }
            }
        }

        private string _userInputB = string.Empty;
        public string UserInputB
        {
            get => _userInputB;
            set
            {
                if (_userInputB != value)
                {
                    _userInputB = value;
                    OnPropertyChanged(nameof(UserInputB));
                    UpdateAllInput();
                }
            }
        }

        private string _allInput = string.Empty;
        public string AllInput
        {
            get => _allInput;
            private set
            {
                if (_allInput != value)
                {
                    _allInput = value;
                    OnPropertyChanged(nameof(AllInput));
                }
            }
        }
        //User入力文字列を足し合わせるメソッド
        private void UpdateAllInput()
        {
            AllInput = $"-b:v {_userInputA}k -codec:v {_userInputB}";
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

    }
}

```
# コードビハインド

```csharp:MainWIndow.xaml.cs
using System.ComponentModel;
using System.Windows;

namespace WPFMaltiBindingTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
        {     
            public MainWindow()
            {
                InitializeComponent();
                // ビューモデルまたはコードビハインドのプロパティに初期値を設定


                // DataContextにデータクラスをセット
                var qf =new QueryField();
                DataContext = qf;
            }   
        }
    }
```

# XAML
```xml:MainWindow.xaml
<Window x:Class="WPFMaltiBindingTest.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:WPFMaltiBindingTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <!-- ユーザー入力のためのテキストボックス -->
        <StackPanel>
            <TextBox x:Name="UserInputA" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Height="30"
                     Text="{Binding UserInputA, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBox x:Name="UserInputB" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Height="30"
                     Text="{Binding UserInputB, UpdateSourceTrigger=PropertyChanged}"/>

            <!-- フォーマットされた文字列を表示するテキストブロック -->
            <TextBlock HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top" FontSize="16"
                   Text="{Binding AllInput}">
            </TextBlock>
        </StackPanel>
    </Grid>
</Window>

```


# Converterを利用した方法

意外と簡単なので追記。コード量も短くて済む。

参考にしたページ。ChatGPTは調子が悪かった(笑)

https://www.codeproject.com/Articles/1065904/WPF-Fundamental-Concepts-in-Easy-Samples-Part

## Github
SampleCodeがダウンロード可能だが、古いのでこちらでも用意しておくことにする。
Visual Studio 2022 .net8を指定している

```slim:git
git clone https://github.com/Sheephuman/ConverterBindingTest.git
```




## ViewModel

```Csharp

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConvertetBindingTest
{
    // ViewModel.cs
    public class ViewModel : INotifyPropertyChanged
    {
        private string _inputA = string.Empty;
        public string InputA
        {
            get => _inputA;
            set
            {
                _inputA = value;
                OnPropertyChanged(nameof(InputA));
            }
        }

        private string _inputB = string.Empty;
        public string InputB
        {
            get => _inputB;
            set
            {
                _inputB = value;
                OnPropertyChanged(nameof(InputB));
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

```

## Converterを実装

```csharp
using System.Globalization;
using System.Windows.Data;

namespace ConvertetBindingTest
{
    // YourConverter.cs
    public class SheepConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            string result = string.Empty;

            result = values[0].ToString() + values[1].ToString()
                + values[2].ToString();

            return result;
        }

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

```



## XAMLの実装

```xml
<Window x:Class="ConvertetBindingTest.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:ConvertetBindingTest" d:DataContext="{d:DesignInstance Type=local:ViewModel}"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <!-- コンバーター -->
   
    <Grid>
        <Grid.Resources>
            <local:SheepConverter x:Key="SheepConverter"/>
        </Grid.Resources>
        <TextBlock Height="50" Background="AliceBlue">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource SheepConverter}">
                    <Binding Path="Text" 
                ElementName="Input1"/>
                    <Binding Path="Text"
                ElementName="Input2" />
                    <Binding Path="Text"
                ElementName="Input3" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        <StackPanel Orientation="Vertical">
            <TextBox x:Name="Input1"
          Grid.Column="0"
          HorizontalAlignment="Stretch"
          Margin="2,0"/>
            <TextBox x:Name="Input2"
          Grid.Column="1"
          HorizontalAlignment="Stretch"
          Margin="2,0" />
            <TextBox x:Name="Input3"
          Grid.Column="2"
          HorizontalAlignment="Stretch"
          Margin="2,0" />
        </StackPanel>
    </Grid>
</Window>

```


# 実行結果

![bandicam 2024-04-29 01-00-24-580_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/542a7cbc-55fd-f577-d1d8-2161624295c1.gif)