[C#/WPF] コンバーターの書き方

# もくじ

https://tera1707.com/entry/2022/02/06/144447

# やりたいこと

WPFで画面を作るときにコンバーターをよく使うが、都度既存のコードからコピペしていて身についた感がなかったので、テンプレートとして持っておくために下記にメモしておく。


# コンバーターの作り方

今回は、[MSの公式サンプル](https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8)をもとに、

- DateTime型の値を、画面表示の際に日付の文字列に変換する
- 日付の文字列を、プロパティget時にDateTime型の値に変換する

というコンバーターを作ってみる。

## コンバーターのクラスを作る

クラス名は`DateTimeConverter`のように、`〇〇Conveter`というようにするのが慣例っぽい。
その名前で、IValueConverterインターフェースを実装した下記のようなクラスを作る。

- Convertメソッドが「プロパティ→画面表示に反映時」の変換を記述するところ。
- ConvertBackメソッドが「画面表示→プロパティに反映時」の変換記述するところ。

```DateTimeConverter.cs
public class DateTimeConverter : IValueConverter
{
    // DateTimeを「yyyy/MM/dd HH:mm:ss.fff」形式の文字列に変換
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        DateTime date = (DateTime)value;
        return date.ToString("yyyy/MM/dd HH:mm:ss.fff");
    }
    // 文字列をDateTimeに変換
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string strValue = value as string;
        DateTime resultDateTime;
        if (DateTime.TryParse(strValue, out resultDateTime))
        {
            return resultDateTime;
        }
        return DependencyProperty.UnsetValue;
    }
}
```

※個人的経験では、ほとんどの場合「プロパティ→画面表示に反映時」に変換することしかしないので、`Converter`メソッドだけ使って`ConvertBack`メソッドはほとんど使ったことがない。

そういう場合は、`ConvertBack`の中身は下記のようにしておけばいい。

```cs
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
    throw new NotImplementedException();
}
```


## 画面にリソースとして登録し、それを変換したいところで使う

一番の親(今回はWindow)のリソースとして、下記のようにして登録する。

```MainWIndowの一部.xaml
<Window.Resources>
    <converter:DateTimeConverter x:Key="DateTimeConverter"/>
</Window.Resources>
```

そいつのKeyを使って、変換を行いたいコントロールにバインドしたプロパティのコンバーターとして使用する。
(今回は、ConverterParameterは別に不要だが、パラメータ渡せるということを示すために渡すだけ渡す)

```MainWIndowの一部.xaml
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Dt, ElementName=root, Converter={StaticResource DateTimeConverter}, ConverterParameter=123}"/>
```

## ViewModel等に、バインドするプロパティを作成して、get/setしてみる

下記のように、画面にバインドしたプロパティに

- set(今回は`Dt`に現在の日付をセットした)
- get(今回は`Dt`の値をゲットして`yyyyねんMMがつddにち`形式で表示した)

```MainWindow.xaml.cs
public DateTime Dt
{
    get { return _dt; }
    set { _dt = value; OnPropertyChanged(nameof(Dt)); }
}
// テキストボックス ← プロパティ
private void Button_Click(object sender, RoutedEventArgs e)
{
    // set
    Dt = DateTime.Now;
}
// テキストボックス → プロパティ
private void Button_Click_1(object sender, RoutedEventArgs e)
{
    // get
    AddLog(Dt.ToString("入力した日付は、yyyyねんMMがつddにち です"));
}
```

そうすると、コンバータを通って、下記のように画面に表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/6d03db7e-2d30-6fb7-68a2-9648336064af.png)

以上で、一通りコンバーターが使えるようになった。

# コード全部

```DateTimeConverter.cs
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace WpfApp48.converter
{
    [ValueConversion(typeof(DateTime), typeof(string))]
    public class DateTimeConverter : IValueConverter
    {
        // DateTimeを「yyyy/MM/dd HH:mm:ss.fff」形式の文字列に変換
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.ToString("yyyy/MM/dd HH:mm:ss.fff");
        }
        // 文字列をDateTimeに変換
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string strValue = value as string;
            DateTime resultDateTime;
            if (DateTime.TryParse(strValue, out resultDateTime))
            {
                return resultDateTime;
            }
            return DependencyProperty.UnsetValue;
        }
    }
}
```
```MainWindow.xaml
<Window x:Class="WpfApp48.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:WpfApp48"
        xmlns:converter="clr-namespace:WpfApp48.converter"
        mc:Ignorable="d"
        Title="MainWindow" Height="600" Width="800"
        Loaded="Window_Loaded"
        Name="root">
    <Window.Resources>
        <converter:DateTimeConverter x:Key="DateTimeConverter"/>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="5*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Row="1" Grid.Column="0">
            <Button Content="1" Click="Button_Click"/>
            <Button Content="2" Click="Button_Click_1"/>
        </StackPanel>

        <ListBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Logs, ElementName=root}"/>

        <TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Dt, ElementName=root, Converter={StaticResource DateTimeConverter}, ConverterParameter=123}"/>
    </Grid>
</Window>

```
```MainWindow.xaml.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace WpfApp48
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        #endregion

        #region LogFramework
        public ObservableCollection<string> Logs { get; set; } = new ObservableCollection<string>();

        public void AddLog(string log)
        {
            DateTime now = DateTime.Now;
            Logs.Add(now.ToString("hh:mm:ss.fff ") + log);
            OnPropertyChanged(nameof(Logs));
        }
        #endregion

        DateTime _dt = DateTime.Now;


        public MainWindow() => InitializeComponent();
        private void Window_Loaded(object sender, RoutedEventArgs e) { }

        public DateTime Dt
        {
            get { return _dt; }
            set { _dt = value; OnPropertyChanged(nameof(Dt)); }
        }

        // テキストボックス ← プロパティ
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Dt = DateTime.Now;
        }

        // テキストボックス → プロパティ
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            AddLog(Dt.ToString("入力した日付は、yyyyねんMMがつddにち です"));
        }
    }
}
```


# 参考

How to: Convert Bound Data
https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/data/how-to-convert-bound-data?view=netframeworkdesktop-4.8