C# Windows Forms と JSON で実装する BookManager アプリケーション技術資料

2025年2月5日

この資料では、書籍情報を管理する BookManager アプリケーションのコードについて、初めての方でも理解しやすいように解説します。本アプリケーションでは、書籍の書名、著者、値段を扱い、JSON ファイルにデータを保存・読み込みする機能を持ちます。また、Windows Forms を利用してグラフィカルなユーザーインターフェース(GUI)を作成しています。

必要な名前空間のインポート

プログラムで必要となる名前空間をインポートします。以下の using 文により、ファイル操作や JSON の変換などが利用可能になります。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;  // ファイル操作に必要
using System.Linq;
using Newtonsoft.Json;  // JSONシリアライズ/デシリアライズに必要
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

System.IO: ファイルの読み書き操作に必要です。

Newtonsoft.Json: JSON形式でデータをシリアライズ(オブジェクトからJSON文字列に変換)およびデシリアライズ(JSON文字列からオブジェクトに変換)するために使用します。

クラスの定義と各メソッドの役割

Form1 クラス

このクラスは、アプリケーションのメイン画面(フォーム)を表します。主な処理は次の通りです。

namespace BookManager
{
    public partial class Form1 : Form
    {
        // JSONファイルのパス(相対パス)を定義
        private const string FileName = @"..\..\books.json";

        // コンストラクタ(フォーム初期化時に呼ばれる)
        public Form1()
        {
            InitializeComponent();
            // フォーム表示時に保存データを読み込む
            LoadData();
        }

        // 「追加」ボタンがクリックされたときの処理
        private void AddButtonClicked(object sender, EventArgs e)
        {
            // 重複チェック:すでに同じ書名の本があるか確認
            foreach (DataRow row in bookDataSet.bookDataTable.Rows)
            {
                if (row["書名"].ToString() == this.bookName.Text)
                {
                    MessageBox.Show("同じ書名の本が既に存在します。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
            }

            // テキストボックスの内容を DataTable に追加
            bookDataSet.bookDataTable.AddbookDataTableRow(
                this.bookName.Text,
                this.author.Text,
                int.Parse(this.price.Text));

            // データを JSON 形式で保存
            SaveData();

            // 入力フィールドをクリア
            ClearInputFields();
        }

        // 「削除」ボタンがクリックされたときの処理
        private void RemoveButtonClicked(object sender, EventArgs e)
        {
            // 選択中の行を取得し、削除する
            int row = this.bookDataGrid.CurrentRow.Index;
            this.bookDataGrid.Rows.RemoveAt(row);

            // データを JSON 形式で保存
            SaveData();
        }

        // DataTable の内容を JSON に変換し、ファイルへ保存するメソッド
        private void SaveData()
        {
            // DataTable の内容を Book オブジェクトのリストに変換
            var books = new List<Book>();
            foreach (DataRow row in bookDataSet.bookDataTable.Rows)
            {
                books.Add(new Book
                {
                    BookName = row["書名"].ToString(),
                    Author = row["著者"].ToString(),
                    Price = (int)row["値段"]
                });
            }

            // Book リストを JSON 文字列にシリアライズ(整形表示)
            var json = JsonConvert.SerializeObject(books, Formatting.Indented);

            // JSON 文字列をファイルに書き込み
            File.WriteAllText(FileName, json);
        }

        // JSON ファイルからデータを読み込み、DataTable に反映するメソッド
        private void LoadData()
        {
            // JSON ファイルが存在するか確認
            if (File.Exists(FileName))
            {
                // ファイルから JSON 文字列を読み込み
                var json = File.ReadAllText(FileName);
                // JSON 文字列を List<Book> にデシリアライズ
                var books = JsonConvert.DeserializeObject<List<Book>>(json);

                // 各 Book オブジェクトを DataTable に追加
                foreach (var book in books)
                {
                    bookDataSet.bookDataTable.AddbookDataTableRow(
                        book.BookName,
                        book.Author,
                        book.Price);
                }
            }
        }

        // 入力フィールド(テキストボックス)の内容をクリアするメソッド
        private void ClearInputFields()
        {
            this.bookName.Clear();
            this.author.Clear();
            this.price.Clear();
        }

        // DataGridView で行が選択されたとき、テキストボックスに反映するメソッド
        private void BookDataGrid_SelectionChanged(object sender, EventArgs e)
        {
            if (bookDataGrid.CurrentRow != null)
            {
                DataGridViewRow currentRow = bookDataGrid.CurrentRow;
                this.bookName.Text = currentRow.Cells["書名DataGridViewTextBoxColumn"].Value.ToString();
                this.author.Text = currentRow.Cells["著者DataGridViewTextBoxColumn"].Value.ToString();
                this.price.Text = currentRow.Cells["値段DataGridViewTextBoxColumn"].Value.ToString();
            }
        }
    }
}

このコード行は、DataGridView の現在選択されている行から「書名」に対応するセルの値を取得し、その値を文字列に変換してテキストボックス bookNameText プロパティに設定する処理を行っています。具体的には以下のような流れになります。

