WinFormsでForm2を閉じたらForm1を更新したい ― フォーム間通信の考え方

広告

WinFormsで複数のフォームを使っていると、こんな場面が出てきます。

  • Form1 から Form2 を開く
  • Form2 で何か操作する
  • Form2 を閉じる
  • そのタイミングで Form1 の表示を更新する

たとえば、Form2 がデータの編集画面で、閉じたあとに Form1 の一覧を更新したい場合です。

「Form2が閉じられたとき、Form1のメソッドを実行したい」

ということですね。

方法はいくつかあります。

大切なのは、とりあえず動く方法を覚えることではなく、Form1とForm2をどのような関係にしたいのかを考えることです。

この記事では、次の順番で考えていきます。

  1. FormClosed イベントを使う
  2. ShowDialog() を使う
  3. DialogResult で結果を受け取る
  4. 独自イベントでForm2からForm1へ通知する

まずは一番わかりやすい方法 ― FormClosed

Form1からForm2を開いてみます。

private void button1_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();

    form2.FormClosed += Form2_FormClosed;

    form2.Show();
}

そしてForm1に、次のイベントハンドラを用意します。

private void Form2_FormClosed(object? sender, FormClosedEventArgs e)
{
    RefreshData();
}

RefreshData() は、Form1の表示を更新するためのメソッドだと考えてください。

private void RefreshData()
{
    label1.Text = DateTime.Now.ToString();
}

Form2を閉じると、

Form2
  ↓
FormClosed
  ↓
Form1のForm2_FormClosed()
  ↓
RefreshData()

という順番で処理されます。

これで目的は達成できます。

この書き方は悪いの?

いいえ。

FormClosed は、フォームが閉じられたことを知らせるために用意されているイベントです。

そのため、

Form2が閉じられたら何かしたい

という目的なら、自然な書き方です。

ただし、ここで一つ考えてみましょう。

Form2を表示している間、Form1を操作する必要はあるでしょうか?

ここで設計が分かれます。


Show()とShowDialog()の違い

フォームを表示する代表的な方法には、

form2.Show();

と、

form2.ShowDialog();

があります。

この2つは似ていますが、動作が違います。

Show()

form2.Show();

では、Form2を表示したあともForm1を操作できます。

Form1 ─────── 操作できる
   \
    └─ Form2 ─────── 操作できる

このようなフォームをモードレスなフォームと呼びます。

ShowDialog()

一方、

form2.ShowDialog();

では、Form2を閉じるまでForm1を操作できません。

Form1
  │
  ├─ Form2を開く
  │
  │   Form2を操作
  │   Form2を閉じる
  │
  └─ Form1の処理を再開

このようなフォームをモーダルなフォームと呼びます。

設定画面や入力画面などでは、こちらの動作のほうが自然な場合があります。


Form2を閉じたあとに処理するだけならShowDialog()が簡単

Form2を開いている間にForm1を操作する必要がないのであれば、次のように書けます。

private void button1_Click(object sender, EventArgs e)
{
    using Form2 form2 = new Form2();

    form2.ShowDialog();

    RefreshData();
}

注目するのは、この部分です。

form2.ShowDialog();

RefreshData();

ShowDialog() を実行すると、Form2が閉じるまで次の行へ進みません。

そのため、

  1. Form2を表示
  2. Form2を操作
  3. Form2を閉じる
  4. ShowDialog() の次の行へ進む
  5. RefreshData() を実行

となります。

つまり、

form2.FormClosed += Form2_FormClosed;

とイベントを登録しなくてもよいのです。

イベントを使わないと設計として悪い?

そんなことはありません。

イベントは便利ですが、必要のないイベントまで使う必要はありません。

「Form2を閉じるまで待って、そのあと処理する」

という単純な流れなら、

form2.ShowDialog();
RefreshData();

のほうが処理の流れを読み取りやすい場合があります。


「閉じた」ではなく「保存した」を知りたい

ここでもう一つ問題があります。

Form2には、

  • 保存ボタン
  • キャンセルボタン

があるとします。

どちらを押してもForm2は閉じます。

しかしForm1を更新したいのは、

保存したときだけ

かもしれません。

そこで使えるのが DialogResult です。

Form1を次のようにします。

private void button1_Click(object sender, EventArgs e)
{
    using Form2 form2 = new Form2();

    if (form2.ShowDialog() == DialogResult.OK)
    {
        RefreshData();
    }
}

Form2の保存ボタンでは、

private void saveButton_Click(object sender, EventArgs e)
{
    // 保存処理

    DialogResult = DialogResult.OK;
    Close();
}

キャンセルボタンでは、

private void cancelButton_Click(object sender, EventArgs e)
{
    DialogResult = DialogResult.Cancel;
    Close();
}

とします。

これでForm1は、

Form2を開く
    ↓
┌──────────────┐
│              │
保存          キャンセル
│              │
OK           Cancel
│              │
↓              ↓
更新する      更新しない

と判断できます。


「閉じた」より「何が起きたか」を考えよう

ここは大切なポイントです。

最初の要求は、

Form2が閉じられたらForm1を更新したい

でした。

しかし、本当に知りたかったことは、

Form2が閉じられたこと

なのでしょうか。

実際には、

Form2でデータが保存されたこと

