WindowsFormsアプリで、DataSet,DataTable, DataGridViewを使ったデータの管理

2023年2月14日

C#でデータの管理の方法にはいくつかありますが、今回はタイトルのような仕組みを使ったものを見ていきましょう

基本のデータ管理

Windows Formsアプリケーションで、DataSet、DataTable、DataGridViewを使ったデータの管理について説明します。これらのオブジェクトを使用することで、Windows Formsアプリケーションにおいてデータベースまたは他のデータソースからのデータを管理することができます。例えば、DataSetを使用してデータベースからデータを読み込んで、DataTableに格納し、その後DataGridViewを使用してそのデータを表示することができます。ユーザーがDataGridViewを使用してデータを編集すると、その変更がDataTableに反映されます。DataTableを使用して、この変更をデータベースに保存することもできます。

図書館システムのサンプルに例えると・・・

クラス構成図

オブジェクト構成図

books.AddRange(new Book[] { book1, book2, book3 });
で、まとめて追加もできるよ

WindowsFormsアプリでのDataSetとは

Windows Forms アプリケーションでの DataSet は、.NET Framework アプリケーションで使用される一連のテーブルからなるデータの集まりを表します。DataSet は複数のテーブルを含むことができ、各テーブルには行と列を持つことができます。DataSet は ADO.NET で使用され、データベースなどから読み込まれたデータを一時的に保持し、アプリケーション内で操作することができます。Windows Forms アプリケーションでは、DataSet のデータを DataGridView コントロールなどにバインドすることで、ユーザーにデータを表示することができます。

クラス構成図

オブジェクト構成図

情報の追加イメージ

フォームに登録

実行結果

チュートリアル

ソリューションの作成

BookManagerでソリューションを作成します(サンプルなので、自由に命名して構いません)
この時点でGitリポジトリを作成します
mainブランチから「自作版」というブランチを作成します

教科書を進めるときには、mainブランチから「教科書版」ブランチを作成して進めましょう

画面のデザイン

このイベントハンドラは、Windowsフォームアプリケーションにおいて、フォームの読み込み時に実行されるメソッドを定義しています。

コード

「private」はアクセス修飾子で、このメソッドはこのクラス内からのみアクセス可能であることを示しています。「void」はこのメソッドが戻り値を返さないことを示しています。

「Form1_Load」はメソッド名で、「Form1」はフォームの名前、「Load」はフォームの読み込み時に発生するイベントを表します。「sender」と「EventArgs e」はイベントハンドラのパラメータで、「sender」はイベントを発生させたオブジェクトを、「EventArgs e」はイベントに関連する情報を渡すために使用されます。

このメソッドは、フォームが読み込まれたときに実行されます。開発者はこのメソッド内で、フォームの読み込み時に実行する初期化処理や初期設定を行うことができます。

using System.Data;

namespace BookManager
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            DataSet dataSet = new DataSet();
            DataGridView dataGridView = new DataGridView();

            DataTable dataTable1 = new DataTable();
            // DataTableに列(タイトル行)を追加
            dataTable1.Columns.Add("書籍名", typeof(string));
            dataTable1.Columns.Add("ページ数", typeof(int));

            // DataTableに行(情報)を追加
            dataTable1.Rows.Add("吾輩は猫である", 100);
            dataTable1.Rows.Add("雪国", 200);
            dataTable1.Rows.Add("坊ちゃん", 250);

            // DataTableをDataSetに追加
            dataSet.Tables.Add(dataTable1);

            // DataSetに含まれるDataTableをDataGridViewのDataSourceに設定
            dataGridView.DataSource = dataSet.Tables[0];

            // DataGridViewをFormに追加
            dataGridView.Parent = this;
        }
    }
}

解説

コード全体の解説

Form1クラスは、Windows Formのフォームを表します。このクラスは、Formクラスを拡張しています。

Form1クラスのコンストラクタは、Formの初期化を行います。Form1_Loadメソッドは、Formがロードされた際に呼び出されます。このメソッドでは、以下の処理を行っています。

  1. DataSet オブジェクト dataSet を作成します。
  2. DataGridView オブジェクト dataGridView を作成します。
  3. DataTable オブジェクト dataTable1 を作成します。
  4. dataTable1 に「書籍名」と「ページ数」という列を追加します。
  5. dataTable1 に「吾輩は猫である」、「雪国」、「坊ちゃん」といった情報を表す行を追加します。
  6. dataSetdataTable1 を追加します。
  7. dataGridView のデータソースを dataSet の1番目のテーブル(dataSet.Tables[0])に設定します。
  8. dataGridView をFormに追加します。

