オルタナティブ・ブログ > IT's my business >

IT業界のコメントマニアが始めるブログ。いつまで続くのか?

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

※コードの簡略化のため、コマンドは使っていません。

Xaml17

Comment(0)