  1. currentRow.Cells["書名DataGridViewTextBoxColumn"]
    • currentRow は、DataGridView の現在選択されている行を示す変数です。
    • Cells は、その行に含まれる各セル(列)のコレクションです。
    • インデクサに "書名DataGridViewTextBoxColumn" を指定することで、DataGridView 内の「書名」に対応する列(通常はデザイナーが自動生成した名前)のセルを取得しています。
  2. .Value
    • 取得したセルの内部に保持されているデータ(値)を取り出します。これは一般にオブジェクト型で保持されています。
  3. .ToString()
    • 取り出した値を文字列に変換します。これにより、数値やその他の型であっても文字列として扱えるようになります。
  4. this.bookName.Text = ...
    • 変換された文字列を、フォーム上に配置されているテキストボックス bookNameText プロパティに設定します。これにより、ユーザーインターフェース上で該当セルの値が表示されます。

まとめ

  • 目的: DataGridView の選択行から「書名」データを抽出し、テキストボックスに表示する。
  • 処理の流れ:
  1. 現在選択中の行 (currentRow) を取得
  2. その行の「書名」セル(キー "書名DataGridViewTextBoxColumn")から値を取得
  3. 取得した値を文字列に変換
  4. テキストボックス bookName に表示

このようにして、ユーザーが DataGridView の行を選択した際に、該当する書名がテキストボックスに自動的に反映されるようになっています。

以下は、BindingSource を利用して DataGridView とテキストボックス間の同期を自動化する例です。
これにより、DataGridView の選択が変わったときに、明示的にイベントハンドラ内でテキストボックスへ値を設定する必要がなくなります。

たとえば、元のコードのうち選択行からテキストボックスへ値を反映する処理

// DataGridView で行が選択されたとき、テキストボックスに反映するメソッド
private void BookDataGrid_SelectionChanged(object sender, EventArgs e)
{
    if (bookDataGrid.CurrentRow != null)
    {
        DataGridViewRow currentRow = bookDataGrid.CurrentRow;
        this.bookName.Text = currentRow.Cells["書名DataGridViewTextBoxColumn"].Value.ToString();
        this.author.Text = currentRow.Cells["著者DataGridViewTextBoxColumn"].Value.ToString();
        this.price.Text = currentRow.Cells["値段DataGridViewTextBoxColumn"].Value.ToString();
    }
}

を、下記のようにバインディングを設定するだけで実現できます。


更新例

namespace BookManager
{
    public partial class Form1 : Form
    {
        // JSONファイルのパス(相対パス)を定義
        private const string FileName = @"..\..\books.json";
        // BindingSource インスタンス(フォーム全体で利用)
        private BindingSource bindingSource = new BindingSource();

        // コンストラクタ(フォーム初期化時に呼ばれる)
        public Form1()
        {
            InitializeComponent();

            // フォーム表示時に保存データを読み込む
            LoadData();

            // BindingSource に DataTable を設定(DataSetのテーブル名は bookDataTable)
            bindingSource.DataSource = bookDataSet.bookDataTable;

            // DataGridView に BindingSource をバインド
            bookDataGrid.DataSource = bindingSource;

            // テキストボックスと DataTable の各列をバインドする
            // ※ DataTable の列名 "書名"、"著者"、"値段" に合わせる
            bookName.DataBindings.Add("Text", bindingSource, "書名", true, DataSourceUpdateMode.OnPropertyChanged);
            author.DataBindings.Add("Text", bindingSource, "著者", true, DataSourceUpdateMode.OnPropertyChanged);
            price.DataBindings.Add("Text", bindingSource, "値段", true, DataSourceUpdateMode.OnPropertyChanged);
        }

