クラスのリストでDataGridViewを使いやすくする

2023年2月7日

DataGridViewを利用するとき、DataSourceにDBを直接繋いで処理するなんてことをやっていると思います。
この場合、トランザクションをしないと直接データベースが書き換わってしまいます。
またコネクションは貼りっぱなしになっています。

またデータベースから直接じゃなく、DataGridViewに1セルごとにデータを入れるというのも結構面倒です。

しかし、DataSourceにはDataTableやクラスのリストを入れたりもできるのでそれを利用して使いやすくします。

モデルクラスを作る

今回は、ユーザーモデルクラスを作りユーザー一覧をデータグリッドビューにセットしたいと思います。

参照するアセンブリです。

  • System.ComponentModel

ユーザークラスはとりあえず簡単にこんな感じにしました。

using System.ComponentModel;

namespace DataGridViewSample
{
    public class UserClass
    {
        [DisplayName("ID")]
        public string UserID { set; get; } = "";

        [DisplayName("姓")]
        public string UserFirstName { set; get; } = "";

        [DisplayName("名")]
        public string UserLastName { set; get; } = "";

        [DisplayName("年齢")]
        public int UserAge { set; get; } = -1;

        [DisplayName("住所")]
        public string UserAddress { set; get; } = "";

        [DisplayName("TEL")]
        public string UserTel { set; get; } = "";

        public UserClass(string id = "", string first_name = "", string last_name = "", int age = -1, string address = "", string tel = "")
        {
            this.UserID = id;                   //ID
            this.UserFirstName = first_name;    //姓
            this.UserLastName = last_name;      //名
            this.UserAge = age;                 //年齢
            this.UserAddress = address;         //住所
            this.UserTel = tel;                 //電話番号
        }
    }
}

DataGridViewのDataSourceに入れてみる

こんな画面を作りました。中央の□がDataGridViewです。

検索ボタンクリックしたらデータ検索をして結果をDataGridViewに入れることを想定して以下のようにしました。

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace DataGridViewSample
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }
        private void btnClose_Click(object sender, EventArgs e)
        {
            //閉じる
            this.Close();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //初期化
            Initialize();
        }

        private void btnSeach_Click(object sender, EventArgs e)
        {
            //データ検索
            DataLoad();
        }

        private void Initialize()
        {
            //初期化処理
            this.dataGridView1.DataSource = null;
        }

        private void DataLoad()
        {
            //データを取得する処理
            //ユーザークラスのリスト
            List<UserClass> user_list = new List<UserClass>();

            //取得したデータをセットする(仮のデータを設定)
            user_list.Add(new UserClass(id: "0001", first_name: "山本", last_name: "進", age: 20, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0002", first_name: "中山", last_name: "健太", age: 35, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0003", first_name: "木村", last_name: "篤史", age: 41, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0004", first_name: "池田", last_name: "英明", age: 15, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0005", first_name: "田村", last_name: "悟", age: 67, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0006", first_name: "高野", last_name: "修", age: 54, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0007", first_name: "橋本", last_name: "直哉", age: 25, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0008", first_name: "白井", last_name: "聡", age: 36, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0009", first_name: "大西", last_name: "賢一", age: 16, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0010", first_name: "中山", last_name: "圭太", age: 4, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0011", first_name: "水野", last_name: "巧", age: 38, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0012", first_name: "原", last_name: "正弘", age: 16, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0013", first_name: "石川", last_name: "幸二", age: 95, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0014", first_name: "中井", last_name: "裕太", age: 76, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0015", first_name: "福島", last_name: "俊明", age: 28, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0016", first_name: "上杉", last_name: "潤", age: 21, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0017", first_name: "秋田", last_name: "篤", age: 60, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0018", first_name: "土井", last_name: "宏樹", age: 24, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0019", first_name: "樋口", last_name: "拓海", age: 35, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0020", first_name: "中井", last_name: "孝志", age: 45, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0021", first_name: "清原", last_name: "博", age: 41, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0022", first_name: "野村", last_name: "雅則", age: 26, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0023", first_name: "藤原", last_name: "将弘", age: 23, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0024", first_name: "堀口", last_name: "勝也", age: 14, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0025", first_name: "田村", last_name: "恭弘", age: 36, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0026", first_name: "飯田", last_name: "大志", age: 27, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0027", first_name: "下田", last_name: "明宏", age: 58, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0028", first_name: "玉木", last_name: "康之", age: 49, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0029", first_name: "河村", last_name: "康志", age: 47, address: "〇〇県××市000-00", tel: "000-000-0000"));
            user_list.Add(new UserClass(id: "0030", first_name: "溝上", last_name: "直人", age: 51, address: "〇〇県××市000-00", tel: "000-000-0000"));

            //データソースにセットする
            this.dataGridView1.DataSource = user_list;

            //例えばIDは出したくないので非表示にする
            this.dataGridView1.Columns[nameof(UserClass.UserID)].Visible = false;

            //幅を自動整列
            this.dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
        }
    }
}

