Skip to content

付録J Windows フォーム社員管理アプリ 拡張課題集 — 自習日用

この付録は、第 23〜25 章で作った Windows フォーム社員管理アプリ(EmployeeApp) を一通り完成させた後の、半日〜1 日の拡張デー(自習日) に取り組むための課題集です。

第 25 章で「CRUD が一通りそろった最小完成形」までは作りました。 この付録では、その最小完成形に 機能を足し、操作感を磨いて、実際にリリースされる業務アプリに一歩近づける ことを狙います。

ねらいは大きく 2 つです。

  1. やり残した発展課題を回収する — 第 23〜25 章の発展課題(列カスタマイズ・部署一覧・給与レンジ検索など)にまだ手を付けていない人が、ここであらためて取り組みます。
  2. 「動くアプリ」を「使えるアプリ」にする — ウィンドウのリサイズ対応、入力チェック、キーボード操作、ステータスバーなど、現場のアプリが当たり前に備えている 操作改善 を足していきます。

今回は「リリース演習」として取り組みます

ただ 1 日かけてダラダラ作るのではなく、業務でソフトをリリースする現場をイメージ して進めます。あらかじめ決められた 提出時刻 = リリース時刻 に向けて、自分が作れる範囲を見積もり、納期を守って、中途半端でない動く状態 で提出します。詳しいルールは J-1 にまとめています。

観点本編(第 23〜25 章)本付録 J付録 F(走り切った人向け)
位置づけ章ごとの必修第 25 章直後のリリース演習第 0〜31 章を走り切った後
提出あり(自走ハンズオン)あり(リリース演習:納期厳守)任意
完全なコード掲載あり(本文に実装ステップ)なし(仕様・ヒント・骨格コードのみ)なし
下地これから作る第 25 章の完成アプリを再利用第 25 章の完成アプリを再利用
難度段階的回収(易)→ 機能追加 → 操作改善 → 挑戦上級寄り

本付録の最後(レベル 4)は、付録 F-3(トランザクション付き一括給与・CSV エクスポート・部署マスタ管理)へ誘導します。これらは本付録では作り直しません。J = 第 25 章直後の磨き上げ、F-3 = 全課程を走り切った後の総仕上げ と役割分担しています。


J-1 取り組み方(リリース演習のルール)

Section titled “J-1 取り組み方(リリース演習のルール)”

この付録は リリース演習 です。次の 5 つのルールで進めます。

#ルール意味
1提出時刻 = リリース時刻講師が決めた提出時刻を「製品をリリースする時刻」とみなす。その時刻のコミット(または提出物)が成果物
2先に計画を立てるいきなり作り始めない。まず課題を分析し、リリース時刻までに自分がどこまで作れるか 目標を決める
3納期厳守リリース時刻は動かさない。業務と同じく「間に合わせる」ために、作る範囲のほうを調整する
4中途半端な状態でリリースしないリリースに含める機能は 最後まで動く こと。間に合わない機能は無理に半端な形で入れず、外す(コメントアウト or 次回送り)
5コメントをしっかり入れる足した機能の難所に「なぜ」コメントを残す。リリースする以上、後で読む人(自分・チーム)に説明できる状態にする

「動く範囲を確実にリリースする」のが現場の作法

全部やりきれなくても構いません。大事なのは、自分が「今日リリースする」と決めた範囲を、中途半端でない動く状態で納期どおり出す ことです。間に合わなそうなら、機能を欲張らずに減らしてでも「動くもの」を出します。これは実際のソフト開発で毎回行われている判断(スコープを切る)そのものです。

計画と成果物は「個人」、納期と支え合いは「チーム」

Section titled “計画と成果物は「個人」、納期と支え合いは「チーム」”

この演習は チームで進めますが、目標(作業範囲)も成果物も個人単位 です。チームで 1 つのアプリを分担して作るのではなく、一人ひとりが自分のアプリを持ち、同じ納期に向かって互いに支え合います。これは現場の「リリース日はチーム共通、担当は個人持ち」という形そのものです。

何を単位具体
リリース計画(目標=作業範囲)個人リリース計画.txt は各自が書く。自分が今日リリースする機能を自分で見積もる
制作個人(相談はチーム)自分の EmployeeApp を育てる。詰まったら技術部長やチームに相談
成果物・達成度・評価個人各自のリポジトリの、リリース時刻のコミット。達成度は個人ごとに残る
リリース時刻(納期)チーム共通全員同じ提出時刻。タイムキーパーがフィーチャーフリーズを号令
相談・助け合いチーム技術部長を中心に教え合う
リリース報告チームの場各自が自分のリリース内容を順に報告し合う

チームの誰かに助けてもらってもOKです。最終的に 自分のリポジトリに、動くコードと「なぜ」コメントを残せたか が、あなたの達成度です。

段階やること
① 分析・計画リリース時刻を確認 → 課題(レベル 1〜2.5)を眺めて分析 → 今日リリースする範囲を決める → 後述の「リリース計画」に書いてコミット
② 制作計画した範囲を作る。手を広げすぎない(計画外に目移りしない)
③ リリース前チェック(フィーチャーフリーズ)リリース時刻の少し前で手を止め、中途半端な機能を外し、最後まで動く状態に整える。コメントも見直す
④ リリース(提出)納期どおりにコミット & プッシュ。これが「リリース」
⑤ リリース報告(ミニ発表)チームの場で、各自が自分の 「何をリリースしたか」「計画と実績」「詰まった点」を順に報告する

リリース計画を立てる(各自が作業前にコミット)

Section titled “リリース計画を立てる(各自が作業前にコミット)”

作業を始める前に、一人ひとりが 自分のソリューションフォルダ(KadaiWinFormsApp)に リリース計画.txt を作り、次を書いて 最初に(自分のリポジトリへ)コミット します。

氏名: (自分の名前)
リリース時刻: HH:MM
今日リリースする機能:
- (例)レベル1 J-3-3 給与レンジ検索
- (例)レベル2.5 J-5-1 リサイズ対応、J-5-4 ステータスバー
リリースしない(今回見送る):
- (例)レベル2 J-4-2 メール重複ハンドリング