これにより、DataGridViewに書籍の情報を表示することができます。

データ管理に使う3つのオブジェクト

各クラスのイメージ

このコードでは、DataSet、DataTable、DataGridViewの3つのオブジェクトを宣言しています。

DataSetDataSet は、複数の DataTable を含んだ複合データ構造を表します。DataSet は、DataTable のコレクションを保持しており、関連付けられたテーブル間のリレーションシップを定義することもできます。DataSet は、XML ドキュメントと連携することができ、データベースとのやりとりを行うために使用することもできます。
DataTableDataTable は、DataSet 内の個別のテーブルを表します。DataTable は、列名、型、制約などのスキーマ情報とともに、テーブル内の行を保持します。DataTable は、データベースから取得したデータを格納するために使用することができます。
DataGridViewDataGridView は、Windows Forms アプリケーションで使用するグリッド表示コントロールです。DataGridView は、DataTable 内のデータを表示することができます。ユーザーは DataGridView 内でデータを編集、追加、削除することもできます。DataGridView は、大量のデータを表示する場合に非常に便利です。

検索ができるようにする

特定の列をキーにして検索する

DataTableクラスのSelectメソッドの引数(filterExpression)SQLのWHERE句に似た構文を持っています。以下は、C#でのfilterExpressionの一般的な構文です。

[列名] [比較演算子] '[値]' [論理演算子] [列名] [比較演算子] '[値]' [論理演算子] ...

例えば、"Price"列が10未満である行のみを抽出するフィルタリング条件を表現するfilterExpressionは、次のようになります。

"Price < 10"

複数の条件を組み合わせる場合は、論理演算子(AND、OR)を使用して結合します。例えば、"Price"列が10未満で、"Category"列が"Fruit"である行を抽出するフィルタリング条件は、次のようになります。

"Price < 10 AND Category = 'Fruit'"

以下、書籍名で検索した場合の例になります

コード

// 検索キーを作成します
string searchKey = "雪国";

// DataTableから特定の列をキーにして検索するコード
DataRow searchRow = dataTable1.Select($"書籍名= '{searchKey}'").FirstOrDefault();

string result = $"書籍名: {searchRow["書籍名"]}, ページ数: {searchRow["ページ数"]}";

result変数の内容

書籍名: 雪国, ページ数: 200

削除ができるようにする

特定の列をキーにして削除する

コード

// 検索キーを作成します
string searchKey = "雪国";

// DataTableから特定の列をキーにして検索するコード
DataRow searchRow = dataTable1.Select($"書籍名= '{searchKey}'").FirstOrDefault();

dataTable1.Rows.Remove(searchRow);

削除ボタンを作る

コード

dataSet.Tablesコレクションの要素番号0は、dataSet.Tables[0]になります

private void DeleteRowButton_Click(object sender, EventArgs e)
{
    // 選択された行を削除する
    if (dataGridView.SelectedRows.Count > 0)
    {
        dataSet.Tables[0].Rows.RemoveAt(dataGridView.SelectedRows[0].Index);
    }
}

実行画面

データを保存

デザイン画面

DataSetをXML形式で保存

コード

private void SaveButton_Click(object sender, EventArgs e)
{
    // DataSetをXML形式で保存
    dataSet.WriteXml("DataSet.xml", XmlWriteMode.WriteSchema);
}

DataSetをXML形式で読み出し

コード

private void LoadButton_Click(object sender, EventArgs e)
{
    // 保存されたXML形式のDataSetを読み出す
    if (File.Exists("DataSet.xml"))
    {
        dataSet.Clear();
        dataSet.ReadXml("DataSet.xml");
    }
}

保存されたデータファイル(DataSet.xml)

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Table1">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="書籍名" type="xs:string" minOccurs="0" />
                <xs:element name="ページ数" type="xs:int" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table1>
    <書籍名>吾輩は猫である</書籍名>
    <ページ数>100</ページ数>
  </Table1>
  <Table1>
    <書籍名>坊ちゃん</書籍名>
    <ページ数>250</ページ数>
  </Table1>
