(XAML#17)「DataGrid」
DataGrid は、データをグリッド形式で表示/編集するために WPF 4.0 で導入された比較的新しいコントロールです。もともと Windows フォームには、DataGridView というコントロールがありましたが、初期の WPF には対応するコントロールがなく、WPF Toolkit で提供されていたものを使ったり、サードパーティ製のコントロールに頼らざるをえませんでした。WPF の DataGrid は、データを扱うための機能が“WPF らしく”実装されており、使いこなせばさまざまな用途に対応できるものとなっています。しばらく、DataGrid に関するトピックを取り上げていきます。
DataGrid を使うために、単純なビューモデルを作成します(本来、Entity Framework などを使って、データベースを関連付けたいところですが、WPF 以外の説明が長くなるため見送ります)。ここでは、ID、Name、BirthDate という3つのプロパティを持つ PersonInfo クラスを作成し、そのコレクション(ObservableCollection)をビューモデルの要素としています。ObservableCollection は編集可能なデータの集合として使うもので、表示用途であれば List でもかまいません。
[コード]
public class SampleViewModel
{
public ObservableCollection<PersonInfo> Persons { get; private set; }
public SampleViewModel()
{
Persons = new ObservableCollection<PersonInfo>()
{
new PersonInfo() { ID = 1, Name = "福澤諭吉", BirthDate = new DateTime(1835, 1, 10) },
new PersonInfo() { ID = 2, Name = "樋口一葉", BirthDate = new DateTime(1872, 5, 2) },
new PersonInfo() { ID = 3, Name = "野口英世", BirthDate = new DateTime(1928, 5, 21) },
};
}
}
public enum gender_t
{
Unknown, Male, Female,
}
public class PersonInfo : INotifyPropertyChanged
{
private int _id;
public int ID {
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
private DateTime _birthdate;
public DateTime BirthDate
{
get { return _birthdate; }
set
{
_birthdate = value;
OnPropertyChanged("BirthDate");
}
}
private gender_t _gender;
public gender_t Gender
{
get { return _gender; }
set
{
_gender = value;
OnPropertyChanged("Gender");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
上記のコレクションをウィンドウのデータコンテキストに割り当てるため、ウィンドウクラスのコンストラクタで次のように記述します。
[コード]
public partial class MainWindow : Window
{
private SampleViewModel vm = new SampleViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
}
こうしておくと、XAML 側では、次のように Persons プロパティにバインディングするだけで、データを表示/編集できるようになります。
[XAML]
<Grid>
<DataGrid ItemsSource="{Binding Persons}" />
</Grid>
ただし、それぞれのカラム(列)は、デフォルトの書式で表示されるため、たとえば BirthDate の時間情報も表示されてしまいます。また、ID列を編集させたくない(あるいは表示させたくない)ということもあります。このため、たいていの場合は、マニュアルでカラム情報(DataGrid.Columnsプロパティ)をカスタマイズします。
次の例は、それぞれのカラムを DatGridTextColumn で置き換えた例です。ただし、Gender 項目に対応するカラムは、ここでは除外しています(後述)。
[XAML]
<Grid>
<DataGrid ItemsSource="{Binding Persons}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="BirthDate" Binding="{Binding BirthDate, StringFormat=yyyy/MM/dd}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
ここで AutoGenerateColumns を False にすることでカラム情報を自動生成しなくなり、DataGrid.Columns プロパティにカラム情報のコレクションを設定します(AutoGenerateColumns を False にしないと、自動作成したカラム情報も追加されて、重複してしまいます)。
さきほど除外した Gender 項目を追加してみます。Gender 目は、この名前空間で定義した gender_t 型の項目で、AutoGenerateColumns を使って自動生成するときは、自動的に ComboBox にバインドされていました。同じように、自分で情報を追加するためには、DataGridComboBoxColumn を使います。選択する項目は(Bindingではなく)SelectedValueBinding にバインディングし、選択肢(ItemsSource)を定義しておく必要があります。
ItemsSource に割り当てる列挙型の要素は ObjectDataProvider を使って次のようにリソースとして定義し、ItemsSource に割り当てることができます。
[XAML]
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="325" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="GenderEnum"
MethodName="GetValues"
ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="local:gender_t" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding Persons}"
AutoGenerateColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="BirthDate" Binding="{Binding BirthDate,
Converter={x:Static local:DateFormatConverter.Converter}}"/>
<DataGridComboBoxColumn Header="Gender" SelectedValueBinding="{Binding Gender}"
ItemsSource="{Binding Source={StaticResource GenderEnum}}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Show Data" Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
上記の ObjectDataProvider はプログラムで次のようなコードを書くのと同じで、この結果を GenderEnum というキーで参照できるようになっています。
[コード]
Enum.GetValues(typeof(gender_t));
念のためにデータの編集結果を確認できるよう、少し拡張しておきます。
[XAML]
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding Persons}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="BirthDate" Binding="{Binding BirthDate, StringFormat=yyyy/MM/dd}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Show Data" Click="Button_Click" />
</StackPanel>
</Grid>
[コード]
public partial class MainWindow : Window
{
private SampleViewModel vm = new SampleViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var sb = new StringBuilder();
foreach (var info in vm.Persons)
sb.AppendFormat("{0:D4}: {1,-8} ... {2:yyyy/MM/dd}({3})\n", info.ID, info.Name, info.BirthDate, info.Gender);
MessageBox.Show(sb.ToString());
}
}
※コードの簡略化のため、コマンドは使っていません。