リリース時刻になったら、自分の計画と実際のリリース内容を見比べます。計画どおりでも、減らしても、増やせてもOK です。「自分で見積もって・納期に間に合わせる」経験そのものが狙いです。

チームで進める(支え合い・納期・報告の単位)

Section titled “チームで進める(支え合い・納期・報告の単位)”

チームは 1 つのアプリを分担する単位ではなく、同じ納期に向かって支え合い、進捗を確認し合う単位 です。各自が自分のアプリを作りつつ、役割分担(リーダー / 技術部長 / タイムキーパー = 納期=リリース時刻の管理)で進めます。役割の考え方は 第 17 章「ここからはチームで進める」 を参照してください。

  • 技術部長 を中心に、詰まった人を教え合う(助けてもらってよい。最後に自分のリポジトリへ残せば達成)
  • タイムキーパー は ③ フィーチャーフリーズに入る時刻をチームに号令する
  • リリース後、各自が自分のリリース内容を順に報告(ミニ発表の進め方は 第 23 章 参照:デモ + ペア確認 1 問 + 一言)

本付録には 完全なコードを載せません。掲載しているのは次の 3 つだけです。

  • 仕様(何を作るか)
  • ヒント(詰まりやすい箇所への指針)
  • 骨格コード(メソッドの形・SQL の雛形・画面に足す項目)

第 23〜25 章のように「実装ステップどおりに書けば動く」状態にはしていません。自分で組み立てる練習 の場だと割り切ってください。第 25 章の完成アプリが手元にあるので、そのコードを土台に「どこに何を足すか」を自分で考えるのがこの付録の主眼です。

本付録でも、足した機能の 難所には「なぜそう書くのか」を前行コメントで残します(第 7 章コラム参照)。言い換えコメント(コードをそのまま日本語にしただけ)は避け、「何のため / なぜこの書き方か」を自分の言葉で書いてください。これは提出物の評価対象です。

詰まったら、次の 3 点をメモしてから講師・技術部長に相談してください。

  1. 何をしようとしているか(課題と狙い)
  2. どこまで動いているか(画面ショット or 書いたコード)
  3. 何が起きているか(エラーメッセージ、期待した動きと実際の差)

本付録は、第 25 章の到達点を 下地として再利用 します。始める前に次を確認してください。

必要なもの用途
KadaiWinFormsApp ソリューション / EmployeeApp(Win CRUD 完成形)全課題の出発点第 25 章
TrainingDB(SQLServer)全 DB 系課題第 16 章
第 17 章で接続できた状態全 DB 系課題第 17 章

演習用の拡張データを入れる(SSMS で実行)

Section titled “演習用の拡張データを入れる(SSMS で実行)”

これまでの employees は 10 件でした。これだと集計・並べ替え・一覧の見やすさを試しても変化が分かりにくいので、社員を約 40 名・部署を 3 件 追加 します。SSMS で TrainingDB を開き、新しいクエリに次を貼り付けて 一度だけ実行 してください(F5)。

先に部署 → 後に社員 の順で実行します(社員が部署を外部キーで参照するため)。社員の employee_idIDENTITY で 1011 から自動採番されるので、書きません。

USE TrainingDB;
GO
-- 部署を 3 件追加(department_id は手動採番)
INSERT INTO departments (department_id, department_name, manager_id) VALUES
(6, N'人事', NULL),
(7, N'経理', NULL),
(8, N'情報システム', NULL);
-- 社員を 40 名追加(employee_id は IDENTITY 採番のため指定しない)
INSERT INTO employees (last_name, first_name, email, hire_date, salary, department_id) VALUES
(N'鈴木', N'一郎', 'suzuki.ichiro@example.com', '2005-04-01', 420000, 2),
(N'高橋', N'美咲', 'takahashi.misaki@example.com', '2010-04-01', 510000, 3),
(N'渡辺', N'健太', 'watanabe.kenta@example.com', '2018-10-01', 380000, 1),
(N'伊藤', N'さくら', 'ito.sakura@example.com', '2012-04-01', 470000, 4),
(N'中村', N'翔太', 'nakamura.shota@example.com', '2008-04-01', 650000, 3),
(N'小林', N'由美', 'kobayashi.yumi@example.com', '2015-04-01', 540000, 6),
(N'加藤', N'大樹', 'kato.daiki@example.com', '2019-04-01', 360000, 5),
(N'吉田', N'直樹', 'yoshida.naoki@example.com', '2006-04-01', 720000, 7),
(N'山本', N'恵子', 'yamamoto.keiko@example.com', '2013-09-01', 430000, 2),
(N'佐藤', N'', 'sato.ken@example.com', '2003-04-01', 880000, 8),
(N'松本', N'', 'matsumoto.ryo@example.com', '2011-04-01', 590000, 3),
(N'井上', N'麻衣', 'inoue.mai@example.com', '2016-04-01', 460000, 4),
(N'木村', N'拓也', 'kimura.takuya@example.com', '2017-04-01', 400000, 1),
(N'', N'美穂', 'hayashi.miho@example.com', '2014-04-01', 505000, 6),
(N'清水', N'', 'shimizu.takashi@example.com', '2020-04-01', 345000, 5),
(N'山崎', N'香織', 'yamazaki.kaori@example.com', '2009-04-01', 690000, 7),
(N'', N'健司', 'mori.kenji@example.com', '2002-04-01', 935000, 8),
(N'池田', N'', 'ikeda.aya@example.com', '2021-04-01', 410000, 2),
(N'橋本', N'', 'hashimoto.ryo@example.com', '2007-04-01', 620000, 3),
(N'阿部', N'千夏', 'abe.chinatsu@example.com', '2013-04-01', 480000, 4),
(N'石川', N'雄大', 'ishikawa.yudai@example.com', '2019-10-01', 390000, 1),
(N'山下', N'智子', 'yamashita.tomoko@example.com', '2012-04-01', 560000, 6),
(N'中島', N'', 'nakajima.hiroshi@example.com', '2022-04-01', 350000, 5),
(N'前田', N'詩織', 'maeda.shiori@example.com', '2010-04-01', 705000, 7),
(N'藤田', N'健太郎', 'fujita.kentaro@example.com', '2004-04-01', 910000, 8),
(N'後藤', N'真央', 'goto.mao@example.com', '2018-04-01', 425000, 2),
(N'岡田', N'', 'okada.sho@example.com', '2011-09-01', 605000, 3),
(N'長谷川', N'', 'hasegawa.ai@example.com', '2015-04-01', 455000, 4),
(N'村上', N'大輔', 'murakami.daisuke@example.com', '2020-04-01', 395000, 1),
(N'近藤', N'由香', 'kondo.yuka@example.com', '2013-04-01', 530000, 6),
(N'石井', N'', 'ishii.makoto@example.com', '2023-04-01', 340000, 5),
(N'斉藤', N'', 'saito.manabu@example.com', '2008-09-01', 715000, 7),
(N'坂本', N'美樹', 'sakamoto.miki@example.com', '2005-04-01', 925000, 8),
(N'遠藤', N'', 'endo.ken@example.com', '2019-04-01', 415000, 2),
(N'青木', N'涼太', 'aoki.ryota@example.com', '2009-04-01', 640000, 3),
(N'藤井', N'', 'fujii.kaori@example.com', '2016-09-01', 465000, 4),
(N'西村', N'健一', 'nishimura.kenichi@example.com', '2017-04-01', 405000, 1),
(N'福田', N'真理', 'fukuda.mari@example.com', '2014-09-01', 545000, 6),
(N'太田', N'', 'ota.tsuyoshi@example.com', '2021-04-01', 355000, 5),
(N'三浦', N'結衣', 'miura.yui@example.com', '2007-04-01', 700000, 7);
GO
-- 確認(社員 50 件・部署 8 件になっていれば成功)
SELECT COUNT(*) AS 社員数 FROM employees;
SELECT COUNT(*) AS 部署数 FROM departments;