        // 「追加」ボタンがクリックされたときの処理
        private void AddButtonClicked(object sender, EventArgs e)
        {
            // 重複チェック:すでに同じ書名の本があるか確認
            foreach (DataRow row in bookDataSet.bookDataTable.Rows)
            {
                if (row["書名"].ToString() == this.bookName.Text)
                {
                    MessageBox.Show("同じ書名の本が既に存在します。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
            }

            // テキストボックスの内容を DataTable に追加
            bookDataSet.bookDataTable.AddbookDataTableRow(
                this.bookName.Text,
                this.author.Text,
                int.Parse(this.price.Text));

            // データを JSON 形式で保存
            SaveData();

            // 入力フィールドをクリア
            ClearInputFields();
        }

        // 「削除」ボタンがクリックされたときの処理
        private void RemoveButtonClicked(object sender, EventArgs e)
        {
            // 選択中の行を取得し、削除する
            if (bookDataGrid.CurrentRow != null)
            {
                int rowIndex = bookDataGrid.CurrentRow.Index;
                bookDataGrid.Rows.RemoveAt(rowIndex);

                // データを JSON 形式で保存
                SaveData();
            }
        }

        // DataTable の内容を JSON に変換し、ファイルへ保存するメソッド
        private void SaveData()
        {
            // DataTable の内容を Book オブジェクトのリストに変換
            var books = new List<Book>();
            foreach (DataRow row in bookDataSet.bookDataTable.Rows)
            {
                books.Add(new Book
                {
                    BookName = row["書名"].ToString(),
                    Author = row["著者"].ToString(),
                    Price = (int)row["値段"]
                });
            }

            // Book リストを JSON 文字列にシリアライズ(整形表示)
            var json = JsonConvert.SerializeObject(books, Formatting.Indented);

            // JSON 文字列をファイルに書き込み
            File.WriteAllText(FileName, json);
        }

        // JSON ファイルからデータを読み込み、DataTable に反映するメソッド
        private void LoadData()
        {
            // JSON ファイルが存在するか確認
            if (File.Exists(FileName))
            {
                // ファイルから JSON 文字列を読み込み
                var json = File.ReadAllText(FileName);
                // JSON 文字列を List<Book> にデシリアライズ
                var books = JsonConvert.DeserializeObject<List<Book>>(json);

                // 各 Book オブジェクトを DataTable に追加
                foreach (var book in books)
                {
                    bookDataSet.bookDataTable.AddbookDataTableRow(
                        book.BookName,
                        book.Author,
                        book.Price);
                }
            }
        }

        // 入力フィールド(テキストボックス)の内容をクリアするメソッド
        private void ClearInputFields()
        {
            this.bookName.Clear();
            this.author.Clear();
            this.price.Clear();
        }

        // ※ DataGridView の選択変更時にテキストボックスへ値を反映する処理は、
        //     バインディングによって自動で行われるため、以下のイベントハンドラは不要となります。
        //private void BookDataGrid_SelectionChanged(object sender, EventArgs e)
        //{
        //    // バインディングにより自動的にテキストボックスが更新される
        //}
    }
}

解説

