2012/08/30

DataGridView など

はじめに

C# と PostgreSQL であれこれとやっています。DataGridView が便利そうなのですが、なかなか使いこなせません。で、いままでは ListView なんかを使って誤魔化していたのですが、やっぱりちゃんと DataGridView 使いたいよねということで、気合を入れて頑張ってみました。

住所録アプリです。


メイン画面はこんな感じ。DataGridView がどーんと表示されています。

「行を追加」ボタンで、カーソルがある行の下に空白行が表示されます。
「行を削除」で、カーソルがある行が削除されます。
「保存」で、画面上のデータがデータベースに反映されます。

PostgreSQL との接続には Npgsql を使用しています。ADO 経由の接続もあるのですが、ドライバを別途インストールしないといけないので Npgsql にしました。

 プログラムの起動

起動時にデータベースに接続にいって、該当テーブルを DataGridView に紐付けします。

接続はこんな感じ。
接続に必要な情報は app.config に入れています。


        public static NpgsqlConnection conn;
        public static bool connect_db()
        {
            Properties.Settings.Default.Reload();
            string connection_string = "Server=" + Properties.Settings.Default.Server + ";"
                + "Port=" + Properties.Settings.Default.Port + ";"
                + "User Id=" + Properties.Settings.Default.User_Id + ";"
                + "Password=" + Properties.Settings.Default.Password + ";"
                + "Database=" + Properties.Settings.Default.Database + ";";
            conn = new NpgsqlConnection(connection_string);
            try
            {
                conn.Open();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("DataBase Open Error:" + ex.Message);
                return false;
            }
        }

Form の OnLoad のタイミングでデータを取ってきます。

            Database.GetData();
            dataGridView1.DataSource = Database.dataSet;
            dataGridView1.DataMember = "address";

