WinForms + JSON 書籍管理アプリ 初学者向けチュートリアル
WinFormsで入力したデータを、アプリを閉じたあとも残しておきたいことがあります。今回は、書名・著者・価格を登録できる書籍管理アプリを作り、JSONファイルへの保存と読み込みを体験します。
最初は「保存」「読込」ボタンを押して動きを確認します。仕組みが分かってから、起動時の自動読込と終了時の自動保存へ発展させます。
今回学ぶこと
- オブジェクトをJSON文字列へ変換するシリアライズ
- JSON文字列からオブジェクトを復元するデシリアライズ
List<BookRecord>をファイルへ保存する方法DataGridViewとデータを結びつける方法- 選択中のオブジェクトを安全に削除する方法

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

開発環境
- Visual Studio 2026
- .NET 10.0
- ワークロード:.NET デスクトップ開発
System.Text.Jsonは.NET 10.0に標準で含まれているため、NuGetパッケージの追加は不要です。
プロジェクトを作る
- 「新しいプロジェクトの作成」を選ぶ
- 「Windows フォーム アプリ」を選ぶ
- プロジェクト名を
BookManagerにする - フレームワークに「.NET 10.0」を選ぶ
- 「作成」をクリックする
フォームにコントロールを配置する
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);
}
実行して確認する
- F5キーで実行する
- 書名・著者・価格を入力して「登録」を押す
- 2冊ほど登録して「保存」を押す
- 一覧から書籍を削除する
- 「読込」を押し、保存時の一覧に戻ることを確認する
発展:起動時に読み込み、終了時に保存する
保存と読み込みの仕組みを確認できたら、自動化してみましょう。フォームの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に在庫数を追加し、フォームから入力・保存できるようにしてください。
解答例を見る
BookRecordへStockプロパティを追加します。
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.WriteAllTextとFile.ReadAllTextでファイルを保存・読込する- 一覧から削除するときは、行番号ではなく選択中のオブジェクトを使う
- 仕組みを確認してから自動保存・自動読込へ発展させる
JSONは、アプリの設定・ゲームのセーブデータ・ユーザーが入力した情報など、さまざまなデータ保存に利用できます。









ディスカッション
コメント一覧
まだ、コメントがありません