C# WinForms入門|ボタン・ListBox・合計で作る「簡易レジ」(Form1)

2025年8月12日

題材:にんじん・だいこんの“簡易レジ”

このチュートリアルでは、あなたのイラストどおりの挙動を持つ WinForms アプリ(Form1)を作ります。

左のボタンを押すと右上の枠(購入リスト)に1行追加され、下に合計が更新されます。


1. 新規プロジェクトを作る

  1. Visual Studio →「新しいプロジェクトの作成」
  2. テンプレート:Windows フォーム アプリ(.NET または .NET Framework どちらでも可)
  3. プロジェクト名:SimpleCashier(任意)
  4. 既定のフォーム名はForm1のままにする(希望どおり)

2. 画面を配置する(デザイナ)

フォームに次のコントロールを置いて、名前(Name)と表示(Text)を設定します。

左側(商品ボタン)

  • Button(Name: btnCarrot, Text: にんじん 100)
  • Button(Name: btnDaikon, Text: だいこん 200)

配置のコツ:両ボタンを左に縦並び。Font は見やすいサイズ(例:20pt)。

Anchor を Top, Left にしておくとレイアウトが崩れにくいです。

右側(レシート表示と合計)

  • ListBox(Name: lstItems)……右上の枠に相当。Font を少し大きめに。
  • Label(Name: lblTotal, Text: 合計 0)……右下。AutoSize = true、Anchor = Bottom, Right にして右下固定。Font を大きめ(例:24pt)。

ひとまずこれで配置は完了。動きは次のコードで与えます。


3. 挙動を実装する(Form1.cs)

デザイナで btnCarrot と btnDaikon をダブルクリックして、クリックイベントを作成。

続いて Form1.cs を以下のように記述します。

using System;
using System.Windows.Forms;

namespace SimpleCashier
{
    public partial class Form1 : Form
    {
        // 価格は定数にしておくと変更が楽
        private const int PriceCarrot = 100;
        private const int PriceDaikon = 200;

        // 合計の保持
        private int _total = 0;

        public Form1()
        {
            InitializeComponent();
            UpdateTotalLabel(); // 初期表示
        }

        private void btnCarrot_Click(object sender, EventArgs e)
        {
            AddItem("にんじん", PriceCarrot);
        }

        private void btnDaikon_Click(object sender, EventArgs e)
        {
            AddItem("だいこん", PriceDaikon);
        }

        // 購入リストに1行追加し、合計を更新
        private void AddItem(string name, int price)
        {
            lstItems.Items.Add($"{name} {price}");
            _total += price;
            UpdateTotalLabel();
        }

        private void UpdateTotalLabel()
        {
            lblTotal.Text = $"合計 {_total}";
        }
    }
}

役割分担:

  • AddItem が「1行追加+合計更新」を1か所にまとめています。
  • 価格は定数化しておき、ボタン側は名前だけ渡すシンプルな形に。

4. 動作確認

  1. 実行(F5)
  2. 「にんじん 100」「だいこん 200」を何度か押す
  3. lstItems に押した回数ぶんの行が追加され、lblTotal が正しく加算されればOK

5. よくあるつまずき

  • イベントが発火しない:デザイナでダブルクリックして作られた *_Click が Form1.cs に存在するか確認。
  • 文字が小さい/窮屈:Font を大きめに、ListBox のサイズを広めに。
  • レイアウトが崩れる:Anchor を見直す(lstItems は Top, Bottom, Right、lblTotal は Bottom, Right など)。

6. 発展課題(任意)

  • クリア ボタン(lstItems.Items.Clear(); _total = 0;)
  • 取り消し(1行戻す):最後の行を削除し、その価格を差し引く
  • 小計列:ListView に切り替えて「商品」「価格」「時刻」など列表示
  • 商品追加の一般化:Button.Tag に価格を入れて共通ハンドラ1本で処理

記事の発展課題(クリア/取り消し/ListView化/共通ハンドラ)を1つのフォームで動く形にまとめました。課題の趣旨は記事どおりです。 