このスクリプトは一度だけ実行します。 もう一度実行すると、メールアドレスの UNIQUE 制約に引っかかってエラーで止まります(重複投入を防ぐ安全装置です)。エラーが出たら「もう入っている」というサインなので、そのまま進めて構いません。

社員管理アプリは元々データを追加・削除するものなので、これまでの章より件数が増えても問題ありません。安心して進めてください。

手元の EmployeeApp を育てる(新しいプロジェクトは作らない)

Section titled “手元の EmployeeApp を育てる(新しいプロジェクトは作らない)”

本付録では 新しいプロジェクトを作りません。第 23〜25 章から育ててきた EmployeeApp(KadaiWinFormsApp ソリューション)に、そのまま機能を足していきます。最小 CRUD アプリを 1 本のまま「リリース品質」まで育てていくのが狙いです。

EmployeeApp はすでに Nullable disableMicrosoft.Data.SqlClient 追加済み なので、追加の準備は要りません。Form1.cs / EmployeeEditForm.cs / EmployeeRepository.cs に手を入れていきます。

第 23〜25 章の発展課題を回収する人へ

元の章では Ext_ColumnCustom のような 別プロジェクトを作る 案内になっていますが、本付録では実際の進め方どおり、すべて手元の EmployeeApp に直接足してOK です。プロジェクト名は気にせず、機能を追加していってください。

「壊すのが心配」への備えはコミットで

別プロジェクトに分けなくても、機能を足す前に一度コミット しておけば、いつでも前の状態に戻せます。第 25 章の CRUD 完成時点もコミット履歴に残っているので、安心して 1 本のアプリを育ててください。

フォーム操作の早見表(置く・名前・イベント)

Section titled “フォーム操作の早見表(置く・名前・イベント)”

レベル 2 以降では、ボタンやラベル以外の 新しい部品を自分で画面に足す 場面が増えます。第 23〜25 章でやった操作の復習ですが、迷いやすいので 3 点だけまとめておきます。各課題のヒントのコードは、ここで部品を置いて名前を付けた前提 で書いてあります。

やること手順
① 部品を置くツールボックスから画面へドラッグする(LabelComboBoxStatusStrip など)
② 名前を付ける置いた部品を選び、プロパティ窓の (Name) をヒントのコードと同じ名前にする(例:labelSummarycomboBoxSort)。これをしないとコードから部品を呼べません
③ イベントを紐付けるダブルクリック = その部品の既定イベント(ボタンなら Click)。それ以外のイベント(SelectedIndexChangedKeyDownFormClosing など)は、プロパティ窓の 稲妻アイコン ⚡(イベント一覧) を開き、目的のイベント名をダブルクリックして空のハンドラーを作る

「画面に出ない部品」はフォームの下に並ぶ

ErrorProviderStatusStripContextMenuStrip などは、画面に直接乗るのではなく、デザイナー下部の コンポーネント トレイ(グレーの帯)に並びます。名前付け・プロパティ設定・イベント紐付けは、そこに並んだアイコンを選んでプロパティ窓から行います。

見積もりの目安(リリース計画づくり用)

Section titled “見積もりの目安(リリース計画づくり用)”

各課題の 難易度(★)所要時間の目安 です。①の計画(リリース範囲を決める) の見積もりに使ってください。難易度は ★☆☆ かんたん / ★★☆ ふつう / ★★★ 歯ごたえあり の 3 段階です。

★は「手間の量」ではなく「ロジックの絡み具合」で付けています

難易度は、コントロールを置く数や作業の多さではなく、検索条件・SQL・状態管理といったロジックがどれだけ絡むか で決めています。

  • 検索・絞り込み系(J-3-3 / J-3-4 など)は、画面は小さくても重い:入力チェック → 空欄の扱い → 条件分岐した SQL(パラメータ化)→ 再表示、と要素が連なります。特に J-3-3 給与レンジ(下限・上限の片方だけ指定にも対応)は、条件によって SQL が変わるので本付録でいちばん歯ごたえがあります。
  • 逆に、リサイズ・ステータスバー・見やすさ(J-5-1 / J-5-4 / J-5-5 など)は、項目が多くても軽い:ほとんどが Designer のプロパティ設定で、書くコードはごくわずかです。
