Help us understand the problem. What is going on with this article?

Windowsフォームで型付きDataSetの代わりにPOCOを使ってみる

More than 1 year has passed since last update.

はじめに

WindowsフォームでDataGridView等を用いたデータベースアプリを構築する場合、
データの入れ物として型付きDataSetを使うのが定石かと思います。
しかしながら、型付きDataSetはデザイナの出来の悪さなどから、かなり嫌われているようです。
今回、型付きDataSetの代わりに通常のオブジェクト(POCO)をデータの入れ物として使ってみます。
サードパーティのライブラリとして、BindingListViewDapperを使います。

完成品はこちらに置いてあります
https://github.com/mono1729/BindingWithPoco

作るもの

  • SQL ServerのNorthwindサンプルデータベースのProductsテーブルの内容を表示するアプリを作ります。
  • 一覧の表示にはDataGridViewを使い、ヘッダクリックでソート可能にします。
  • BindingSourceを用いて、データの詳細形式表示も行います(図の下段)。一覧の選択行に同期して表示が変わります。
  • ProductNameでインクリメンタルサーチを行えるテキストボックスも配置します。
  • データ更新が画面に即反映されるか、テストするためのボタンも配置します。("Add row", "Modify first row") image.png

制作の流れ

今回のアプリは以下の流れで制作します。

  1. Productsテーブルの1レコードのデータを表すProductクラスを作る
  2. ProductクラスにはINotifyPropertyChangedを実装する
  3. Visual Studioのデータソース機能を用いて、画面デザインを行う
  4. DataTable/DataViewの代わりとなるコレクション、BindingList<Product>/BindingListView<Product>を定義する
  5. DapperでBindingList<Product>にデータを読み込み、それを元にBindingListView<Product>のインスタンスも生成する
  6. BindingListView<Product>BindingSourceDataSourceにする
  7. BindingSourceDataGridViewDataSourceにする
  8. インクリメンタルサーチとテスト用ボタンの機能を実装する

POCOを作る (Product)

Productsテーブルのデータの入れ物になるクラスを用意します。
データが更新された際、画面に通知しなければいけないので、INotifyPropertyChangedの実装が必要です。
フィールドが多いと大変なので、T4テキストテンプレートやFodyを使うといいかもしれません。

Product.cs
public class Product : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private int _ProductID;
    public int ProductID
    {
        get { return _ProductID; }
        set
        {
            _ProductID = value;
            OnPropertyChanged("ProductID");
        }
    }
    private string _ProductName;
    public string ProductName
    {
        get { return _ProductName; }
        set
        {
            _ProductName = value;
            OnPropertyChanged("ProductName");
        }
    }
/*以下略*/

データソース機能を使って画面デザイン

一度ビルドを行い、先ほど作成したクラスをVisual Studioのデータソースに登録すると、フォームデザイナにドラッグするだけで一覧と詳細を作成できます。
一緒にBindingSourceBindingNavigatorコンポーネントが配置されますが、BindingNavigatorは今回使わないので消しましょう。

image.png

バインド用のコレクションを準備

フォームのコードビハインドで、バインドするためのコレクションを準備します。
ここではデータ保持用のBindingList<Product>とバインド用のBindingListView<Product>、2つのメンバを準備します。

Form1.cs
using Equin.ApplicationFramework;
namespace BindingWithPoco
{
    public partial class Form1 : Form
    {
        private BindingList<Product> _products;
        private BindingListView<Product> _productsView;

データ読込・バインド

Micro-ORMのDapperを使ってProductsテーブルのデータを読み込み、BindingList<Product>に結果を入れます。
これを元データとして、BindingListView<Product>のオブジェクトを作ります(DataViewと同じ役割です)。
そしてBindingListView<Product>BindingSource.DataSourceにセットします。
DataGridView.DataSourceにはBindingSourceをセットして、間接的にBindingListView<Product>を参照させます。

Form1.cs
using Dapper;
public partial class Form1 : Form
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        LoadData();
    }

    private void LoadData()
    {
        var connStr = Properties.Settings.Default.db;
        var conn = new SqlConnection(connStr);
        var sql = "SELECT * FROM Products;";

        _products = new BindingList<Product>(conn.Query<Product>(sql).ToList());
        _productsView = new BindingListView<Product>(_products);
        bindingSource1.DataSource = _productsView;
        dataGridView1.DataSource = bindingSource1;
    }

フィルタ機能の実装

テキストボックスを配置して、TextChangedイベントにフィルタ機能を実装します。
BindingListViewDataTableと同じフィルタ式が使えません。
代わりに匿名メソッドでフィルタ条件を設定します。

Form1.cs
public partial class Form1 : Form
{
    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        var inputLower = ((TextBox)sender).Text.ToLower();
        _productsView.ApplyFilter(p => p.ProductName.ToLower().Contains(inputLower));
    }

動作検証用ボタンの実装

INotifyPropertyChangedBindingListを用いているので、画面表示後にデータをいじっても変更が画面に反映されるはずです。
"Add row"ボタンと、"Modify first row"ボタンを配置して、検証用コードを実装します。

Form1.cs
public partial class Form1 : Form
{
    private void button1_Click(object sender, EventArgs e)
    {
        _products.Add(new Product
        {
            ProductID = 999,
            ProductName = "Added!"
        });
    }

    private void button2_Click(object sender, EventArgs e)
    {
        _products[0].ProductName = "MODIFIED!";
    }

動作確認

ソート・フィルタも動きますし、データの変更も画面に即反映できているようです。
image.png

まとめ

今回はINotifyPropertyChanged, BindingList, BindingListViewを用いることで、型付きDataSetと同じような双方向データバインドを実現したWindowsフォームアプリを構築することができました。

色々工夫が必要でしたが、DataSet/DataTable以下の機能は実現できていません。

  • 入力値のバリデーション(DataRow.RowError)
  • 行の状態・変更の取消(DataRow.RowState,DataTable.AcceptChanges/DataTable.RejectChanges)
  • テーブル間のリレーションを使った機能(DataSet.Relations)

これらの機能を実装するのは大変そうなので、素直に型付きDataSetを使った方がやはり幸せかもしれません…。
Entity Frameworkを使えば実現できそうな気はしますが。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away