DataGridでAutoGenerateColumnsが有効なままで、フォーマットをカスタムする

# 概要
DataGridでデフォルトのフォーマット以外にしたいことがあります。しかしフォーマットを変えるためだけに、AutoGenerateColumnsを無効にして全ての列を自分で定義するのは面倒です。
1つの解決方法はDataGridの`AutoGeneratingColumn`イベントでフォーマットを指定することです。
しかしできればコードビハインドを書きたくないし、複数のDataGridで流用したい、そういう時は添付プロパティが使えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120077/e376fdc6-3f52-5c99-fa6e-965edb75f206.png)


# 添付プロパティ

`DateTime`用の`DateTimeFormatAutoGenerate`と`TimeSpan`用の`TimeSpanFormatAutoGenerate`の2つのフォーマットを指定できる添付プロパティが定義されています。

```csharp
class DataGridOperation
{
    public static string GetDateTimeFormatAutoGenerate(DependencyObject obj) => (string)obj.GetValue(DateTimeFormatAutoGenerateProperty);
    public static void SetDateTimeFormatAutoGenerate(DependencyObject obj, string value) => obj.SetValue(DateTimeFormatAutoGenerateProperty, value);
    public static readonly DependencyProperty DateTimeFormatAutoGenerateProperty =
        DependencyProperty.RegisterAttached("DateTimeFormatAutoGenerate", typeof(string), typeof(DataGridOperation),
            new PropertyMetadata(null, (d, e) => AddEventHandlerOnGenerating<DateTime>(d, e)));

    public static string GetTimeSpanFormatAutoGenerate(DependencyObject obj) => (string)obj.GetValue(TimeSpanFormatAutoGenerateProperty);
    public static void SetTimeSpanFormatAutoGenerate(DependencyObject obj, string value) => obj.SetValue(TimeSpanFormatAutoGenerateProperty, value);
    public static readonly DependencyProperty TimeSpanFormatAutoGenerateProperty =
        DependencyProperty.RegisterAttached("TimeSpanFormatAutoGenerate", typeof(string), typeof(DataGridOperation),
            new PropertyMetadata(null, (d, e) => AddEventHandlerOnGenerating<TimeSpan>(d, e)));

    private static void AddEventHandlerOnGenerating<T>(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is DataGrid dGrid))
            return;

        if ((e.NewValue is string format))
            dGrid.AutoGeneratingColumn += (o, e) => AddFormat_OnGenerating<T>(e, format);
    }

    private static void AddFormat_OnGenerating<T>(DataGridAutoGeneratingColumnEventArgs e, string format)
    {
        if (e.PropertyType == typeof(T))
            (e.Column as DataGridTextColumn).Binding.StringFormat = format;
    }
}
```

# 使用方法

XAML上でフォーマットを指定します。
`DataGridOperation.DateTimeFormatAutoGenerate="yy年MM月dd日"`

コードビハインドは使用していません。

```xml:MainWindow.xaml
<Window
   x:Class="DataGridAutogenerateCustom.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="clr-namespace:DataGridAutogenerateCustom"
   Width="400"
   Height="250">
   <Window.DataContext>
      <local:MainWindowViewModel />
   </Window.DataContext>
   <StackPanel>
      <TextBlock Text="DEFAULT FORMAT" />
      <DataGrid ItemsSource="{Binding Dates}" />
      <TextBlock Margin="0,30,0,0" Text="CUSTOM FORMAT" />
      <DataGrid
         local:DataGridOperation.DateTimeFormatAutoGenerate="yy年MM月dd日"
         local:DataGridOperation.TimeSpanFormatAutoGenerate="dd\日hh\時mm\分ss\秒"
         ItemsSource="{Binding Dates}" />
   </StackPanel>
</Window>
```

ViewModelは以下のようになります。


```csharp:MainWindowViewModel.cs
public class MainWindowViewModel
{
    public DatePairs[] Dates { get; } = new DatePairs[]
    {
        new (){StartDate= new (2011,1,1), EndDate= new (2011,2,1) },
        new (){StartDate= new (2020,1,1), EndDate= new (2021,1,1) },
    };
}

public class DatePairs
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public TimeSpan Span => EndDate - StartDate;
}
```

# 環境

VisualStudio 2019 Version 16.8.4
.NET 5
C#9

# 参考
https://stackoverflow.com/questions/7978249/date-formatting-in-wpf-datagrid