WinFormsのイベント登録は2通り:コードで += か、プロパティウィンドウ(稲妻)か——違い・使い分け・落とし穴まで
- 2つの登録方法:①コードで +=、②Visual Studio の [プロパティ]ウィンドウ→稲妻(イベント)。
- どちらも最終的には同じ(Form1.Designer.cs に購読コードが並ぶ)。
- 混在させると二重登録になりやすい。チームや教材では片方に統一。
- 匿名ラムダは解除しにくいので、基本はメソッド名ハンドラを推奨。
1. 例題:起動時に保存データを読み込む
WinForms 超入門:カレンダーで選んだ日に「1行メモ」を追加し、JSONに保存する(単機能ミニアプリ)を参考に説明します
Form.Load
コードで登録(.NET 6/7/8)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// ① コードで登録:Loadイベント
Load += Form1_Load;
}
private void Form1_Load(object? sender, EventArgs e)
{
// 起動時の初期化
_all = JsonStorage.Load();
UpdateListForSelectedDate();
}
}
ラムダで短く書くなら:
Load += (_, __) =>
{
_all = JsonStorage.Load();
UpdateListForSelectedDate();
};
_/__ の“捨て”をやめて、普通に sender と e(イベント引数)を受け取ればOKです。イベントの種類ごとに e の型が違う点に注意してください。
基本(Form.Load)
Load += (object? sender, EventArgs e) =>
{
var form = sender as Form; // 送信元(ここでは Form1)
// form?.Text = "読み込み中..."; // 例:フォームにアクセス
_all = JsonStorage.Load();
UpdateListForSelectedDate();
};
- sender: イベントを発生させたオブジェクト(多くはそのコントロール自身)
- e: 追加情報を持つ引数(イベントごとに型が異なる)
具体例
1) MonthCalendar.DateChanged(型:DateRangeEventArgs)
monthCalendar1.DateChanged += (object? sender, DateRangeEventArgs e) =>
{
var start = e.Start.Date;
var end = e.End.Date;
// 選択範囲に応じて更新
UpdateListForSelectedDate(); // 単日なら start を使う
};
2) Form.FormClosing(型:FormClosingEventArgs)
FormClosing += (object? sender, FormClosingEventArgs e) =>
{
if (e.CloseReason == CloseReason.UserClosing)
{
// 「×」で閉じようとしたときだけキャンセルして最小化、など
e.Cancel = true;
this.Hide();
}
};
3) Button.Click(型:EventArgs)
btnAdd.Click += (object? sender, EventArgs e) =>
{
if (sender is Button btn)
{
// どのボタンか分岐したいとき
// Debug.WriteLine(btn.Name);
}
var text = txtMemo.Text.Trim();
if (text.Length == 0) return;
var d = monthCalendar1.SelectionStart.Date;
_all.Add(new DayMemo { Date = d, Text = text });
JsonStorage.Save(_all);
txtMemo.Clear();
UpdateListForSelectedDate();
};
4) Control.MouseMove(型:MouseEventArgs)
this.MouseMove += (object? sender, MouseEventArgs e) =>
{
// e.X, e.Y, e.Button などが使える
// label1.Text = $"({e.X}, {e.Y})";
};
型を間違えないコツ
- プロパティウィンドウ(稲妻)でハンドラを作らせると、正しい引数シグネチャのメソッドが自動生成されます。
- 自分でラムダを書く場合も、引数の型を明示しておくとコンパイル時にミスに気づけます。
- 例:(object? sender, DateRangeEventArgs e) => { … }
参考:よく使うイベントと引数型
- Load / Click / TextChanged … EventArgs
- FormClosing … FormClosingEventArgs
- DateChanged(MonthCalendar)… DateRangeEventArgs
- MouseDown/Move/Up … MouseEventArgs
- KeyDown/Up/Press … KeyEventArgs / KeyPressEventArgs
補足:sender は null 許容(object?)なので、厳密に書くなら if (sender is Form f) { … } のように パターンマッチで扱うと安全です。
プロパティウィンドウ(稲妻)で登録
- デザイナでフォームの空白をクリックして Form1 を選択
- F4 で[プロパティ]を開き、上部の 稲妻アイコン(イベント)をクリック
- Load の欄に Form1_Load と入力して Enter→ エディタに下記メソッド雛形が生成され、Form1.Designer.cs に購読コードが追加されます。
生成される購読コード(SDKスタイル .NET 6+ の一例):
// Form1.Designer.cs(自動生成)
this.Load += Form1_Load;
(.NET Framework だと new EventHandler(this.Form1_Load) 形式になることがありますが意味は同じ)
2. それぞれのメリット/デメリット
コードで登録(+=)
メリット
- 条件付き登録・解除が柔軟(モードに応じて付け外し可能)。
- どのタイミングで登録しているか追いやすい(ソース一点管理)。
- 複数ハンドラや購読順の明示がしやすい。
デメリット
- UI中心の開発ではやや記述が増える。
- チームでデザイナ操作が前提だと慣習とズレる場合がある。
プロパティウィンドウ(稲妻)
メリット
- GUI操作で直感的。初学者やUI主導の開発で扱いやすい。
- ハンドラ雛形が自動生成される(引数型ミスがない)。
デメリット
- 登録箇所が Designer.cs に分散 し、追跡しづらいことがある。
- 手書き += と併用すると二重登録しやすい。
- 誤って Designer.cs を手編集すると上書きで消える。
結論:
- 教材や小規模なら どちらか片方に統一。
- 実務で状態によって付け外しするなら コードで登録 が便利。
3. 典型的な落とし穴と対策
落とし穴1:二重登録(イベントが2回呼ばれる)
原因:デザイナで Load を登録しつつ、コードでも Load += … を書いてしまう。
対策:片方に統一。既存の登録を確認するには、Form1.Designer.cs の this.Load += … の有無を確認。
落とし穴2:匿名ラムダの解除ができない
button1.Click += (s, e) => DoSomething();
// ↓ これでは同じ匿名ラムダを参照できず解除できない
button1.Click -= (s, e) => DoSomething();
対策:メソッド名ハンドラにするか、変数に保持してから登録・解除。
EventHandler? h = null;
h = (s, e) => { DoSomething(); button1.Click -= h; }; // 1回だけの自己解除パターン
button1.Click += h;
落とし穴3:Designer.cs を直接編集
原因:手で編集しても、デザイナ操作で再生成されて上書き。
対策:購読の追加・削除は プロパティウィンドウ または コード側 で行う。Designer.cs は触らない。
落とし穴4:購読順序の誤解
+= の追加順で呼び出されます。複数ハンドラの順序を確かめる演習は有益。
例外が投げられると後続が呼ばれない場合があるため、ハンドラ内での例外処理は各自で行う。
4. 解除(-=)と寿命の考え方
- フォームやコントロールのイベントは、通常はフォーム破棄時にまとめて解放されるため、明示的解除が必須な場面は少ない。
- ただし 長寿命オブジェクト(staticやシングルトン)→短寿命(フォーム) の方向で購読した場合、リークの温床になります。こうした場合は確実に -= で解除しましょう。
5. 代表的イベントの登録パターン(両方式)
Form.Load
- プロパティウィンドウ:Load → Form1_Load
- コード:Load += Form1_Load;
Form.FormClosing
- プロパティウィンドウ:FormClosing → Form1_FormClosing
- コード:FormClosing += Form1_FormClosing;
Button.Click
- プロパティウィンドウ:ボタンを選択 → Click → btnAdd_Click
- コード:btnAdd.Click += btnAdd_Click;
MonthCalendar.DateChanged
- プロパティウィンドウ:カレンダーを選択 → DateChanged → monthCalendar1_DateChanged
- コード:monthCalendar1.DateChanged += monthCalendar1_DateChanged;
6. 実例:単機能メモアプリ(両方式の対比)
A. すべてプロパティウィンドウで登録する方針
- Load → Form1_Load
- FormClosing → Form1_FormClosing
- btnAdd.Click → btnAdd_Click
- monthCalendar1.DateChanged → monthCalendar1_DateChanged
// 生成される購読(例:.NET 6+)
this.Load += Form1_Load;
this.FormClosing += Form1_FormClosing;
btnAdd.Click += btnAdd_Click;
monthCalendar1.DateChanged += monthCalendar1_DateChanged;
B. すべてコードで登録する方針
public Form1()
{
InitializeComponent();
Load += Form1_Load;
FormClosing += Form1_FormClosing;
btnAdd.Click += btnAdd_Click;
monthCalendar1.DateChanged += monthCalendar1_DateChanged;
}
混ぜないのが鉄則。教材では「A で統一」か「B で統一」を明示しておくと事故が減ります。
7. 実務での判断基準(チェックリスト)
- UI操作中心で初学者/非エンジニアも触る → プロパティウィンドウ
- 条件に応じて付け外し/テストで差し替えたい → コード
- 大人数開発で差分レビューを楽にしたい → コードに寄せる(差分が追いやすい)
- 教材で初心者にイベント概念だけ伝えたい → プロパティウィンドウが理解しやすい
8. 付録:よく使う小ワザ
匿名ラムダで一時的に購読(自己解除)
EventHandler? h = null;
h = (s, e) =>
{
DoSomethingOnce();
someControl.SomeEvent -= h; // 1回だけ実行
};
someControl.SomeEvent += h;
一括でハンドラを切り替える
void EnableHandlers(bool on)
{
if (on)
{
btnAdd.Click += btnAdd_Click;
monthCalendar1.DateChanged += monthCalendar1_DateChanged;
}
else
{
btnAdd.Click -= btnAdd_Click;
monthCalendar1.DateChanged -= monthCalendar1_DateChanged;
}
}
複数ハンドラの呼び出し順チェック(学習用)
btnAdd.Click += (_, __) => Debug.WriteLine("A");
btnAdd.Click += (_, __) => Debug.WriteLine("B"); // 追加順に A→B と出る
まとめ
- WinForms のイベント登録は 「コードで +=」 と 「プロパティウィンドウ(稲妻)」 の2通り。
- 最終的な動作は同じだが、混在は二重登録の温床。どちらかに統一しよう。
- 匿名ラムダは解除が難しいため、メソッド名ハンドラが安全。
- 長寿命→短寿命の購読は明示解除を忘れずに。
既存プロジェクトのスタイルに合わせて「片方へ統一するPR用差分例」や、「初学者用の演習プリント(スクショ付き)」も用意できます。必要ならフォーム名・コントロール名を教えてください。
ディスカッション
コメント一覧
まだ、コメントがありません