Database.GetData() は、こんな感じ。
ちょっと、ゴチャゴチャしてますね。

        public static void GetData()
        {
            dataSet = new System.Data.DataSet();
            dataAdapter = new NpgsqlDataAdapter();
            string sql = "select * from address";

            dataAdapter.SelectCommand = new NpgsqlCommand(sql, Database.conn);

            sql = "insert into address (name, birthday, zipcode, addr, tel) values (:name, :birthday, :zipcode, :addr, :tel)";
            dataAdapter.InsertCommand = new NpgsqlCommand(sql, Database.conn);
            dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("name", NpgsqlTypes.NpgsqlDbType.Text, 0, "name", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("birthday", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "birthday", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("zipcode", NpgsqlTypes.NpgsqlDbType.Text, 0, "zipcode", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("addr", NpgsqlTypes.NpgsqlDbType.Text, 0, "addr", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("tel", NpgsqlTypes.NpgsqlDbType.Text, 0, "tel", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));

            sql = "update address set name = :name, birthday = :birthday, zipcode = :zipcode, addr = :addr, tel = :tel where id = :id";
            dataAdapter.UpdateCommand = new NpgsqlCommand(sql, Database.conn);
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("name", NpgsqlTypes.NpgsqlDbType.Text, 0, "name", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("birthday", NpgsqlTypes.NpgsqlDbType.Timestamp, 0, "birthday", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("zipcode", NpgsqlTypes.NpgsqlDbType.Text, 0, "zipcode", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("addr", NpgsqlTypes.NpgsqlDbType.Text, 0, "addr", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("tel", NpgsqlTypes.NpgsqlDbType.Text, 0, "tel", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Current, DBNull.Value));
            dataAdapter.UpdateCommand.Parameters.Add(new NpgsqlParameter("id", NpgsqlTypes.NpgsqlDbType.Integer, 0, "id", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Original, DBNull.Value));

            sql = "delete from address where id = :id";
            dataAdapter.DeleteCommand = new NpgsqlCommand(sql, Database.conn);
            dataAdapter.DeleteCommand.Parameters.Add(new NpgsqlParameter("id", NpgsqlTypes.NpgsqlDbType.Integer, 0, "id", System.Data.ParameterDirection.Input, false, 0, 0, System.Data.DataRowVersion.Original, DBNull.Value));

            dataAdapter.Fill(dataSet, "address");
        }

これで表示はできますが、項目見出しが英語のままです。データベースには comment で日本語の情報を登録していますのでこれを使って項目見出しを日本語にします。

            Utils.SetHeaderString(dataGridView1, Database.GetComment("address"));

Database.GetComment() が、コメントを取ってくる部分。
Utils.SetHeaderString() が、コメントを DataGridView の HeaderText に当てはめる部分です。

        public static Hashtable GetComment(string table_name)
        {
            Hashtable ht = new Hashtable();
            NpgsqlCommand command = new NpgsqlCommand("select "
                + "  attr.attname as column_name, "
                + "  des2.description as column_comment "
                + "from "
                + "  pg_catalog.pg_class a "
                + "    inner join pg_catalog.pg_namespace ns on a.relnamespace = ns.oid "
                + "    inner join pg_catalog.pg_attribute attr on a.oid = attr.attrelid and attr.attnum > 0 "
                + "    left join pg_catalog.pg_description des2 on a.oid = des2.objoid and attr.attnum = des2.objsubid "
                + "where "
                + "  a.relkind IN ('r', 'v') "
                + "  and a.relname = :table_name "
                + "  and ns.nspname IN ('public') "
                , conn);
            command.Parameters.Add(new NpgsqlParameter("table_name", NpgsqlTypes.NpgsqlDbType.Text));
            command.Parameters["table_name"].Value = table_name;

            NpgsqlDataReader dr = command.ExecuteReader();
            while (dr.Read())
            {
                ht.Add(dr["column_name"], dr["column_comment"]);
            }

            return ht;
        }


        public static void SetHeaderString(System.Windows.Forms.DataGridView grid, System.Collections.Hashtable hash)
        {
            for (int ii = 0; ii < grid.Columns.Count; ii++)
            {
                if (grid.Columns[ii].HeaderText == "id")
                {
                    grid.Columns[ii].Visible = false;
                }
                if (hash[grid.Columns[ii].HeaderText] != null)
                {
                    grid.Columns[ii].HeaderText = hash[grid.Columns[ii].HeaderText].ToString();
                }
            }
        }


行を追加

このボタンをクリックすると、カーソルがある行の下に空白行を挿入します。最終行が選択されている場合、データがない場合は、行が追加されます。

        private void buttonAdd_Click(object sender, EventArgs e)
        {
            int insert_pos = 0;

            if (dataGridView1.Rows.Count > 0)
            {
                if (dataGridView1.CurrentRow == null)
                {
                    MessageBox.Show("一覧から選択して下さい");
                    return;
                }
                insert_pos = dataGridView1.CurrentRow.Index + 1;
            }

            //  挿入する行の生成
            DataRow newRow = Database.dataSet.Tables[0].NewRow();

            //  選択行の下に追加
            Database.dataSet.Tables[0].Rows.InsertAt(newRow, insert_pos);
        }

行を削除

このボタンをクリックすると、カーソルがある行が削除されます。DataGridView からです。まだデータベースからは削除されません。

        private void buttonDel_Click(object sender, EventArgs e)
        {
            if (dataGridView1.CurrentRow == null)
            {
                MessageBox.Show("一覧から選択して下さい");
                return;
            }
            DataRow myRow = Database.dataSet.Tables[0].Rows[dataGridView1.CurrentRow.Index];
            myRow.Delete();
        }

保存

DagaGridView の内容がデータベースに反映されます。

        private void buttonSave_Click(object sender, EventArgs e)
        {
            try
            {
                Database.dataAdapter.Update(Database.dataSet, "address");
                MessageBox.Show("保存しました");
            }
            catch (Exception ex)
            {
                MessageBox.Show("失敗しました。" + ex.Message);
            }
        }

まとめ

今回苦労したところは、DagaGridView への操作と、実際のデータとの連携の部分でした。DataSet を無視して DataGridView への追加、削除は簡単に出来たのですが、いざそれを保存しようというところではまってしまいました。

あと、このプログラムで連続して追加や削除を行なって「保存」しようとするとエラーが出る場合があります。DataSet 内の Status の関係だと思うのですが追求してません ^^;)

ソース一式をあげておきます。 -> ここ