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) { … } のように パターンマッチで扱うと安全です。

プロパティウィンドウ(稲妻)で登録

  1. デザイナでフォームの空白をクリックして Form1 を選択
  2. F4 で[プロパティ]を開き、上部の 稲妻アイコン(イベント)をクリック
  3. 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用差分例」や、「初学者用の演習プリント(スクショ付き)」も用意できます。必要ならフォーム名・コントロール名を教えてください。

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