C# WinForms入門|ボタン・ListBox・合計で作る「簡易レジ」(Form1)
題材:にんじん・だいこんの“簡易レジ”
このチュートリアルでは、あなたのイラストどおりの挙動を持つ WinForms アプリ(Form1)を作ります。
左のボタンを押すと右上の枠(購入リスト)に1行追加され、下に合計が更新されます。
1. 新規プロジェクトを作る
- Visual Studio →「新しいプロジェクトの作成」
- テンプレート:Windows フォーム アプリ(.NET または .NET Framework どちらでも可)
- プロジェクト名:SimpleCashier(任意)
- 既定のフォーム名は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. 動作確認
- 実行(F5)
- 「にんじん 100」「だいこん 200」を何度か押す
- 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 を信頼し、表示文言は自由に整形できます。
注意点・小さな改善
- スペースの種類や重複スペース日本語の全角スペースや連続スペースに強くするなら:
var parts = b.Text.Split(new[] { ' ', ' ' }, StringSplitOptions.RemoveEmptyEntries);
string name = parts.Length > 0 ? parts[0] : string.Empty;
- 価格は常に Tag を使う表示側の Text に「100円」など単位が付いても問題ありません(価格は Tag から取るため)。
- さらに堅牢にするなら、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 が完成です。
ディスカッション
コメント一覧
まだ、コメントがありません