検索ボタンをクリックして上記のイベントを実行してみます。すると以下のような画面が出てきました。DisplayNameがHeaderTextに入っています。Column.Nameはプロパティ名になっています。

これのどこが便利なのか?

クラスをリスト化して表示することの利点は、何か上げていきます。

型(Type)がプロパティに準拠して設定される

例えばこの場合、年齢はintで宣言してあります。強制的に数値しか入力できなくなります。数値以外を入力するとエラーダイアログが表示されます。

DataErrorイベントをハンドルしてください。」とあるので以下のイベント関数をDataGridViewのDataErrorに紐づけを行いました。

private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
    if (e.Exception.GetType() == typeof(FormatException))
    {
        if (e.ColumnIndex == this.dataGridView1.Columns[nameof(UserClass.UserAge)].Index)
        {
            MessageBox.Show("年齢は数値のみ入力可能です。");
            e.Cancel = true;
        }
    }
}

行にバインドされてるクラスオブジェクトを取得できる

各行にバインドされているオブジェクトは、DataBoundItemプロパティを利用することにより以下のように取得できます。

private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
    //クラスを取り出す
    UserClass usr = (UserClass)this.dataGridView1.Rows[e.RowIndex].DataBoundItem;
    
    ・・・
}

クラスはDataGridView以外にも使いまわしができる

クラスなので別にDataGridViewにこだわる必要はなく、ほかでも使えます。

なので、例えばユーザークラスとしてエラーチェック処理や更新処理、削除処理を入れてしまえば、ユーザーを管理するクラスとしても使えます。

プログラミングするときに、ユーザーに関しては必ずユーザークラスを通すようにルールを作っておけばユーザーに関する処理をユーザークラスに集中させることができます。

こうすることで管理が楽になります。

バインドリストで行追加をなどを簡単にできるようになる

DataGridViewのためのリストではなく、プログラム全体で利用するためにモジュール単位で宣言しているクラスリストの場合、BindingList<>を利用することでDataSourceにセットしたあとでもBindingListにアイテムを追加するだけでDataGridViewにも反映されます。

使用してみた感じ、あんまりリアルタイムではありませんがDataGireViewのRefresh()することで即時反映されるようです。

サンプルです。

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace DataGridViewSample
{
    public partial class Form1 : Form
    {
        //BindingListの宣言
        System.ComponentModel.BindingList<UserClass> _user_bind_list = new System.ComponentModel.BindingList<UserClass>();

        public Form1()
        {
            InitializeComponent();
        }

        //フォームロード
        private void Form1_Load(object sender, EventArgs e)
        {
            //初期化
            Initialize();
        }

        //閉じるボタン押下
        private void btnClose_Click(object sender, EventArgs e)
        {
            //閉じる
            this.Close();
        }

        //検索ボタン押下
        private void btnSeach_BindingList_Click(object sender, EventArgs e)
        {
            //データ読み込み
            DataLoad2();
        }

        //行追加ボタン押下
        private void btnRowAdd_Click(object sender, EventArgs e)
        {
            //リストにユーザークラスオブジェクトを追加する
            _user_bind_list.Add(new UserClass());
        }

        //初期化処理
        private void Initialize()
        {
            //初期化処理
            this.dataGridView1.DataSource = null;
        }


        //データ読み込み
        private void DataLoad2()
        {
            _user_bind_list.Add(new UserClass(id: "0001", first_name: "山本", last_name: "進", age: 20, address: "〇〇県××市000-00", tel: "000-000-0000"));
            _user_bind_list.Add(new UserClass(id: "0002", first_name: "中山", last_name: "健太", age: 35, address: "〇〇県××市000-00", tel: "000-000-0000"));
            _user_bind_list.Add(new UserClass(id: "0003", first_name: "木村", last_name: "篤史", age: 41, address: "〇〇県××市000-00", tel: "000-000-0000"));

            //データソースにセットする
            this.dataGridView1.DataSource = _user_bind_list;

            //例えばIDは出したくないので非表示にする
            this.dataGridView1.Columns[nameof(UserClass.UserID)].Visible = false;

            //幅を自動整列
            this.dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
        }

    }
}

まとめ

プログラミングは、基本的にアイデア勝負なところがあります。

アイデアをひねり出すには知識だけでは足りなく、経験からの知恵も必要になってきます。
技術と呼ばれるものは全部が経験からくるものになります。