Windows Forms で作るシンプル 4択クイズ(CSV版)

(初心者向け:まずは直下CSV → チーム運用で data フォルダに発展)

このチュートリアルは、最短で動くシンプル版を先に作り、慣れたらチーム運用しやすい構成に発展させます。


完成イメージ

  • Label(問題文)+ Button(選択肢×4・次へ)だけのUI
  • 問題は CSV から読み込み
  • 正解/不正解は MessageBox、最後にスコア表示

パートA:まずは最短で動かす(CSVはプロジェクト直下)

1. プロジェクト作成

  1. Visual Studio → 新しいプロジェクト
  2. Windows フォームアプリ(C#)」を選択
  3. 名前:FourChoiceCsvApp

2. フォームに部品を置く(ツールボックス)

  • Label → lblQuestion
  • Button → btnChoice1, btnChoice2, btnChoice3, btnChoice4
  • Button → btnNext

3. questions.csv を作る(プロジェクト直下)

メモ帳などで作成し、UTF-8 で保存。

question,choice1,choice2,choice3,choice4,correctIndex
"C#でWindowsアプリを作るための仕組みは?","WPF","Windows Forms","ASP.NET","Blazor",1
"MessageBoxを表示する命令は?","Console.WriteLine","MessageBox.Show","Print()","Form.Close",1
"配列の最初の番号は?","0","1","-1","10",0

correctIndex は 0〜3(0=choice1, 1=choice2, …)

4. CSVを出力先にコピー(プロパティ設定)

  1. ソリューションエクスプローラで questions.csv を右クリック → プロパティ
  2. 出力ディレクトリにコピー → 常にコピーする(初心者に最も安全)

5. コード(Form1.cs)

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace FourChoiceCsvApp
{
    public partial class Form1 : Form
    {
        private class Quiz
        {
            public string Question { get; set; }
            public string[] Choices { get; set; }
            public int Correct { get; set; }
        }

        private readonly List<Quiz> _quizzes = new();
        private int _current = 0;
        private int _score = 0;
        private bool _answered = false;

        public Form1() => InitializeComponent();

        private void Form1_Load(object sender, EventArgs e)
        {
            LoadQuizzes("questions.csv"); // まずは直下

            btnChoice1.Click += Answer_Click;
            btnChoice2.Click += Answer_Click;
            btnChoice3.Click += Answer_Click;
            btnChoice4.Click += Answer_Click;
            btnNext.Click += BtnNext_Click;

            ShowQuestion();
        }

        private void LoadQuizzes(string path)
        {
            if (!File.Exists(path))
            {
                MessageBox.Show($"{path} が見つかりません。", "エラー");
                Close();
                return;
            }

            var lines = File.ReadAllLines(path);
            for (int i = 1; i < lines.Length; i++) // 1行目はヘッダー
            {
                var parts = SplitCsvLine(lines[i]);
                if (parts.Length < 6) continue;

                _quizzes.Add(new Quiz
                {
                    Question = parts[0],
                    Choices = new[] { parts[1], parts[2], parts[3], parts[4] },
                    Correct  = int.Parse(parts[5])
                });
            }
        }

        // カンマ・ダブルクォート対応の簡易パーサ
        private static string[] SplitCsvLine(string line)
        {
            var list = new List<string>();
            bool inQuotes = false;
            var cur = "";

            foreach (char c in line)
            {
                if (c == '"') inQuotes = !inQuotes;
                else if (c == ',' && !inQuotes) { list.Add(cur); cur = ""; }
                else cur += c;
            }
            list.Add(cur);
            return list.ToArray();
        }

        private void ShowQuestion()
        {
            if (_current >= _quizzes.Count)
            {
                MessageBox.Show($"終了! スコア:{_score}/{_quizzes.Count}");
                return;
            }

            var q = _quizzes[_current];
            lblQuestion.Text = q.Question;
            btnChoice1.Text = q.Choices[0];
            btnChoice2.Text = q.Choices[1];
            btnChoice3.Text = q.Choices[2];
            btnChoice4.Text = q.Choices[3];

            btnChoice1.Enabled = btnChoice2.Enabled = btnChoice3.Enabled = btnChoice4.Enabled = true;
            _answered = false;
        }

        private void Answer_Click(object sender, EventArgs e)
        {
            if (_answered) return;

            int index = sender == btnChoice1 ? 0 :
                        sender == btnChoice2 ? 1 :
                        sender == btnChoice3 ? 2 : 3;

            var q = _quizzes[_current];
            if (index == q.Correct) { MessageBox.Show("正解!"); _score++; }
            else MessageBox.Show($"不正解… 正解は {q.Choices[q.Correct]}");

            btnChoice1.Enabled = btnChoice2.Enabled = btnChoice3.Enabled = btnChoice4.Enabled = false;
            _answered = true;
        }

        private void BtnNext_Click(object sender, EventArgs e)
        {
            if (!_answered) { MessageBox.Show("答えてから次へ進んでください"); return; }
            _current++;
            ShowQuestion();
        }
    }
}

6. 実行

F5で起動 → 出題 → 次へ → 最後にスコア表示。これで最短版は完成です。


パートB:チーム運用しやすい構成に発展(data フォルダへ)

「みんなで問題を増やす」運用に移るときは、CSVを data にまとめると管理が楽になります。

1. フォルダとファイルを移動

  • プロジェクト直下に data フォルダを作成
  • questions.csv を data/questions.csv へ移動
  • data/questions.csv の プロパティ → 出力ディレクトリにコピー → 常にコピーする

これでビルド後は bin/Debug/data/questions.csv が常に用意されます。

2. 読み込みパスを変更(Form1.cs)

private void Form1_Load(object sender, EventArgs e)
{
    LoadQuizzes(@"data\questions.csv"); // data配下を読む
    ...
}

3. チームでの分担(GitHub Desktopの最小運用)

  • ルールは 「Pull → 追記 → Commit → Push」
  • 競合回避のため、ID帯を割り当て(例:A=Q001〜Q003, B=Q004〜Q006, C=Q007〜Q009)
  • CSVの列はそのまま(ヘッダー+6列)でOK

追記例(チームで1人3問)

question,choice1,choice2,choice3,choice4,correctIndex
"…", "…","…","…","…",1
(Aさんの3問)
(Bさんの3問)
(Cさんの3問)

カンマを含むセルは “ダブルクォート" で囲みます。

correctIndex は 0〜3(choice1が0)。


よくある質問(FAQ)

Q. dataフォルダの話は基本コードに入っていましたか?

A. いいえ。基本版は LoadQuizzes(“questions.csv") で直下を読むだけです。

チーム運用に上げるタイミングで data\questions.csv に切り替えます。

Q. 「出力ディレクトリにコピー」は必要ですか?

A. はい。常にコピー を推奨(初心者はこれが最も安全)。ビルド後の実行フォルダにCSVが展開されるため、実行時に確実に見つかります。

Q. Excelで保存すると文字化けします

A. 文字コードを UTF-8 にしてください。うまくいかない場合はメモ帳(Windows標準の「UTF-8」)で保存。


まとめ

  • まずは 直下のCSV+最短コード で完成させる(パートA)
  • 慣れたら data/ 配下に移行し、プロパティで 常にコピー、コードの読み込みパスを変更(パートB)
  • チーム運用は Pull → 追記 → Commit → Push と ID帯の分担でシンプルに回せます

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

C#,チーム開発

Posted by hidepon