課題難易度目安
〔L1 回収〕 J-3-1 列の表示カスタマイズ★☆☆20〜30 分
J-3-2 部署一覧画面(新しい画面を作る)★★☆40〜60 分
J-3-3 給与レンジで検索★★★45〜65 分
J-3-4 入社年で絞り込み★★☆35〜50 分
J-3-5 編集後も検索条件を保つ★★☆30〜45 分
J-3-6 給与の一括引き上げ★★☆35〜50 分
〔L2 機能〕 J-4-1 件数・給与の集計表示★★☆30〜45 分
J-4-2 メール重複をわかりやすく★★☆35〜50 分
J-4-3 一覧の並べ替え(ソート)★★☆35〜55 分
〔L2.5 UX〕 J-5-1 ウィンドウのリサイズ対応★☆☆20〜30 分
J-5-2 入力チェック強化(ErrorProvider)★★☆30〜45 分
J-5-3 キーボード操作★☆☆25〜40 分
J-5-4 ステータスバー★☆☆20〜35 分
J-5-5 一覧の見やすさ★☆☆15〜20 分
J-5-6 処理中フィードバック★☆☆15〜25 分
〔L3 仕上げ〕 J-6-2 前回状態の復元(設定の永続化)★★★50〜80 分
J-6-3 一覧の作り込み(色分け+右クリック)★★☆40〜60 分
J-6-1 固まらない化(BackgroundWorker)参考(読み物)
〔L4 挑戦・付録 F-3〕 F-3-1 部署一括の給与アップ(トランザクション)★★★40〜60 分
F-3-2 社員一覧の CSV エクスポート★★☆30〜45 分
F-3-3 部署マスタ管理画面(部署の追加・編集・削除)★★★60〜90 分

上の「目安」はあくまで見積もりの当たりで、フォーム操作に慣れていないうちは長めに見ておく のが安心です。特に ErrorProviderStatusStripContextMenuStrip など 初めて触るコンポーネント は、置く・名前を付ける・イベントを紐付ける手間が上乗せされます(早見表参照)。「思ったより 1 つに時間がかかる」前提で計画してください。

演習日は 1 日ですが、発表・確認の時間を除くと実作業はおよそ 5 時間 です。1 つあたり想定より時間がかかりやすいので、その中なら「レベル 1 の回収 + レベル 2・2.5 を合わせて 2〜4 個」あたりが現実的な目安です。ただし 個数を競うのではありません。自分で立てた計画を、中途半端でない動く状態で納期どおりリリースする ことが評価の中心です。手を広げて全部が半端になるより、2〜3 個を確実に動かしてリリース するほうが高評価です。レベル 3 のうち J-6-2・J-6-3(★★★/★★☆)は重いので入れるなら 1 つに絞り、J-6-1(BackgroundWorker)は実装せず読み物として目を通すだけ にします。

必須(リリースに必ず含めるもの)

Section titled “必須(リリースに必ず含めるもの)”
内容
必須リリース計画を立て、リリース計画.txt をコミットした上で、レベル 1 から、まだやっていない発展を 2 本以上(=リリースの最低ライン)を 動く状態 で含め、難所に「なぜ」コメントをつける
発展レベル 2・2.5 の機能追加、さらに余力があれば レベル 3 の仕上げ(J-6-2 / J-6-3 を EmployeeApp に積む。J-6-1 は実装せず読み物)。計画して動く状態でリリースした分を評価
挑戦レベル 4(付録 F-3)。全部こなした猛者向け

レベル 1:やり残した発展を回収する

Section titled “レベル 1:やり残した発展を回収する”

第 23〜25 章の発展課題をここに集約しました。詳しい仕様は元の章を参照 してください(ここでは要点だけ再掲します)。まだ手を付けていないものを優先して取り組みましょう。

J-3-1 列の表示をカスタマイズする

Section titled “J-3-1 列の表示をカスタマイズする”

DataGridView の列順・日本語ヘッダー・非表示列・3 桁区切り表示を整える。

  • 要点:列順を 社員番号 → 氏名 → 部署 → メール → 入社日 → 給与LastName/FirstName/DepartmentIdVisible = falseSalaryDefaultCellStyle.Format = "N0"
  • 詳しい仕様 → 第 23 章 課題 23-2

departments テーブルを表示する画面を作る(Department クラス + DepartmentRepository.GetAll())。

  • 要点:ManagerId が NULL の行は空欄になるよう IsDBNull を処理
  • 詳しい仕様 → 第 23 章 課題 23-3

給与の下限・上限を TextBox で受け取り、パラメータ化クエリで絞り込む。

  • 要点:decimal.TryParse で数値チェック、空欄は「指定なし」、SQL は salary >= @min AND salary <= @max
  • 詳しい仕様 → 第 24 章 課題 24-2

入社年(西暦 4 桁)で絞り込む。

  • 要点:YEAR(hire_date) = @year、空欄は「指定なし」、4 桁数字以外は警告
  • 詳しい仕様 → 第 24 章 課題 24-3

編集・削除・新規登録の後、検索条件を保ったまま一覧を再読み込みする。

  • 要点:検索条件が入っていれば SearchEmployees() を、空なら LoadEmployees() を呼ぶように分岐
  • 詳しい仕様 → 第 25 章 課題 25-2

選択した部署の全社員の給与を 1 万円上げるボタンを足す。

  • 要点:確認ダイアログ → UPDATE employees SET salary = salary + 10000 WHERE department_id = @id、影響行数を表示
  • 詳しい仕様 → 第 25 章 課題 25-3

ここまでが「やり残しの回収」です。1 つでも未着手のものがあれば、まずはそれを完成させましょう。すでに全部やった人は、レベル 2 へ進んでください。


レベル 2:機能を足す(EmployeeApp に追加)

Section titled “レベル 2:機能を足す(EmployeeApp に追加)”

ここからは、手元の EmployeeApp にそのまま機能を積み上げていきます。1 つ足すごとにビルドして動かし、壊れていないことを確認しながら進めてください(足す前にコミットしておくと安心です)。

J-4-1 件数・給与の集計を表示する

