2011年1月10日月曜日

WPFのDataGridにDataTableを表示

0 コメント
WPFのDataGridコントロールにDataTableの内容を表示してみました。

テーブルの構成は下図のような感じ。

この中の商品マスタをDataGridで表示します。

まず、VisualStudio 2010で、[新規作成]→[プロジェクト]→[WPFアプリケーション]を選択し、WPFDataGridSampleというプロジェクトを作成します。

MainWindowsというフォームが作成されるので、ここにDataGridコントロールを貼り付けて、XAMLのソースを次のように編集して、カラムを追加します。
<Window x:Class="WPFDataGridSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="NumericStyle" TargetType="TextBlock">
            <Setter Property="TextAlignment" Value="Right" />
        </Style>
    </Window.Resources>
    <Grid>
        <DataGrid Name="dataGrid1" AutoGenerateColumns="False"
                  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding}">
            <DataGrid.Columns>
                <DataGridTextColumn
                    x:Name="col_item_id"
                    Header="ID"
                    Binding="{Binding item_id}"
                    ElementStyle="{StaticResource NumericStyle}"
                    IsReadOnly="True"
                />
                <DataGridTextColumn
                    x:Name="col_item_name"
                    Header="商品名"
                    Binding="{Binding item_name}"
                />
                <DataGridComboBoxColumn
                    x:Name="col_category_id"
                    Header="カテゴリ"
                    SelectedValueBinding="{Binding category_id}"
                    SelectedValuePath="category_id"
                    DisplayMemberPath="category_name"
                />
                <DataGridCheckBoxColumn
                    x:Name="col_status"
                    Header="ステータス"
                    Binding="{Binding status}"
                />
                <DataGridTextColumn
                    x:Name="col_create_date"
                    Header="作成日時"
                    Binding="{Binding create_date, StringFormat=yyyy/MM/dd HH:mm:ss}"
                    IsReadOnly="True"
                />
                <DataGridTextColumn
                    x:Name="col_update_date"
                    Header="更新日時"
                    Binding="{Binding update_date, StringFormat=yyyy/MM/dd HH:mm:ss}"
                    IsReadOnly="True"
                />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
そうすると、次のようなフォームができます。

ポイントは、DataGrid.Columnsの中で各カラムを設定するときに、
  • Header="xxxx"で、カラムのタイトルを設定します。例えば Header="商品名"
  • Binding="{Binding xxxx}"で、DataTableの列名を指定します。例えば Binding="{Binding item_name}"
とします。

次に、MainWindows.xaml.csに次のコードを記述します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Data;