を知りたいのかもしれません。

この違いを考えると、プログラムがわかりやすくなります。

FormClosed
    ↓
「フォームが閉じた」

DialogResult.OK
    ↓
「OKという結果で終了した」

DataChanged
    ↓
「データが変更された」

下へ行くほど、プログラム上の意味が具体的になっています。


Form2を開いたままForm1も操作したい場合

では、

form2.Show();

を使いたい場合はどうでしょう。

この場合は ShowDialog() のようにForm2が閉じるまで待つことはできません。

単純に「Form2が閉じたこと」を知りたいだけなら、最初に紹介した FormClosed が使えます。

しかし、

Form2で保存されたらForm1を更新したい

という要求なら、別の方法も考えられます。

それが独自イベントです。


Form2に独自イベントを作る

Form2に次のイベントを用意します。

public partial class Form2 : Form
{
    public event EventHandler? DataChanged;

    public Form2()
    {
        InitializeComponent();
    }

    private void saveButton_Click(object sender, EventArgs e)
    {
        // 保存処理

        DataChanged?.Invoke(this, EventArgs.Empty);
    }
}

ここで、

public event EventHandler? DataChanged;

は、

データが変更された

という出来事を表しています。

そして、

DataChanged?.Invoke(this, EventArgs.Empty);

で、その出来事が発生したことを知らせています。


Form1でイベントを受け取る

Form1では次のようにします。

private void button1_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();

    form2.DataChanged += Form2_DataChanged;

    form2.Show();
}

イベントハンドラは、

private void Form2_DataChanged(object? sender, EventArgs e)
{
    RefreshData();
}

です。

流れは次のようになります。

Form1
  │
  │ Form2を生成
  ↓
Form2
  │
  │ 保存
  ↓
DataChanged
  │
  │ 通知
  ↓
Form1
  │
  ↓
RefreshData()

ここで重要なのは、Form2がForm1の RefreshData() を直接呼び出していないことです。


Form2からForm1を直接操作してはいけないの?

たとえばForm2から、

form1.RefreshData();

のように呼び出す方法を思いつくかもしれません。

小さなプログラムでは、それでも動かせます。

直接呼び出すこと自体が、必ず間違いというわけでもありません。

ただし、この方法ではForm2が、

自分を使っている相手はForm1である

ということを知る必要があります。

関係を図にすると、

Form1 ─────→ Form2
  ↑           │
  └───────────┘

となります。

Form1はForm2を知っていて、Form2もForm1を知っています。

プログラムが大きくなると、このような相互の依存関係が増えるほど変更しにくくなることがあります。


イベントならForm2はForm1を知らなくてよい

イベントを使った場合は違います。

Form2が知っているのは、

データが変更されたので通知する

ということだけです。

Form2
  │
  │ DataChanged
  │
  ↓
「データが変わりました」

それを誰が受け取るかはForm2には関係ありません。

Form1が受け取ってもよいですし、別のオブジェクトが受け取ることもできます。

つまりForm2は、

「誰に何をさせるか」

ではなく、

「自分に何が起きたか」

を通知しています。

これがイベントを使う大きなメリットです。


では、どの方法を使えばよい?

今回紹介した方法を整理してみましょう。

やりたいこと方法
Form2を開いたままForm1も操作したいShow()
Form2が閉じたことを知りたいFormClosed
Form2を閉じるまでForm1を操作させないShowDialog()
OK・キャンセルなど終了結果を知りたいDialogResult
Form2で起きた出来事をForm1へ知らせたい独自イベント

最初から、

フォーム間通信ならイベント

と決める必要はありません。

まず、

Form2を開いている間にForm1を操作する必要があるのか?

を考えます。

必要がなければ、

using Form2 form2 = new Form2();

if (form2.ShowDialog() == DialogResult.OK)
{
    RefreshData();
}

のような書き方がシンプルです。

一方、Form2を開いたままForm1も操作し、さらにForm2で起きた出来事を知らせたいなら、独自イベントが役立ちます。


「フォーム間通信」もオブジェクト同士のやり取り

Form1やForm2は特別な存在に見えるかもしれません。

しかしC#から見れば、

Form2 form2 = new Form2();

で作られたForm2も、ひとつのインスタンスです。

そのためフォーム間のやり取りも、

  • メソッドを呼ぶ
  • プロパティから値を取得する
  • イベントを購読する
  • イベントで通知する

という、これまで学んできたオブジェクト同士のやり取りとして考えることができます。


まとめ ― 「どう呼ぶか」より「何を伝えたいか」

今回の最初の問題は、

Form2が閉じられたとき、Form1でメソッドを実行したい

でした。

もちろん FormClosed を使えば実現できます。

しかし、一歩進んで考えてみると、

本当に知りたいのは「閉じたこと」なのか?

という疑問が出てきます。

保存されたことを知りたいのであれば DialogResultDataChanged のほうが、プログラムの意味を表しやすい場合があります。

大切なのは、

「どうすればForm1のメソッドを呼べるか」だけを考えないことです。

まず、

「Form1はForm2から何を知りたいのか」

を考えてみましょう。

それが決まれば、

FormClosed
ShowDialog()
DialogResult
独自イベント

のどれを選ぶべきかも、自然に見えてきます。

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

広告