LoginSignup
2
2

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

Last updated at Posted at 2024-04-17

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

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

Github

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

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

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

前回の関連記事

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

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

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

実装の概要

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

<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>

コードビハインド側

ユーザー入力用変数とそれを反映させる変数を用意します。

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

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

あとがき

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

MainWindowのDataContextと混在させるとき

参考Link

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

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

メインサンプルのBranchを作成しました

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

外観

<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>

コードビハインド

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

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

実行結果

bandicam 2024-04-22 09-47-27-655_Harua.mp4.gif

Code

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));
        }

    }
}

コードビハインド

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

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は調子が悪かった(笑)

Github

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

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

ViewModel


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を実装

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の実装

<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

2
2
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
2
2