WinForms + JSON 書籍管理アプリ 初学者向けチュートリアル

2026年9月9日

広告

WinFormsで入力したデータを、アプリを閉じたあとも残しておきたいことがあります。今回は、書名・著者・価格を登録できる書籍管理アプリを作り、JSONファイルへの保存と読み込みを体験します。

最初は「保存」「読込」ボタンを押して動きを確認します。仕組みが分かってから、起動時の自動読込と終了時の自動保存へ発展させます。

今回学ぶこと

  • オブジェクトをJSON文字列へ変換するシリアライズ
  • JSON文字列からオブジェクトを復元するデシリアライズ
  • List<BookRecord>をファイルへ保存する方法
  • DataGridViewとデータを結びつける方法
  • 選択中のオブジェクトを安全に削除する方法

JSONとは

JSONは、データを文字として保存・受け渡しするための形式です。C#のオブジェクトを、そのままファイルへ保存することはできません。そこで、オブジェクトをJSON文字列へ変換してから保存します。

用語 処理
シリアライズ C#のオブジェクト → JSON文字列
デシリアライズ JSON文字列 → C#のオブジェクト

WinFormsで入力した書籍情報をBookRecordとListで管理し、JSONへ保存して再起動後に一覧を復元する流れ
入力したデータをJSONへ保存し、次回起動時に一覧を復元する流れ


開発環境

  • Visual Studio 2026
  • .NET 10.0
  • ワークロード:.NET デスクトップ開発

System.Text.Jsonは.NET 10.0に標準で含まれているため、NuGetパッケージの追加は不要です。

プロジェクトを作る

  1. 「新しいプロジェクトの作成」を選ぶ
  2. 「Windows フォーム アプリ」を選ぶ
  3. プロジェクト名をBookManagerにする
  4. フレームワークに「.NET 10.0」を選ぶ
  5. 「作成」をクリックする

フォームにコントロールを配置する

Form1に次のコントロールを配置します。授業中に確認しやすいよう、Nameプロパティを表と同じ名前に統一してください。

コントロール Name Text
DataGridView bookDataGrid
TextBox txtTitle 空欄
TextBox txtAuthor 空欄
TextBox txtPrice 空欄
Button btnAdd 登録
Button btnRemove 削除
Button btnSave 保存
Button btnLoad 読込

「書名」「著者」「価格」を示すLabelも配置します。Labelはコードから操作しないため、Nameは既定のままでも構いません。

BookRecordクラスを作る

プロジェクトを右クリックし、「追加」→「クラス」を選び、ファイル名をBookRecord.csにします。書籍1冊分のデータを表すクラスです。

namespace BookManager
{
    public class BookRecord
    {
        public string Title { get; set; } = "";
        public string Author { get; set; } = "";
        public int Price { get; set; }
    }
}

List<BookRecord>に複数の書籍を入れ、まとめてJSONへ変換します。

Form1.csを書く

まずは完成コードです。そのあとで、JSONに関係する部分を順番に確認します。

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

namespace BookManager
{
    public partial class Form1 : Form
    {
        private readonly BindingSource bindingSource = new BindingSource();

        private List<BookRecord> books = new List<BookRecord>();

        private readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions
        {
            WriteIndented = true,
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
        };

        public Form1()
        {
            InitializeComponent();

            bindingSource.DataSource = books;
            bookDataGrid.DataSource = bindingSource;

            bookDataGrid.ReadOnly = true;
            bookDataGrid.AllowUserToAddRows = false;
            bookDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            bookDataGrid.MultiSelect = false;
            bookDataGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtTitle.Text)
                || string.IsNullOrWhiteSpace(txtAuthor.Text))
            {
                MessageBox.Show("書名と著者を入力してください。");
                return;
            }

            if (!int.TryParse(txtPrice.Text, out int price)
                || price < 0)
            {
                MessageBox.Show("価格は0以上の整数で入力してください。");
                return;
            }

            BookRecord book = new BookRecord
            {
                Title = txtTitle.Text.Trim(),
                Author = txtAuthor.Text.Trim(),
                Price = price
            };

            books.Add(book);
            bindingSource.ResetBindings(false);

            txtTitle.Clear();
            txtAuthor.Clear();
            txtPrice.Clear();
            txtTitle.Focus();
        }

        private void btnRemove_Click(object sender, EventArgs e)
        {
            BookRecord? selectedBook = bookDataGrid.CurrentRow?.DataBoundItem as BookRecord;

            if (selectedBook == null)
            {
                MessageBox.Show("削除する書籍を選んでください。");
                return;
            }

            books.Remove(selectedBook);
            bindingSource.ResetBindings(false);
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            if (SaveDataAsJson())
            {
                MessageBox.Show($"保存しました。\n{GetJsonPath()}");
            }
        }

        private void btnLoad_Click(object sender, EventArgs e)
        {
            if (LoadDataFromJson())
            {
                MessageBox.Show("読み込みました。");
            }
        }

        private string GetJsonPath()
        {
            string folderPath = Path.Combine(
                Environment.GetFolderPath(
                    Environment.SpecialFolder.LocalApplicationData),
                "BookManager");

            Directory.CreateDirectory(folderPath);

            return Path.Combine(folderPath, "BookData.json");
        }

        private bool SaveDataAsJson()
        {
            try
            {
                string json = JsonSerializer.Serialize(books, jsonOptions);

                File.WriteAllText(GetJsonPath(), json);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"保存に失敗しました。\n{ex.Message}");
                return false;
            }
        }

        private bool LoadDataFromJson()
        {
            try
            {
                string path = GetJsonPath();

                if (!File.Exists(path))
                {
                    MessageBox.Show("まだ保存ファイルがありません。");
                    return false;
                }

                string json = File.ReadAllText(path);

                books = JsonSerializer.Deserialize<List<BookRecord>>(json) ?? new List<BookRecord>();

                bindingSource.DataSource = books;
                bindingSource.ResetBindings(false);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"読み込みに失敗しました。\n{ex.Message}");
                return false;
            }
        }
    }
}