Section titled “J-4-1 件数・給与の集計を表示する”
  • 一覧の下に、現在表示している社員の「件数」「給与合計」「給与平均」 を表示するラベルを置く
  • 検索・絞り込みで表示が変わったら、集計も連動して更新される
  • 一覧が 0 件のときは「該当する社員はいません」と表示する
  • まず一覧の下に Label を 1 つ置き、(Name)labelSummary にします(早見表 ①②)。集計結果はこの labelSummary.Text に入れます。
  • 一覧表示に使っている List<Employee> をそのまま集計できます。第 12 章で学んだ LINQ が使えます。
// 表示中のリスト(LoadEmployees / SearchEmployees で取得したもの)を集計する
int count = employees.Count;
decimal total = employees.Sum(e => e.Salary);
decimal average = count == 0 ? 0 : employees.Average(e => e.Salary);
// labelSummary.Text = $"件数:{count} 合計:{total:N0} 平均:{average:N0}";
  • LoadEmployees()SearchEmployees() の両方で同じ集計を呼びたい」と気づいたら、集計を 1 つのメソッド(例 UpdateSummary(List<Employee> list))に切り出す と重複が消えます。これは Repository ではなくフォーム側のヘルパーです。
  • 0 件のときに Average を呼ぶと例外になります。count == 0 の分岐を先に書いてください(条件演算子は第 23 章で学習済み)。
  • 起動直後(全件)の件数・合計・平均が正しい
  • 検索で件数が減ると集計も変わる
  • 0 件のときに落ちない

J-4-2 メールアドレスの重複をわかりやすく知らせる

Section titled “J-4-2 メールアドレスの重複をわかりやすく知らせる”
  • 新規登録・編集で、すでに使われているメールアドレス を保存しようとすると、SQLServer の UNIQUE 制約に引っかかってエラーになる(email 列は第 16 章で UNIQUE を付けた)
  • そのまま放置すると「例外のメッセージがそのまま出て分かりにくい」ので、「このメールアドレスは既に登録されています」とやさしく知らせる ように直す
  • INSERT / UPDATE を実行している箇所(EmployeeRepository または保存を呼ぶフォーム側)で SqlException を捕まえます。
  • 一意制約違反かどうかは、SqlException.Number2627(制約違反)または 2601(一意インデックス違反) かで判定できます。型の判定には第 13・24 章で学んだ is 演算子が使えます。