  • BindingSource の利用
    フォーム内で BindingSource bindingSource = new BindingSource(); を作成し、
    bindingSource.DataSource = bookDataSet.bookDataTable; とすることで、DataTable をラップします。
    その後、DataGridView に bookDataGrid.DataSource = bindingSource; と設定すると、
    選択行が変わるたびに BindingSource の Current が更新されます。
  • テキストボックスとのバインディング
    各テキストボックスの Text プロパティを、BindingSource の対応する列(ここでは “書名"、"著者"、"値段")にバインドします。
    これにより、DataGridView の選択行が変わると自動的にテキストボックスの内容も更新されます。
  • 不要なイベントハンドラの削除
    以前は SelectionChanged イベントで手動にテキストボックスへ値を設定していましたが、
    バインディングによりその必要がなくなります。不要なコードは削除(またはコメントアウト)しておくとよいでしょう。

このようにデータバインディングを用いると、選択行の変更に応じたテキストボックスの更新処理を自動化でき、コードがシンプルになります。

Bookクラス

// JSON との変換用に用意したデータモデル
public class Book
{
    public string BookName { get; set; }
    public string Author { get; set; }
    public int Price { get; set; }
}
  • JSON ファイルへのシリアライズ/デシリアライズに使用する データモデルです。
  • フォームの入力から得た書籍名 (BookName)、著者 (Author)、値段 (Price) を格納します。

各メソッドの説明

コンストラクタ Form1()

  • フォームの初期化時に InitializeComponent() で各コントロールを設定します。
  • LoadData() を呼び出し、保存済みデータ(JSONファイル)があれば読み込んで DataTable に反映させます。

AddButtonClicked メソッド

ユーザーが「追加」ボタンを押したときに呼ばれます。

  1. 同じ書名の本がすでに存在するかを確認し、あればエラーメッセージを表示して処理を中断します。
  2. 重複がなければ、テキストボックスの入力内容を DataTable に追加し、SaveData() を呼んで JSON ファイルに保存します。
  3. ClearInputFields() を呼び出して入力欄をクリアします。

RemoveButtonClicked メソッド

ユーザーが「削除」ボタンを押したときに呼ばれます。

  • DataGridView の選択行を取得し、削除します。
  • 削除後に SaveData() を呼んで JSON ファイルに保存します。

SaveData メソッド

  1. DataTable の各行を Book オブジェクトのリストに変換します。
  2. 変換したリストを JSON 文字列にシリアライズし、ファイルに書き込みます。
    • Formatting.Indented により、JSON は見やすい形(インデント付き)で保存されます。

LoadData メソッド

  1. JSON ファイルが存在する場合、その内容を読み込みます。
  2. JSON 文字列を List<Book> にデシリアライズし、DataTable に追加します。

ClearInputFields メソッド

  • データ追加後や必要に応じて、書名・著者・値段のテキストボックスをすべてクリアして、次の入力をしやすくします。

BookDataGrid_SelectionChanged メソッド

  • DataGridView で行が選択されたときに呼ばれ、選択された行のデータをテキストボックスに表示します。

JSON のシリアライズ/デシリアライズの基本

JSON とは?

JSON (JavaScript Object Notation) は、データを表現するためのシンプルなフォーマットです。人にも機械にも読みやすく、Web やアプリ間のデータ交換に広く使われています。
例:

{
    "BookName": "C#入門",
    "Author": "山田太郎",
    "Price": 2500
}

シリアライズとは?

シリアライズは、プログラム内のオブジェクトを JSON 文字列などの形式に変換することです。
例:C# の Book オブジェクトを JSON に変換

Book myBook = new Book { BookName = "C#入門", Author = "山田太郎", Price = 2500 };
string json = JsonConvert.SerializeObject(myBook);
// 出力例: {"BookName":"C#入門","Author":"山田太郎","Price":2500}

デシリアライズとは?

デシリアライズは、JSON 文字列からオブジェクトに変換することです。
例:JSON 文字列から Book オブジェクトに変換

string json = "{\"BookName\":\"C#入門\",\"Author\":\"山田太郎\",\"Price\":2500}";
Book myBook = JsonConvert.DeserializeObject<Book>(json);
// myBook の内容: BookName = "C#入門", Author = "山田太郎", Price = 2500

ソリューションエクスプローラで作成したJsonファイルを確認する方法

  • Visual Studio のソリューションエクスプローラでプロジェクトを展開し、設定により隠れている場合は 「すべてのファイルを表示」 をクリックして .json ファイルを表示します。
  • ダブルクリックまたは右クリックして「開く」を選択すると、JSONファイルの中身をテキストエディタで確認できます。

トグルになっていますので、再度クリックで表示無しになります

プロジェクトの作成と実装手順

  1. プロジェクトの作成
    • Visual Studio で新しい Windows Forms アプリケーション プロジェクトを作成します。
  2. NuGet パッケージのインストール
    • NuGet パッケージマネージャを利用して、Newtonsoft.Json パッケージをインストールします。
  3. 名前空間のインポート
    • 先ほど紹介した using System.IO; や using Newtonsoft.Json; などをソースコードの先頭に追加します。
  4. フォームデザイン
    • テキストボックス(書名、著者、値段用)
    • 「追加」ボタンと「削除」ボタン
    • DataGridView(データ一覧用)
      をフォームに配置します。
  5. コードの実装
    • Form1 クラス および Book クラス のコードをプロジェクトに追加します。
  6. イベントハンドラの設定
    • 各ボタン(追加・削除)や DataGridView のイベント(例:SelectionChanged)に、対応するメソッドを設定します。

補足(オプション機能)

DataGridView を列幅いっぱいに表示したい

bookDataGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
  • 列幅を自動的にグリッド全体の幅に合わせます。
  • AutoSizeColumnsMode プロパティを Fill に設定することで、各列が均等に幅を割り当てられます。

セルの値を直接変更できないようにしたい

// 各列を読み取り専用に設定
foreach (DataGridViewColumn column in bookDataGrid.Columns)
{
    column.ReadOnly = true;
}

また、DataGridViewを選択し、ビジュアルでの設定も可能です

  • これにより、DataGridView でセルの内容をユーザーが直接編集できなくなります。
  • 画面上にデータを表示するだけの場合に便利です。

セルをクリックすると行全部が選択されるようにしたい

// 行全体を選択するように設定
bookDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
bookDataGrid.MultiSelect = false; // 複数行選択を無効化する場合
  • SelectionMode を FullRowSelect に設定すると、セルをクリックした際に行全体が選択されます。
  • MultiSelect を false にすると、複数行の同時選択を防ぎます。

選択された行の値をテキストボックスに反映させたい

// 選択された行のデータをテキストボックスに反映するメソッド
private void BookDataGrid_SelectionChanged(object sender, EventArgs e)
{
    if (bookDataGrid.CurrentRow != null)
    {
        DataGridViewRow currentRow = bookDataGrid.CurrentRow;
        this.bookName.Text = currentRow.Cells["書名DataGridViewTextBoxColumn"].Value.ToString();
        this.author.Text = currentRow.Cells["著者DataGridViewTextBoxColumn"].Value.ToString();
        this.price.Text = currentRow.Cells["値段DataGridViewTextBoxColumn"].Value.ToString();
    }
}
  • SelectionChanged イベントを利用し、選択された行のデータをテキストボックスに表示する仕組みです。
  • Form1 のコンストラクタなどで bookDataGrid.SelectionChanged += BookDataGrid_SelectionChanged; のようにイベントを関連付けてください。

重複追加をできないようにしたい

foreach (DataRow row in bookDataSet.bookDataTable.Rows)
{
    if (row["書名"].ToString() == this.bookName.Text)
    {
        MessageBox.Show("同じ書名の本が既に存在します。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }
}
  • AddButtonClicked メソッド で重複チェックを行い、同じ書名の本が存在する場合はエラーを表示して処理を中断します。
  • ユーザーに重複追加をさせない仕組みを簡単に実装できます。

データ追加後に入力フィールドをクリアしたい

// 入力フィールドをクリアするメソッド
private void ClearInputFields()
{
    this.bookName.Clear();
    this.author.Clear();
    this.price.Clear();
}
  • データを追加した後に ClearInputFields() を呼び出すことで、次の入力がしやすいようにテキストボックスを空にします。

入力ごとに右寄せし、カーソルは常に右端にしたい

1. テキストを右寄せする場合

  • フォームデザイナー上、TextBox の TextAlign プロパティを Right に設定します。

2. カーソルを常に右端にする場合

private void TextBox_TextChanged(object sender, EventArgs e)
{
    TextBox textBox = sender as TextBox;
    // カーソルをテキストの右端に移動
    textBox.SelectionStart = textBox.Text.Length;
    textBox.SelectionLength = 0;
}

TextBoxのTextAlignプロパティをRightに設定することでも可能

  • TextChanged イベントを利用し、常に入力文字列の末尾にカーソルが移動するようにします。
  • これにより、レジ入力のように右端から入力するユーザーインターフェースを実現できます。