</NewDataSet>

複数のテーブルを用意してみる

フィールドdataTable2を追加

using System.Data;

namespace BookManager
{
    public partial class Form1 : Form
    {
        DataSet dataSet;
        DataGridView dataGridView;
        DataTable dataTable1;
        DataTable dataTable2;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataSet = new DataSet();
            dataGridView = new DataGridView();

            dataTable1 = new DataTable();

            // DataTableに列(タイトル行)を追加
            dataTable1.Columns.Add("書籍名", typeof(string));
            dataTable1.Columns.Add("ページ数", typeof(int));

            // DataTableに行(情報)を追加
            dataTable1.Rows.Add("吾輩は猫である", 100);
            dataTable1.Rows.Add("雪国", 200);
            dataTable1.Rows.Add("坊ちゃん", 250);

            dataTable2 = new DataTable();

            // DataTableに列(タイトル行)を追加
            dataTable2.Columns.Add("ゲーム名", typeof(string));
            dataTable2.Columns.Add("メーカ", typeof(string));

            // DataTableに行(情報)を追加
            dataTable2.Rows.Add("ゼルダの伝説", "任天堂");
            dataTable2.Rows.Add("星のカービー", "任天堂");
            dataTable2.Rows.Add("オクトパストラベラーⅡ", "スクエニ");

            // DataTableをDataSetに追加
            dataSet.Tables.AddRange(new DataTable[] { dataTable1, dataTable2 });

            // DataSetに含まれるDataTableをDataGridViewのDataSourceに設定
            dataGridView.DataSource = dataSet.Tables[1];

            // DataGridViewをFormに追加
            dataGridView.Parent = this;
        }

        private void DeleteRowButton_Click(object sender, EventArgs e)
        {
            // 選択された行を削除する
            if (dataGridView.SelectedRows.Count > 0)
            {
                dataSet.Tables[1].Rows.RemoveAt(dataGridView.SelectedRows[0].Index);
            }
        }

        private void SaveButton_Click(object sender, EventArgs e)
        {
            // DataSetをXML形式で保存
            dataSet.WriteXml("DataSet.xml", XmlWriteMode.WriteSchema);
        }

        private void LoadButton_Click(object sender, EventArgs e)
        {
            // 保存されたXML形式のDataSetを読み出す
            if (File.Exists("DataSet.xml"))
            {
                dataSet.Clear();
                dataSet.ReadXml("DataSet.xml");
            }
        }
    }
}

保存されたデータファイル(DataSet.xml)

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Table1">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="書籍名" type="xs:string" minOccurs="0" />
                <xs:element name="ページ数" type="xs:int" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
          <xs:element name="Table2">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="ゲーム名" type="xs:string" minOccurs="0" />
                <xs:element name="メーカ" type="xs:string" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table1>
    <書籍名>吾輩は猫である</書籍名>
    <ページ数>100</ページ数>
  </Table1>
  <Table1>
    <書籍名>雪国</書籍名>
    <ページ数>200</ページ数>
  </Table1>
  <Table1>
    <書籍名>坊ちゃん</書籍名>
    <ページ数>250</ページ数>
  </Table1>
  <Table2>
    <ゲーム名>ゼルダの伝説</ゲーム名>
    <メーカ>任天堂</メーカ>
  </Table2>
  <Table2>
    <ゲーム名>星のカービー</ゲーム名>
    <メーカ>任天堂</メーカ>
  </Table2>
  <Table2>
    <ゲーム名>オクトパストラベラーⅡ</ゲーム名>
    <メーカ>スクエニ</メーカ>
  </Table2>
</NewDataSet>

DataTableをListで管理することに変更

using System.Data;

namespace BookManager
{
    public partial class Form1 : Form
    {
        DataSet dataSet;
        DataGridView dataGridView;
        List<DataTable> dataTables;