catch (SqlException ex) when (ex.Number == 2627 || ex.Number == 2601)
{
MessageBox.Show("このメールアドレスは既に登録されています。",
"登録できません", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
catch (SqlException ex)
{
// それ以外の DB エラーは従来どおり ShowError などで表示
}
  • 「保存できなかったら編集画面を閉じない」ようにするのがポイントです。保存に失敗したら DialogResult.None のままにして、ユーザーがメールを直せるようにします(第 25 章の入力チェックと同じ考え方)。
  • 既存のメール(例:yamada.jiro@example.com)で登録するとやさしいメッセージが出る
  • そのとき編集画面が閉じず、入力を直せる
  • それ以外の DB エラーは従来どおりのエラー表示になる

J-4-3 一覧を並べ替える(ソート)

Section titled “J-4-3 一覧を並べ替える(ソート)”
  • 一覧を 並べ替え できるようにする(例:給与の高い順 / 入社日の新しい順 / 社員番号順)
  • 画面に「並び順」の ComboBox を 1 つ追加し、選ぶと一覧がその順に並び替わる
  • 検索で絞り込んだ結果に対しても、並べ替えが効く

拡張データ(J-2)で社員が 50 件あるので、並べ替えの効果がはっきり分かります。

  • 表示中の List<Employee>LINQ で並べ替えて、もう一度 DataSource に入れ直す のが簡単です(第 12 章の OrderBy / OrderByDescending)。
// 「並び順」ComboBox の選択に合わせて、表示中の employees を並べ替える例
List<Employee> sorted;
if (comboBoxSort.SelectedIndex == 0)
{
sorted = employees.OrderByDescending(e => e.Salary).ToList(); // 給与の高い順
}
else if (comboBoxSort.SelectedIndex == 1)
{
sorted = employees.OrderByDescending(e => e.HireDate).ToList(); // 入社日の新しい順
}
else
{
sorted = employees.OrderBy(e => e.EmployeeId).ToList(); // 社員番号順
}
dataGridViewEmployees.DataSource = sorted;
  • 並べ替えた結果を「今表示しているリスト」として持っておくと、集計(J-4-1)とも食い違いません。
  • ComboBox を 1 つ置き、(Name)comboBoxSort に(早見表 ①②)。Designer の Items に「給与の高い順 / 入社日の新しい順 / 社員番号順」を入れ、DropDownStyle = DropDownList にしておきます。
  • 並べ替えを 発火させる には、comboBoxSort を選んで 稲妻アイコン ⚡ → SelectedIndexChanged(早見表 ③)を紐付け、その中に上のコードを書きます。コードを書いただけでは呼ばれないので、この紐付けが要です。

列ヘッダーのクリックで並べ替えたい人へ(任意)

DataGridView まかせの 自動ソート は、List<T> をそのまま使う本アプリでは効かず、SortableBindingList の自作(ジェネリクス+リフレクション)が必要で 泥沼になりやすい ため、研修では扱いません。どうしても列ヘッダーで並べたい人だけ、ColumnHeaderMouseClick イベント(早見表 ③)で「押された列」を見て上と同じ LINQ 並べ替えを呼べば、ComboBox 方式とほぼ同じ手間で実現できます。基本は ComboBox 方式で完成とします。

  • 「並び順」を変えると一覧の順番が変わる
  • 給与の高い順で、先頭が最高給与の社員になる
  • 検索で絞り込んだあとでも並べ替えが効く

ここは「アプリを使いやすくする」磨きの層です。新しい C# 文法はほとんど出てきません。Designer のプロパティ設定が中心 で、1 つ足すごとにアプリが「ちゃんとしたソフト」に近づきます。これも引き続き EmployeeApp に足していきます。

ここで使う ErrorProvider / StatusStrip / ToolTip はいずれも Windows フォーム標準のコンポーネント です(NuGet 不要、ツールボックスにあります)。

J-5-1 ウィンドウのリサイズに対応する

Section titled “J-5-1 ウィンドウのリサイズに対応する”
  • ウィンドウを広げたり縮めたりしても、レイアウトが破綻しないようにする
  • DataGridView はウィンドウいっぱいに広がり、ボタンは端に張り付く
  • 小さくしすぎて操作できなくならないよう、最小サイズ を決める
  • 起動時に画面中央に、ちょうどよい初期サイズで開く
  • 各コントロールの Anchor を見直します(第 23〜25 章でも一部設定済み)。DataGridViewTop, Bottom, Left, Right、右上のボタンは Top, Right、下のボタンは Bottom, Right
  • フォームの MinimumSize を設定すると、それより小さくできなくなります。
  • フォームの StartPosition = CenterScreenSize で初期サイズを指定します。
  • 横に広げると一覧も広がる
  • 縦に広げると行がたくさん見える
  • 小さくしてもボタンが画面外に消えない(MinimumSize)

J-5-2 入力チェックを強化する(ErrorProvider)

Section titled “J-5-2 入力チェックを強化する(ErrorProvider)”
  • 編集フォームの保存時の入力チェックを強化する
  • 必須項目(姓・名・メール)が空なら保存できない
  • メールに「@」が含まれていなければ警告
  • 給与は 正の数 でなければ警告
  • エラーのとき、どの項目が悪いのか が分かるように、項目の横に赤いマークを出す(ErrorProvider)
  • 第 25 章の ValidateInput() を拡張します。string.IsNullOrWhiteSpace(第 9 章)で空チェック、decimal.TryParse で数値チェックができます。
  • ErrorProvider はツールボックスから置くと、画面ではなく コンポーネント トレイ(フォーム下)に入ります(早見表)。既定名 errorProvider1 のまま使えます。項目ごとにエラーを set / clear します。
// 例:メール欄が不正なとき
if (!textBoxEmail.Text.Contains("@"))
{
errorProvider1.SetError(textBoxEmail, "メールアドレスの形式が正しくありません");
isValid = false;
}
else
{
errorProvider1.SetError(textBoxEmail, ""); // 正常なら消す
}
  • メールの厳密な形式チェック(正規表現など)までは踏み込まなくて構いません。「空でない」「@ が含まれる」程度で十分です。
  • 姓・名・メールが空だと保存できない
  • 不正な項目の横に赤いマークが出る
  • 直すと赤いマークが消える
  • 給与にマイナスや文字を入れると弾かれる

J-5-3 キーボードで操作できるようにする

Section titled “J-5-3 キーボードで操作できるようにする”
  • Tab キーで、入力欄やボタンを上から下へ自然な順に移動できる(タブオーダーを整える)
  • 検索欄で Enter キーを押したら検索 が走る
  • Esc キーでフォームを閉じる(編集画面など)
  • F5 キーで一覧を再読み込み
  • ボタンに アクセスキー(Alt + 文字)を付ける
  • タブオーダー は、各コントロールの TabIndex を小さい番号から順に振ると、その順に Tab で移動します。Designer のメニュー 「表示 → タブ オーダー」 を開くと、画面上のコントロールを クリックした順に番号が振られる ので、これがいちばん簡単です(検索キーワード → 部署 → 検索ボタン → … のように、人が入力する自然な流れで振ります)。
    • ラベルなど入力を受けないコントロールは飛ばしたいので、TabStop = false にしておくと Tab で止まりません。
    • 編集ダイアログ(EmployeeEditForm)でも、姓 → 名 → メール → 入社日 → 給与 → 部署 → 保存、の順に整えると一気に入力しやすくなります。
  • フォームの AcceptButton に検索ボタンを、CancelButton に閉じるボタンを設定すると、Enter / Esc が効くようになります。
  • F5 は、フォームの KeyPreview = true にしてから、フォームの KeyDown イベント(早見表 ③:稲妻アイコンから紐付け)で e.KeyCode == Keys.F5 を拾い、一覧の再読み込みを呼びます。
  • アクセスキーは、ボタンの Text& を入れます(例:検索(&S)Alt + S)。
  • Tab キーで入力欄・ボタンが自然な順に移動する(変な順に飛ばない)
  • 検索欄で Enter を押すと検索される
  • 編集画面で Esc を押すと閉じる
  • F5 で一覧が更新される

  • ウィンドウ下部に ステータスバー(StatusStrip) を置く
  • 「表示件数」「接続先サーバ」「最終更新時刻」を表示する
  • 一覧を読み込むたびに、件数と時刻が更新される
  • ツールボックスから StatusStrip を置くと、画面下部に帯が出ます(本体はコンポーネント トレイにも入ります)。帯の上の をクリック → StatusLabel を選ぶと、表示用ラベル(ToolStripStatusLabel)を 1 つずつ足せます。
  • 足した各ラベルの (Name)statusLabelCount などにしておくと、コードから更新できます(早見表 ②)。
  • 件数は J-4-1 の集計と連動させてもよいです(statusLabelCount.Text = $"{count} 件";)。
  • 時刻は DateTime.Now.ToString("HH:mm:ss") で十分です。
  • 接続先サーバは固定文字列(localhost\SQLEXPRESS など)でも構いません。
  • 下部にステータスバーが表示される
  • 読み込むたびに件数・時刻が更新される

  • DataGridView を業務画面らしく整える
  • 1 行おきに薄く色を付ける(交互の行の色)
  • 行をクリックしたら 行全体 が選択される
  • セルを直接編集できないようにする(ReadOnly)
  • 左端の行ヘッダー(▶ が出るグレーの列)を消す
  • すべて DataGridView のプロパティで設定できます。
やりたいことプロパティ
交互の行の色AlternatingRowsDefaultCellStyle.BackColor
行全体を選択SelectionMode = FullRowSelect
編集禁止ReadOnly = true
行ヘッダーを消すRowHeadersVisible = false
  • 1 行おきに色が変わって見やすい
  • 行をクリックすると行全体が選択される
  • セルを直接書き換えられない

J-5-6 処理中だと分かるようにする

Section titled “J-5-6 処理中だと分かるようにする”
  • DB から読み込んでいる間など、処理中はマウスカーソルを砂時計(待機) にする
  • 処理が終わったら元に戻す
  • 研修の TrainingDB は小さく一瞬で終わってしまうので、わざと数秒の待機(疑似ウェイト) を入れて、砂時計が出ることを目で確認できるようにする
  • このコードは、すでにある 一覧読み込みメソッド(例:LoadEmployees)や読み込みボタンの Click の中に入れます。新しい部品やイベントの追加は不要です。
  • 重い処理の前後でカーソルを切り替えます。try / finally で確実に戻すのがコツです(第 15 章の finally)。
  • Thread.Sleep(2000) で 2 秒間プログラムを止められます(ミリ秒指定)。これを処理の中に挟むと、砂時計が出ている様子をはっきり確認できます。
Cursor.Current = Cursors.WaitCursor;
try
{
System.Threading.Thread.Sleep(2000); // ★疑似ウェイト(確認用):本物の重い処理の代わり
// DB 読み込みなどの処理
}
finally
{
Cursor.Current = Cursors.Default; // 例外が起きても必ず戻す
}

このウェイトは「確認用」です

Thread.Sleep はプログラムを本当に止めてしまい、その間アプリは固まって操作できなくなります(これも「処理中は触れない」体験として観察してみてください)。 砂時計が出ることを確認できたら、Thread.Sleep の行はコメントアウトするか削除します。本物のアプリにこの待機を残してはいけません。「現場では時間のかかる処理にカーソル切り替えを付ける」という型を身に付けるのが本当の狙いです。

  • 読み込みボタンを押すと、2 秒ほどカーソルが砂時計になるのが見える
  • その 2 秒間はアプリが固まって操作できない(Thread.Sleep が止めている)
  • エラーが起きてもカーソルが砂時計のまま固まらない(finally で戻る)
  • 確認できたら Thread.Sleep の行を消した

レベル 3:仕上げ ― 市販アプリに近づける(★★★)

Section titled “レベル 3:仕上げ ― 市販アプリに近づける(★★★)”

ここは「市販のソフトみたいな完成度・操作性」に踏み込む、歯ごたえのある層です。別スレッドや設定の保存など、実際の業務アプリが必ず備えているが、実装は決して簡単ではない 工夫を扱います。引き続き EmployeeApp に足していきます。

実装しなくても「読み物」として読んでOK

ふだん使っているアプリの「読み込み中でも固まらない」「前回の状態を覚えている」といった機能は、当たり前に見えて、裏では決して簡単でない仕組み で実現されています。このレベルは、時間内に実装まで届かなくても構いません。各課題の骨格コードと「なぜ」の解説を読んで、「現場ではこういう手法で作っているのか」と知っておくこと自体に価値があります(配属後に「どう実現するか」の見当がつきます)。

リリース計画では無理に入れず、余裕があれば実装、なければ読んで知る で使ってください。手を広げて全部が半端になるより、確実に動くものをリリースするのが優先です。

J-6-2 前回の状態を覚えて次回復元する(設定の永続化)

Section titled “J-6-2 前回の状態を覚えて次回復元する(設定の永続化)”
  • アプリを閉じるときに、ウィンドウのサイズ・位置(できれば前回の検索キーワード・選択部署も)を保存する
  • 次に起動したとき、その状態を 復元 する
  • フォームの FormClosing で保存、Load で復元 します。FormClosing は早見表 ③(稲妻アイコン)から紐付けます(Load は第 23 章で紐付け済み)。
  • 保存先は実行フォルダのファイル 1 つ。.NET 8 標準の System.Text.Json(NuGet 不要)で設定オブジェクトをまるごと保存すると簡単です。
  • ウィンドウは this.Size / this.Location(必要なら this.WindowState)を読み書きします。
  • まずはウィンドウのサイズ・位置だけ で完成にしましょう。検索条件まで復元するのは「復元してから自動で検索を走らせる」順番が絡んで難しくなるので、余裕があれば挑戦する(任意)くらいで十分です。
// 保存したい設定をまとめるクラス
public class AppSettings
{
public int WindowWidth { get; set; }
public int WindowHeight { get; set; }
public string LastKeyword { get; set; }
public int LastDepartmentId { get; set; }
}
// 保存(FormClosing)
string json = System.Text.Json.JsonSerializer.Serialize(settings);
File.WriteAllText("settings.json", json);
// 復元(Load)
if (File.Exists("settings.json"))
{
AppSettings s = System.Text.Json.JsonSerializer.Deserialize<AppSettings>(File.ReadAllText("settings.json"));
// s の値を画面(Size/Location や検索条件)に反映する
}

ファイルが無い初回起動や、ファイルが壊れていた場合でも落ちないように、File.Exists のチェックや try-catch(第 15 章)を入れておきます。これも「市販アプリらしい気配り」です。

  • ウィンドウを大きくして閉じ、もう一度開くと同じ大きさで開く
  • (できれば)前回の検索条件が復元される
  • settings.json が無くても・壊れていても起動できる

J-6-3 一覧を作り込む(条件付き書式+右クリックメニュー)

Section titled “J-6-3 一覧を作り込む(条件付き書式+右クリックメニュー)”
  • 給与に応じて 行を色分け する(例:80 万以上は薄い水色、40 万未満は薄い赤)
  • 一覧の行を 右クリック すると「編集 / 削除 / メールをコピー」のメニューが出る
  • 色分けは DataGridViewCellFormatting イベント(早見表 ③:DataGridView を選んで稲妻アイコンから紐付け)で、行のデータを見て DefaultCellStyle.BackColor を変えます。
  • 右クリックメニューは ContextMenuStrip(標準コンポーネント)をツールボックスから置くと、コンポーネント トレイに入ります(早見表)。画面に出たメニュー枠に「編集 / 削除 / メールをコピー」と文字を打って項目を足し、DataGridViewContextMenuStrip プロパティ にこの部品を割り当てます。
  • 各メニュー項目を ダブルクリック すると、その項目の Click ハンドラーができます(早見表 ③)。中では、選択中の社員(GetSelectedEmployee())に対して既存の編集・削除処理を呼んだり、Clipboard.SetText(selected.Email) でメールをコピーします。
private void dataGridViewEmployees_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
Employee emp = dataGridViewEmployees.Rows[e.RowIndex].DataBoundItem as Employee;
if (emp == null) return;
if (emp.Salary >= 800000)
{
dataGridViewEmployees.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightCyan;
}
else if (emp.Salary < 400000)
{
dataGridViewEmployees.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.MistyRose;
}
}

(任意) 右クリックした行を選択状態にしたいときは、CellMouseDowndataGridViewEmployees.CurrentCell を設定すると親切です(右クリック前に選んでいた行と取り違えない)。ここは細かい配慮なので、まずは「色分け+右クリックメニューが出て動く」までで完成として構いません。

  • 給与の高い行・低い行が色分けされる
  • 行を右クリックするとメニューが出る
  • メニューから編集・削除・メールのコピーができる

J-6-1 重い処理でもアプリが固まらないようにする(BackgroundWorker)― 参考

Section titled “J-6-1 重い処理でもアプリが固まらないようにする(BackgroundWorker)― 参考”

この課題は「参考(読み物)」です。実装はしません。

仕組みが少し高度(別スレッド)で、初学者が実装に踏み込むと配線やスレッド特有のエラーで時間を取られがちです。ここでは 「なぜ固まるのか」「現場ではどう解決しているのか」を知る ことだけを目的にします。手を動かしてみたい人は、配属後やもっと余裕のあるときに挑戦してください。

J-5-6 で、読み込み中はアプリが固まって動かせないことを体験しました。なぜでしょうか。

ボタンのクリックも画面の再描画も、ぜんぶ 1 本の「UI スレッド」 が順番にこなしています。そこで時間のかかる処理をすると、その間ほかが回らず、アプリが固まります。

現場では、重い処理だけを別のスレッドに逃がして、UI スレッドを空けておく という手法で解決します。Windows フォームには、これを助ける BackgroundWorker という標準部品があり、

  • 時間のかかる処理は 別スレッド側(DoWork)で行い、
  • 画面の更新は UI スレッドに戻ってから(RunWorkerCompleted)行う

というふうに書き分けます(別スレッドから画面を直接触ると例外になるため)。

図にすると、こんな分担です。

比べてみてください。J-5-6 では、この「重い処理」を UI スレッドの上で やってしまうので、その間 UI->>User: ちゃんと反応する の矢印が描けず、アプリが固まりました。重い処理を 別スレッド に逃がすと、UI スレッドが空いたままになるのがポイントです。

処理は裏のスレッドで、画面の更新は表の UI スレッドで」――この役割分担だけ知っておけば十分です。配属後にマルチスレッドを本格的に学ぶときの足がかりになります。


レベル 4:さらにその先へ(付録 F-3)

Section titled “レベル 4:さらにその先へ(付録 F-3)”

ここまでできたら、もう一段難しいテーマに挑戦できます。付録 F-3「Windows フォーム編 発展課題」 に進んでください。

課題内容
F-3-1部署一括の給与アップ(トランザクション 付き・ロールバック対応)
F-3-2社員一覧の CSV エクスポート(SaveFileDialog + CSV 整形)
F-3-3部署マスタ管理画面(部署の追加・編集・削除)

これらは「全課程を走り切った人向け」の総仕上げ課題です。本付録で育てた EmployeeApp をさらに発展させる題材にもなります。


リリース時刻の少し前(フィーチャーフリーズ)で、このリストを確認してから提出します。

リリースの規律

  • 作業前に 自分の リリース計画.txt(氏名入り)を書いて自分のリポジトリにコミットした
  • 納期(リリース時刻)を守った
  • リリースに含めた機能は すべて最後まで動く(押すと落ちる・途中で止まるボタンが残っていない)
  • 間に合わなかった中途半端な機能は 外した(コメントアウト or 次回送り)
  • 起動してひととおり操作し、エラーが出ないことを確認した

最低ライン・品質

  • レベル 1 から、まだやっていなかった発展を 2 本以上 動く状態で含めた
  • 含めた機能の難所に「なぜ」コメントを前行で書いた
  • コメントが言い換えではなく「なぜ / 何のため」になっている
  • 機能を足す前にコミットし、いつでも前の状態に戻せるようにした
  • パラメータ化クエリで書いている(+ での SQL 連結が残っていない)
  • SqlException をキャッチして MessageBox でエラーを表示する
  • binobj.vs フォルダが Git 管理に入っていない
  • チームの場で 自分の リリース報告(デモ + 計画と実績 + 詰まった点)を行った

リリース時刻(= 提出時刻)になったら、中途半端な機能を外した「動く状態」を保存してリリースします。この時刻のコミットが成果物です。

Terminal window
git status
git add .
git commit -m "AppendixJ: リリース / <今回リリースした機能> / <詰まった点・工夫>"
git push origin main

提出方法:Git が使えないときはサーバへコピー

Git の調子が悪いときは、講師の指示で push の代わりに KadaiWinFormsApp フォルダをサーバ上の自分のフォルダへコピーして提出します。 その場合は、コミットメッセージの代わりに、提出先へエクスプローラーの右クリック →「新規作成」→「テキスト ドキュメント」で 提出メモ.txt を作り、「どこまで完成したか」「詰まったポイント」を書いておいてください。

Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。


  • 第 25 章で作った最小 CRUD アプリは、機能と操作感を足していくことで 実際の業務アプリに近づけられる
  • まずは やり残した発展課題(レベル 1) を回収して、第 23〜25 章で学んだ技術を確実にする
  • 集計表示・重複エラーの親切な通知・部署プルダウン(レベル 2)は、これまでに学んだ LINQ・例外処理・データバインドの組み合わせで作れる
  • リサイズ対応・入力チェック・キーボード操作・ステータスバー・見やすさ・処理中表示(レベル 2.5)は、新しい文法なしで「使えるアプリ」に変えてくれる
  • 現場のアプリは、こうした 地味だが効く操作改善 の積み重ねでできている
  • そして何より、納期を決めて見積もり、中途半端でない動く状態でリリースする という現場の作法を、この 1 日で体験する
  • さらに挑戦したい人は付録 F-3 へ