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

1. ゴール

  • 書籍の「タイトル」「著者」「価格」を登録できる
  • 登録データが一覧(表)に表示される
  • 選択行を削除できる
  • アプリ終了時に自動で JSON 保存
  • アプリ起動時に JSON から自動で読み込み

2. 準備

必要な環境

  • Visual Studio 2022
    • ワークロード:.NET デスクトップ開発
  • .NET 8 SDK(インストール済みなら VS で選べます)

3. 新しいプロジェクト作成

  1. Visual Studio を起動 → 「新しいプロジェクトの作成」
  2. 「Windows フォーム アプリ」(.NET 6/7/8 用) を選択
  3. プロジェクト名:BookManager
  4. フレームワーク:.NET 8.0
  5. 作成をクリック

4. UI の設計(デザイナ)

コントロールの配置

フォーム (Form1) に以下を配置します。

コントロールName プロパティText(表示名)備考
DataGridViewbookDataGrid(空)列は後で自動生成
Label書名
TextBoxtxtTitle(空)
Label著者
TextBoxtxtAuthor(空)
Label価格
TextBoxtxtPrice(空)
ButtonbtnAdd登録
ButtonbtnRemove削除

5. コードの作成

5.1 書籍データクラス(BookRecord.cs)

プロジェクトに「クラス」を追加し、以下を記述します。

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

5.2 Form1.cs のコード

既存のコードを以下に置き換えます。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.Windows.Forms;

namespace BookManager
{
    public partial class Form1 : Form
    {
        private BindingSource bindingSource = new BindingSource();
        private List<BookRecord> books = new List<BookRecord>();

        public Form1()
        {
            InitializeComponent();

            // DataGridView とデータを結びつける
            bindingSource.DataSource = books;
            bookDataGrid.DataSource = bindingSource;

            // 列名の表示をわかりやすく
            bookDataGrid.AutoGenerateColumns = true;
        }

        // 起動時に JSON 読み込み
        private void Form1_Load(object sender, EventArgs e)
        {
            LoadDataFromJson();
        }

        // 終了時に JSON 保存
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveDataAsJson();
        }

        // 登録ボタン
        private void btnAdd_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrWhiteSpace(txtTitle.Text) ||
                string.IsNullOrWhiteSpace(txtAuthor.Text))
            {
                MessageBox.Show("書名と著者は必須です。", "入力エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

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

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

            bindingSource.ResetBindings(false);

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

        // 削除ボタン
        private void btnRemove_Click(object sender, EventArgs e)
        {
            if (bookDataGrid.CurrentRow == null)
            {
                MessageBox.Show("削除する行を選択してください。", "削除",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            int index = bookDataGrid.CurrentRow.Index;
            if (index >= 0 && index < books.Count)
            {
                books.RemoveAt(index);
                bindingSource.ResetBindings(false);
            }
        }

        // JSON保存先
        private string GetJsonPath()
        {
            string baseDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "BookManager");

            if (!Directory.Exists(baseDir))
                Directory.CreateDirectory(baseDir);

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

        // JSONに保存
        private void SaveDataAsJson()
        {
            try
            {
                var options = new JsonSerializerOptions
                {
                    WriteIndented = true,
                    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
                };
                var json = JsonSerializer.Serialize(books, options);
                File.WriteAllText(GetJsonPath(), json, Encoding.UTF8);
            }
            catch (Exception ex)
            {
                MessageBox.Show("保存に失敗しました。\n" + ex.Message, "保存エラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // JSONから読み込み
        private void LoadDataFromJson()
        {
            try
            {
                string path = GetJsonPath();
                if (File.Exists(path))
                {
                    var json = File.ReadAllText(path, Encoding.UTF8);
                    books = JsonSerializer.Deserialize<List<BookRecord>>(json) ?? new();
                    bindingSource.DataSource = books;
                    bindingSource.ResetBindings(false);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("読み込みに失敗しました。\n" + ex.Message, "読み込みエラー",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

6. イベント配線

デザイナで Form を選択し、プロパティ(稲妻マーク)で以下を設定:

  • Load → Form1_Load
  • FormClosing → Form1_FormClosing

ボタン:

  • btnAdd.Click → btnAdd_Click
  • btnRemove.Click → btnRemove_Click

7. 実行と確認

  1. F5 で実行
  2. 「書名」「著者」「価格」を入力して 登録
  3. 登録内容が一覧に表示される
  4. アプリを終了 → 再起動するとデータが残っている

8. 発展課題

  • 検索機能(タイトルや著者で絞り込み)
  • ソート(価格やタイトル順)
  • 別フォルダやファイル名を選べる保存機能
  • JSONのバックアップ作成

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