一覧表示に関係する設定

最初の2行で、booksの内容がBindingSourceを通してbookDataGridに表示されるようにしています。

コード 役割
bindingSource.DataSource = books; 書籍リストをBindingSourceに設定する
bookDataGrid.DataSource = bindingSource; BindingSourceの内容を一覧に表示する
ReadOnly = true 一覧上で直接編集できないようにする
AllowUserToAddRows = false 一覧の末尾に空の入力行を表示しない
SelectionMode = FullRowSelect セルではなく行全体を選択する
MultiSelect = false 複数行を同時に選択できないようにする
AutoSizeColumnsMode = Fill 列幅を一覧の横幅に合わせる

今回は入力欄から書籍を登録し、選択した1冊を削除するため、このような設定にしています。

イベントを割り当てる

デザイナで各ボタンを選び、プロパティウィンドウの⚡からClickイベントを割り当てます。

コントロール イベント イベントハンドラー
btnAdd Click btnAdd_Click
btnRemove Click btnRemove_Click
btnSave Click btnSave_Click
btnLoad Click btnLoad_Click

JSONへ保存する処理

string json = JsonSerializer.Serialize(books, jsonOptions);

File.WriteAllText(GetJsonPath(), json);

Serializeによって、booksの内容がJSON形式の文字列になります。その文字列をFile.WriteAllTextでファイルへ保存します。

WriteIndented = trueはJSONを改行・字下げして読みやすくします。Encoderの設定は、日本語をそのまま確認しやすくするためのものです。

保存されるJSONを確認する

2冊登録して保存すると、BookData.jsonは次のようになります。

[
  {
    "Title": "吾輩は猫である",
    "Author": "夏目漱石",
    "Price": 680
  },
  {
    "Title": "銀河鉄道の夜",
    "Author": "宮沢賢治",
    "Price": 550
  }
]

保存先は次のフォルダーです。ユーザー名の部分はパソコンによって異なります。

C:Usersユーザー名AppDataLocalBookManagerBookData.json

JSONから読み込む処理

string json = File.ReadAllText(path);

books = JsonSerializer.Deserialize<List<BookRecord>>(json) ?? new List<BookRecord>();

Deserialize<List<BookRecord>>は、JSON文字列から書籍のリストを復元します。復元後にbindingSource.DataSourceを設定し直すことで、DataGridViewにも読み込んだ内容が表示されます。

削除は行番号ではなくオブジェクトで行う

CurrentRow.Indexを使ってRemoveAtすると、一覧の並び順を変更した場合に別の書籍を削除する可能性があります。選択行に結びついているBookRecordを取得して削除する方が安全です。

BookRecord? selectedBook = bookDataGrid.CurrentRow?.DataBoundItem as BookRecord;

if (selectedBook != null)
{
    books.Remove(selectedBook);
    bindingSource.ResetBindings(false);
}

実行して確認する

  1. F5キーで実行する
  2. 書名・著者・価格を入力して「登録」を押す
  3. 2冊ほど登録して「保存」を押す
  4. 一覧から書籍を削除する
  5. 「読込」を押し、保存時の一覧に戻ることを確認する

発展:起動時に読み込み、終了時に保存する

保存と読み込みの仕組みを確認できたら、自動化してみましょう。フォームのLoadイベントとFormClosingイベントを割り当てます。

private void Form1_Load(object sender, EventArgs e)
{
    if (File.Exists(GetJsonPath()))
    {
        LoadDataFromJson();
    }
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    SaveDataAsJson();
}

自動化しても、最初に作った「保存」「読込」ボタンは動作確認や手動バックアップに利用できます。

練習問題

BookRecordに在庫数を追加し、フォームから入力・保存できるようにしてください。

解答例を見る

BookRecordStockプロパティを追加します。

public int Stock { get; set; }

フォームには在庫数入力用のtxtStockを追加し、登録処理で整数へ変換します。

if (!int.TryParse(txtStock.Text, out int stock)
    || stock < 0)
{
    MessageBox.Show("在庫数は0以上の整数で入力してください。");
    return;
}

BookRecord book = new BookRecord
{
    Title = txtTitle.Text.Trim(),
    Author = txtAuthor.Text.Trim(),
    Price = price,
    Stock = stock
};

プロパティを追加すれば、JsonSerializerが在庫数もJSONへ保存・復元します。

まとめ

  • Serialize:オブジェクトをJSON文字列へ変換する
  • Deserialize:JSON文字列からオブジェクトを復元する
  • File.WriteAllTextFile.ReadAllTextでファイルを保存・読込する
  • 一覧から削除するときは、行番号ではなく選択中のオブジェクトを使う
  • 仕組みを確認してから自動保存・自動読込へ発展させる

JSONは、アプリの設定・ゲームのセーブデータ・ユーザーが入力した情報など、さまざまなデータ保存に利用できます。

訪問数 101 回, 今日の訪問数 1回

広告