DataGrid に新規追加行(NewItemPlaceholder)を表示する方法

WPF の DataGrid に新規追加の行(NewItemPlaceholder)が表示されなくて数日はまったので、解決方法のメモ。新規追加の行と言ってるのは、ユーザがアイテムを新たに追加するためのプレースホルダのこと。表示される場合は、デフォルトでは空の行が表示されます。かっこ悪いです。
DataGrid に渡すデータはよっぽどのことがなければ ObservableCollection を使うと思いますが、この T が問題でした。この T に引数なしのコンストラクタがないといけません。
マニュアルに書いといて欲しい…。

以上。で終わるのもなんなのでサンプル。

まずはダメな例。FileInfo には引数なしのコンストラクタがないので、これを DataGrid にバインディングしても新規追加の行は表示されません。

// ダメな例。DataGridに追加のための行が表示されない
private ObservableCollection<FileInfo> dameCreateData()
{
    var fs = Directory.GetFiles( "." ).Select( s => new FileInfo( s ) );
    var data = new ObservableCollection<FileInfo>( fs );
    return data;
}

これを直して NewItemPlaceholder が表示されるようにするには、MVVM でいうところの ViewModel のようなクラス、つまり FileInfo をくるむラッパーを追加し、引数なしのコンストラクタを用意します。面倒です。やってられませんw

public class FileInfoViewModel : INotifyPropertyChanged
{
  private FileInfo fileInfo;

  // 引数なしのコンストラクタ
  public FileInfoViewModel() : this( @"C:\pagefile.sys" ) {}

  public FileInfoViewModel( string fileName )
  {
    this.fileInfo = new FileInfo( fileName );
  }

  public event PropertyChangedEventHandler PropertyChanged;

  protected virtual void OnPropertyChanged( string propertyName )
  {
    if ( this.PropertyChanged != null )
    {
      this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
    }
  }

  public string Name
  {
    get { return this.fileInfo.Name; }
    set
    {
      this.fileInfo = new FileInfo( value );
      OnPropertyChanged( "Name" );
      OnPropertyChanged( "Length" );
      OnPropertyChanged( "CreationTime" );
      OnPropertyChanged( "LastAccessTime" );
    }
  }

  public long Length { get { return this.fileInfo.Length; } }

  public DateTime CreationTime
  {
    get { return this.fileInfo.CreationTime; }
    set
    {
      this.fileInfo.CreationTime = value;
      OnPropertyChanged( "CreationTime" );
    }
  }

  public DateTime LastAccessTime { get { return this.fileInfo.LastAccessTime; } }
}

超超手抜きでもこんなに長い。だるい。MVVM って本気なの?M なの?

これを使って、先のダメな例を書き直す。

private ObservableCollection<FileInfoViewModel> CreateData()
{
  var fs = Directory.GetFiles( "." ).Select( s => new FileInfoViewModel( s ) );
  var data = new ObservableCollection<FileInfoViewModel>( fs );
  return data;
}

あとは this.DataContext = CreateData(); として DataGrid に渡すだけ。

<Window x:Class="DataGridTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:tk="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Window1" Height="300" Width="800">
    <Grid>
        <tk:DataGrid ItemsSource="{Binding}" />
    </Grid>
</Window>

これでユーザーが追加できるようになります。

WPF の DataGrid について