フォームに追加・設定するコントロール

  • Button:btnCarrot(Text: にんじん 100, Tag: 100
  • Button:btnDaikon(Text: だいこん 200, Tag: 200
  • Button:btnUndo(Text: 取り消し)
  • Button:btnClear(Text: クリア)
  • ListView:lvItems(後述の通り Details 表示に)
  • Label:lblTotal(Text: 合計 0)

商品ボタンは 共通ハンドラを使う前提です(Tag に価格を入れて処理)。 


Form1.cs(完成サンプル)

using System;
using System.Windows.Forms;

namespace SimpleCashier
{
    public partial class Form1 : Form
    {
        private int _total = 0;

        public Form1()
        {
            InitializeComponent();

            // ListView を列表示に
            lvItems.View = View.Details;
            lvItems.FullRowSelect = true;
            lvItems.GridLines = true;
            lvItems.Columns.Clear();
            lvItems.Columns.Add("商品", 120);
            lvItems.Columns.Add("価格", 80, HorizontalAlignment.Right);
            lvItems.Columns.Add("時刻", 90);

            UpdateTotalLabel();

            // 商品ボタンの共通ハンドラを割り当て(デザイナで設定済みなら不要)
            btnCarrot.Click += ProductButton_Click;
            btnDaikon.Click += ProductButton_Click;

            // クリア/取り消し
            btnClear.Click += btnClear_Click;
            btnUndo.Click += btnUndo_Click;
        }

        // 参考:ボタン共通ハンドラ(Tag に価格、Text の先頭を商品名として扱う)
        private void ProductButton_Click(object sender, EventArgs e)
        {
            if (sender is Button b && int.TryParse(b.Tag?.ToString(), out int price))
            {
                string name = b.Text.Split(' ')[0]; // "にんじん 100" → "にんじん"
                AddItem(name, price);
            }
        }

        private void AddItem(string name, int price)
        {
            var item = new ListViewItem(name);
            item.SubItems.Add(price.ToString());
            item.SubItems.Add(DateTime.Now.ToString("HH:mm:ss"));
            lvItems.Items.Add(item);

            _total += price;
            UpdateTotalLabel();
        }

        private void UpdateTotalLabel()
        {
            lblTotal.Text = $"合計 {_total}";
        }

        // 取り消し(最後の1行を削除し、その価格を差し引く)
        private void btnUndo_Click(object sender, EventArgs e)
        {
            if (lvItems.Items.Count == 0) return;

            var last = lvItems.Items[lvItems.Items.Count - 1];
            if (int.TryParse(last.SubItems[1].Text, out int price))
            {
                _total -= price;
            }
            lvItems.Items.RemoveAt(lvItems.Items.Count - 1);
            UpdateTotalLabel();
        }

        // クリア
        private void btnClear_Click(object sender, EventArgs e)
        {
            lvItems.Items.Clear();
            _total = 0;
            UpdateTotalLabel();
        }
    }
}

メモ

  • ListView への置き換えで「商品/価格/時刻」の列を表示(小計列のイメージ)。 
  • 取り消しは ListView の最後の行から価格を読み取り、合計から減算して削除。 
  • クリアは項目全消去+合計リセット。 
  • 共通ハンドラは記事の参考コードの考え方と同じです。 

7. 参考:ボタン共通ハンドラ(少し進んだ書き方)

ボタンの Tag に価格、Text に「にんじん 100」などと書いておき、クリックを1本で拾います。

private void ProductButton_Click(object sender, EventArgs e)
{
    if (sender is Button b && int.TryParse(b.Tag?.ToString(), out int price))
    {
        // ボタンの頭の単語を商品名とみなす例("にんじん 100" -> "にんじん")
        string name = b.Text.Split(' ')[0];
        AddItem(name, price);
    }
}

これは「複数の“商品ボタン”に同じクリックハンドラを割り当てて、押されたボタンから商品名と価格を取り出し、AddItemに渡す」ためのコードです。行ごとにポイントを解説します。

何をしているコードか(概要)

  • sender で「どのボタンが押されたか」を特定
  • そのボタンの Tag に入れておいた価格を int に変換
  • ボタンの Text の“先頭の単語”を商品名とみなす
  • AddItem(name, price) を呼んでレシート追加・合計更新などを行う

行ごとの解説

private void ProductButton_Click(object sender, EventArgs e)
  • クリックイベント用のメソッド。sender にはイベントを発生させたコントロール(ここでは押された Button)が入ります。
if (sender is Button b && int.TryParse(b.Tag?.ToString(), out int price))
  • パターンマッチングで sender が Button なら b に受け取る、という書き方。
  • b.Tag は任意オブジェクトを入れられるメタデータ用プロパティ。ここでは「価格」を入れておく想定。
  • ?.ToString() は Tag が null でも落ちないようにするための null 条件演算子。
  • int.TryParse(…, out int price) で文字列を int に安全に変換。失敗したら if が false になり処理しません(ガード節)。
// ボタンの頭の単語を商品名とみなす例("にんじん 100" -> "にんじん")
string name = b.Text.Split(' ')[0];
  • b.Text(ボタン表示文字列)を半角スペース ' ' で分割し、先頭要素を商品名とみなしています。
  • 例:"にんじん 100″ → [“にんじん","100″] → 先頭は “にんじん"。
AddItem(name, price);
  • 取得した name と price を使ってレシート行の追加や合計の更新を行う想定の自前メソッド。

使い方(初期化例)

btnCarrot.Text = "にんじん 100";
btnCarrot.Tag  = 100;                 // 価格をTagに保持(ロジック用データ)
btnCarrot.Click += ProductButton_Click;

btnDaikon.Text = "だいこん 200";
btnDaikon.Tag  = 200;
btnDaikon.Click += ProductButton_Click;
  • 表示(Text)とロジック用データ(Tag)を分離しているのがポイント。価格は Tag を信頼し、表示文言は自由に整形できます。

注意点・小さな改善

  1. スペースの種類や重複スペース日本語の全角スペースや連続スペースに強くするなら:
var parts = b.Text.Split(new[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
string name = parts.Length > 0 ? parts[0] : string.Empty;
  1. 価格は常に Tag を使う表示側の Text に「100円」など単位が付いても問題ありません(価格は Tag から取るため)。
  2. さらに堅牢にするなら、Tag に“商品オブジェクト”を入れる文字列分割に依存せず、表示と完全に分離できます。
// 準備時
btnCarrot.Tag = new Product { Name = "にんじん", Price = 100 };

// ハンドラ
if (sender is Button b && b.Tag is Product p)
{
    AddItem(p.Name, p.Price);
}

この書き方は「ボタンを増やしてもハンドラを使い回せる」「表示文言の変更にロジックが影響されにくい」というメリットがあります。“Tag にデータ、Text は見た目”という分離の意図を強調すると理解が進みます。

デザイナで、各商品ボタンの Tag に 100 / 200 を設定し、Click イベントをどちらも ProductButton_Click に割り当てます。


これで、イラストどおりの挙動をする「簡易レジ」WinForms が完成です。

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