namespace WPFDataGridSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        //-------------------------------------------------------------------- メンバ変数
        private DataTable m_table_mst_category;    // カテゴリマスタテーブル
        private DataTable m_table_mst_item;        // 商品マスタテーブル

        //-------------------------------------------------------------------- コンストラクタ
        public MainWindow()
        {
            InitializeComponent();

            // テーブルの初期化
            try
            {
                InitTables();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Title, MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            // グリッドにバインド
            dataGrid1.DataContext = m_table_mst_item;
            col_category_id.ItemsSource = m_table_mst_category.DefaultView;    // カテゴリ列
        }

        //-------------------------------------------------------------------- メンバ関数
        /// <summary>
        /// テーブルの初期化
        /// </summary>
        private void InitTables()
        {
            // カテゴリマスタテーブルの生成
            m_table_mst_category = new DataTable("mst_category");
            m_table_mst_category.Columns.Add(new DataColumn("category_id"  , typeof(int)     ));    // カテゴリID
            m_table_mst_category.Columns.Add(new DataColumn("category_name", typeof(string)  ));    // カテゴリ名
            m_table_mst_category.Columns.Add(new DataColumn("create_date"  , typeof(DateTime)));    // 作成日時
            m_table_mst_category.Columns.Add(new DataColumn("update_date"  , typeof(DateTime)));    // 更新日時
            m_table_mst_category.PrimaryKey = new DataColumn[] { m_table_mst_category.Columns["category_id"] };    // プライマリキーの設定
            m_table_mst_category.Columns["category_name"].Unique = true;                                           // ユニークキーの設定
            m_table_mst_category.Columns["category_id"].AutoIncrement     = true;                                  // 自動採番の設定
            m_table_mst_category.Columns["category_id"].AutoIncrementSeed = 1;                                     // 自動採番は1から始まり
            m_table_mst_category.Columns["category_id"].AutoIncrementStep = 1;                                     // 自動採番は1ずつ増加
            m_table_mst_category.TableNewRow += new DataTableNewRowEventHandler(m_table_mst_category_TableNewRow); // TableNewRowイベントの追加
            // サンプルデータ追加
            foreach (var category_name in new string[] { "液晶テレビ", "ブルーレイレコーダ" })
            {
                DataRow newRow = m_table_mst_category.NewRow();
                newRow["category_name"] = category_name;
                m_table_mst_category.Rows.Add(newRow);
            }

            // 商品マスタテーブルの生成
            m_table_mst_item = new DataTable("mst_item");
            m_table_mst_item.Columns.Add(new DataColumn("item_id"    , typeof(int)     ));          // 商品ID
            m_table_mst_item.Columns.Add(new DataColumn("item_name"  , typeof(string)  ));          // 商品名
            m_table_mst_item.Columns.Add(new DataColumn("category_id", typeof(int)     ));          // カテゴリID
            m_table_mst_item.Columns.Add(new DataColumn("status"     , typeof(bool)    ));          // ステータス
            m_table_mst_item.Columns.Add(new DataColumn("create_date", typeof(DateTime)));          // 作成日時
            m_table_mst_item.Columns.Add(new DataColumn("update_date", typeof(DateTime)));          // 更新日時
            m_table_mst_item.PrimaryKey = new DataColumn[] { m_table_mst_item.Columns["item_id"] };                // プライマリキーの設定
            m_table_mst_item.Columns["item_name"].Unique = true;                                                   // ユニークキーの設定
            m_table_mst_item.Columns["item_id"].AutoIncrement     = true;                                          // 自動採番の設定
            m_table_mst_item.Columns["item_id"].AutoIncrementSeed = 1;                                             // 自動採番は1から始まり
            m_table_mst_item.Columns["item_id"].AutoIncrementStep = 1;                                             // 自動採番は1ずつ増加
            m_table_mst_item.TableNewRow += new DataTableNewRowEventHandler(m_table_mst_item_TableNewRow);         // TableNewRowイベントの追加
            // サンプルデータ追加
            DataRow newRowItem;
            newRowItem = m_table_mst_item.NewRow();
            newRowItem["item_name"] = "LED REGZA ZG1";
            newRowItem["category_id"] = m_table_mst_category.Select("category_name='液晶テレビ'")[0]["category_id"];
            m_table_mst_item.Rows.Add(newRowItem);
            newRowItem = m_table_mst_item.NewRow();
            newRowItem["item_name"] = "RD-X10";
            newRowItem["category_id"] = m_table_mst_category.Select("category_name='ブルーレイレコーダ'")[0]["category_id"];
            m_table_mst_item.Rows.Add(newRowItem);
        }

        //-------------------------------------------------------------------- イベント関数
        // カテゴリマスタのTableNewRowイベント
        private void m_table_mst_category_TableNewRow(object sender, DataTableNewRowEventArgs e)
        {
            e.Row["create_date"] = DateTime.Now;
            e.Row["update_date"] = DateTime.Now;
        }

        // 商品マスタのTableNewRowイベント
        private void m_table_mst_item_TableNewRow(object sender, DataTableNewRowEventArgs e)
        {
            e.Row["status"     ] = true;
            e.Row["create_date"] = DateTime.Now;
            e.Row["update_date"] = DateTime.Now;
        }

    }
}
メンバ変数に、m_table_mst_categoryとm_table_mst_itemというDataTableを用意して、InitTables関数で各テーブルを生成しサンプルデータを作成しています。

ポイントになるのは、コンストラクタの
            // グリッドにバインド
            dataGrid1.DataContext = m_table_mst_item;
            col_category_id.ItemsSource = m_table_mst_category.DefaultView;    // カテゴリ列
の部分で、
            dataGrid1.DataContext = m_table_mst_item;
で、DataGridに商品マスタのDataTableをバインドしています。
また、DataGridの列の中で、カテゴリ列はコンボボックスで選択できるようにするため、
            col_category_id.ItemsSource = m_table_mst_category.DefaultView;
で、col_category_idにバインドしています。さらにMainWindow.xamlの中でカテゴリ列は
                <DataGridComboBoxColumn
                    x:Name="col_category_id"
                    Header="カテゴリ"
                    SelectedValueBinding="{Binding category_id}"
                    SelectedValuePath="category_id"
                    DisplayMemberPath="category_name"
                />
としているため、カテゴリマスタテーブル(m_table_mst_category)のcategory_idが選択値、category_nameが表示値となります。
※商品マスタのcategory_idとのバインディングは、Binding="{Binding category_id}"ではなく、SelectedValueBinding="{Binding category_id}"で行います。

ここで注意する点として、col_category_id.ItemsSourceを、
            col_category_id.ItemsSource = m_table_mst_category.Rows;
とすると、カテゴリ列が空白になって正しく表示されません。ただ入力するときのコンボボックスのドロップダウンする行数はカテゴリマスタの件数と一致しているので、カテゴリ列とカテゴリマスタのDataTableとのバインドはうまくいっていると思いますが、表示がうまくいかないようです(DataGridのバグなのか制約なのかよくわかりません)。

実行すると、次のような画面が表示されます。



個人的な感想として、BindingSourceやBindingNavigationなどが使えないため、Windowsフォームのように簡単には作れない感じです。
あと、データとDataGridのバインディングの方法がわかりにくい(今もまだしっかりわかっていない気がします)です。