        public Form1()
        {
            InitializeComponent();
            dataTables = new List<DataTable>();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataSet = new DataSet("ネットショップデータセット");
            dataGridView = new DataGridView();

            DataTable dataTable = new DataTable();

            // DataTableに列(タイトル行)を追加
            dataTable.Columns.Add("書籍名", typeof(string));
            dataTable.Columns.Add("ページ数", typeof(int));

            // DataTableに行(情報)を追加
            dataTable.Rows.Add("吾輩は猫である", 100);
            dataTable.Rows.Add("雪国", 200);
            dataTable.Rows.Add("坊ちゃん", 250);

            // DataTableをDataSetに追加
            dataTables.Add(dataTable);

            dataTable = new DataTable();

            // DataTableに列(タイトル行)を追加
            dataTable.Columns.Add("ゲーム名", typeof(string));
            dataTable.Columns.Add("メーカ", typeof(string));

            // DataTableに行(情報)を追加
            dataTable.Rows.Add("ゼルダの伝説", "任天堂");
            dataTable.Rows.Add("星のカービー", "任天堂");
            dataTable.Rows.Add("オクトパストラベラーⅡ", "スクエニ");

            // DataTableをDataSetに追加
            dataTables.Add(dataTable);

            // List型のdataTablesを配列型に変更してdataSet.Tablesに追加する
            dataSet.Tables.AddRange(dataTables.ToArray());

            // DataSetに含まれるDataTableの要素番号1のデータをDataGridViewのDataSourceに登録
            dataGridView.DataSource = dataSet.Tables[1];

            // DataGridViewをform1(Form1クラスのインスタンス)に追加
            dataGridView.Parent = this;
        }

        private void DeleteRowButton_Click(object sender, EventArgs e)
        {
            // 選択された行を削除する
            if (dataGridView.SelectedRows.Count > 0)
            {
                dataSet.Tables[1].Rows.RemoveAt(dataGridView.SelectedRows[0].Index);
            }
        }

        private void SaveButton_Click(object sender, EventArgs e)
        {
            // DataSetをXML形式で保存
            dataSet.WriteXml("DataSet.xml", XmlWriteMode.WriteSchema);
        }

        private void LoadButton_Click(object sender, EventArgs e)
        {
            // 保存されたXML形式のDataSetを読み出す
            if (File.Exists("DataSet.xml"))
            {
                dataSet.Clear();
                dataSet.ReadXml("DataSet.xml");
            }
        }
    }
}

このコードは、Windowsフォームアプリケーションで、データを管理するためのクラス、DataSetとその配下にあるクラス、DataTableを使用しています。DataGridViewというWindowsフォームコントロールを使って、データを表形式で表示し、行の削除、保存、読み込みなどの操作を実行する機能を提供しています。具体的には、データセットに書籍情報とゲーム情報を追加し、DataGridViewにゲーム情報のみを表示しています。また、データをXML形式で保存・読み込みする機能も備えています。

保存されたデータファイル(DataSet.xml)

<?xml version="1.0" standalone="yes"?>
<ネットショップデータセット>
  <xs:schema id="ネットショップデータセット" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="ネットショップデータセット" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Table1">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="書籍名" type="xs:string" minOccurs="0" />
                <xs:element name="ページ数" type="xs:int" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
          <xs:element name="Table2">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="ゲーム名" type="xs:string" minOccurs="0" />
                <xs:element name="メーカ" type="xs:string" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table1>
    <書籍名>吾輩は猫である</書籍名>
    <ページ数>100</ページ数>
  </Table1>
  <Table1>
    <書籍名>雪国</書籍名>
    <ページ数>200</ページ数>
  </Table1>
  <Table1>
    <書籍名>坊ちゃん</書籍名>
    <ページ数>250</ページ数>
  </Table1>
  <Table2>
    <ゲーム名>ゼルダの伝説</ゲーム名>
    <メーカ>任天堂</メーカ>
  </Table2>
  <Table2>
    <ゲーム名>星のカービー</ゲーム名>
    <メーカ>任天堂</メーカ>
  </Table2>
  <Table2>
    <ゲーム名>オクトパストラベラーⅡ</ゲーム名>
    <メーカ>スクエニ</メーカ>
  </Table2>
</ネットショップデータセット>

参考

考えてみましょう

テーブルの切り替えのヒント

int SelectedTable
{
    get
    {
        return  change ? 1 : 0;
    }
}

bool change;

private void SelectedTableButton_Click(object sender, EventArgs e)
{
    change = !change;
    dataGridView.DataSource = dataSet.Tables[SelectedTable];
}

次のところは、省力可能ですね

int SelectedTable
{
    get
    {
        return  change ? 1 : 0;
    }
}

こんなようにもリファクタリングできます

int SelectedTable => change ? 1 : 0;

実行結果