【C#】formを継承して派生クラスのイベントを先に処理する

「form(フォーム)を継承して派生クラスのイベントを先に処理するのはどうしたらいいの?」

こんなことを悩んでいませんか?
こういった疑問に答えます。

今日は「Windowsアプリケーションで、form(フォーム)を継承して派生クラスのイベントを先に処理する方法」について紹介します。

継承したform(フォーム)のイベント発生順は?

form(フォーム)のイベント発生順

次の例を見てください。

FormClosingに処理が書いてある基底クラスフォームと、派生クラスフォームがあります。

基底クラスフォームのイベントには、フォーム終了時に共通で実行したい処理があります。
派生クラスフォームのイベントには、フォーム固有の終了時に実行したい処理があります。

保存していれば、画面を閉じる事ができる。
保存していないと、画面を閉じられない処理があるような画面のイメージです。

【C#】formを継承して派生クラスのイベントを先に処理

画面が開いたら、閉じるボタンを押してみます。
イベントの発生順をみてみます。

Copy
/// <summary> /// 基底クラス /// </summary> public partial class BaseForm : Form { /// <summary> /// FormClosing /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BaseForm_FormClosing(object sender, FormClosingEventArgs e) { Console.WriteLine("==== BaseForm BaseForm_FormClosing ===="); Console.WriteLine("==== ログ出力:○○画面が閉じられました。 ===="); } } /// <summary> /// 派生クラス /// </summary> public partial class ChildForm : BaseForm { /// <summary> /// 保存したか /// </summary> bool Saved { get; set; } = false; /// <summary> /// Form_Load /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ChildForm_Load(object sender, EventArgs e) { Console.WriteLine("==== ChildForm ChildForm_Load ===="); } /// <summary> /// FormClosing /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ChildForm_FormClosing(object sender, FormClosingEventArgs e) { Console.WriteLine("==== ChildForm ChildForm_FormClosing ===="); if (!Saved) { // 保存していないので画面のClosingをキャンセルする。 Console.WriteLine("==== e.Cancel = true; ===="); e.Cancel = true; return; } } /// <summary> /// 保存ボタン Click /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonSave_Click(object sender, EventArgs e) { Console.WriteLine("==== ChildForm buttonSave_Click ===="); Saved = true; } /// <summary> /// 閉じるボタン Click /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonClose_Click(object sender, EventArgs e) { Console.WriteLine("==== ChildForm buttonClose_Click ===="); Console.WriteLine("==== this.Close(); ===="); this.Close(); } } // 【実行結果】 // ==== ChildForm ChildForm_Load ==== // ==== ChildForm buttonClose_Click ==== // ==== this.Close(); ==== // ==== BaseForm BaseForm_FormClosing ==== // ==== ログ出力:○○画面が閉じられました。 ==== // ==== ChildForm ChildForm_FormClosing ==== // ==== e.Cancel = true; ====  

イベントの順番はこうなります。

this.Close();BaseForm_FormClosingChildForm_FormClosing

そのため、基底クラスで『○○画面が閉じられました。』とログが書かれてから、派生クラスの画面のClose判定がされて、実際には画面が閉じられていません。

おかしいですよね?

ポイント

  • イベントの発生順は、基底クラスから派生クラスの順です。

では、派生クラスで画面を閉じる判定をしてから、基底クラスが呼ばれるためにはどうしたらいいでしょうか?

派生クラスのイベントを先に処理するには?

派生クラスのイベントを先に処理する

先ほどのソースにOnFormClosingイベントという見慣れないイベントが追加されています。

ChildForm_FormClosingイベントにあった判定がこちらに移動して、base.OnFormClosing(e);というソースも追加されています。

今度は画面が開いたら、保存ボタン、閉じるボタンと押してみます。
イベントの発生順はどうなるでしょうか。

Copy
/// <summary> /// 基底クラス /// </summary> public partial class BaseForm : Form { /// <summary> /// FormClosing /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BaseForm_FormClosing(object sender, FormClosingEventArgs e) { Console.WriteLine("==== BaseForm BaseForm_FormClosing ===="); Console.WriteLine("==== ログ出力:○○画面が閉じられました。 ===="); } } /// <summary> /// 派生クラス /// </summary> public partial class ChildForm : BaseForm { /// <summary> /// 保存したか /// </summary> bool Saved { get; set; } = false; /// <summary> /// Form_Load /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ChildForm_Load(object sender, EventArgs e) { Console.WriteLine("==== ChildForm ChildForm_Load ===="); } /// <summary> /// OnFormClosing /// </summary> /// <param name="e"></param> protected override void OnFormClosing(FormClosingEventArgs e) { Console.WriteLine("==== ChildForm OnFormClosing ===="); if (!Saved) { // 保存していないので画面のClosingをキャンセルする。 Console.WriteLine("==== e.Cancel = true; ===="); e.Cancel = true; return; } // 基本クラスのイベント呼び出しメソッドを呼び出します。 base.OnFormClosing(e); } /// <summary> /// FormClosing /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ChildForm_FormClosing(object sender, FormClosingEventArgs e) { Console.WriteLine("==== ChildForm ChildForm_FormClosing ===="); } /// <summary> /// 保存ボタン Click /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonSave_Click(object sender, EventArgs e) { Console.WriteLine("==== ChildForm buttonSave_Click ===="); Saved = true; } /// <summary> /// 閉じるボタン Click /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void buttonClose_Click(object sender, EventArgs e) { Console.WriteLine("==== ChildForm buttonClose_Click ===="); Console.WriteLine("==== this.Close(); ===="); this.Close(); } } // 【実行結果】 // ==== ChildForm ChildForm_Load ==== // ==== ChildForm buttonSave_Click ==== // ==== ChildForm buttonClose_Click ==== // ==== this.Close(); ==== // ==== ChildForm OnFormClosing ==== // ==== BaseForm BaseForm_FormClosing ==== // ==== ログ出力:○○画面が閉じられました。 ==== // ==== ChildForm ChildForm_FormClosing ====  

イベントの順番はこうなります。

this.Close();OnFormClosingBaseForm_FormClosingChildForm_FormClosing

ポイント

  • FormClosingイベントの基底クラスから派生クラスへの順番はそのままですが、ChildFormOnFormClosingイベントが一番先に発生します。
  • ChildForm_FormClosingイベントにあるbase.OnFormClosing(e);BaseForm_FormClosingを呼び出します。
    これがないとBaseForm_FormClosingイベントが発生しなくなります。

そのため、OnFormClosingイベントで画面のClosingをキャンセルして、BaseFormFormClosingイベントを発生させないことも可能になります。

画面が開いたら、閉じるボタンと押してみるとこうなります。

Copy
// 【実行結果】 // ==== ChildForm ChildForm_Load ==== // ==== ChildForm buttonClose_Click ==== // ==== this.Close(); ==== // ==== ChildForm OnFormClosing ==== // ==== e.Cancel = true; ====  

イベントの順番はこんな感じです。

this.Close();OnFormClosing

本来やりたかった『form(フォーム)を継承して派生クラスのイベントを先に処理する』ことができるようになりました。

ポイント

  • 派生クラスへのOnFormClosingイベントでキャンセルすれば、以降のBaseForm_FormClosingが発生しません。

最後にOnXxxxイベントの作り方のポイントだけご紹介します。

OnXxxxイベントの作り方は?

OnXxxxイベントの作り方

今回ここで使ったOnFormClosingのようなOnXxxxイベントは、OnClickOnKeyDownなど他にもたくさんあります。

メソッドを作成する場合は、一般的なイベントと違って、デザイナから作成出来ないので、ちょっと工夫が必要です。

OnXxxxイベントの作り方

  1. 派生クラスでbase.Onまで打つ。
  2. 候補を選択する。コンパイルエラーになるけど無視する。
  3. 定義へ移動する。
  4. protected virtual void OnKeyDown(KeyEventArgs e);みたいなメソッドが見つかる。
  5. 派生クラスでoverrideして使う。
  6. 派生クラスでbase.OnXxxxの呼び出しを書く。

まとめ

常に基底クラスからイベントを発生させるると便利なこともありますが、ほとんどの場合は、派生クラスで独自の処理をしてから基底クラスのイベントを呼び出せるほうが使いやすいはずです。

派生クラスから基底クラスを呼び出す方法を使えば、この処理が簡単に実現できます。

フォームロード(Form_Load)時のフォーカスの仕方が知りたい方は「【C#】フォームロード(Form_Load)時にフォーカスする方法」をどうぞ。

スキルアップしたい初心者プログラマの方は「初心者プログラマがスキルアップするための方法」の記事もどうぞ。

それでは、また。

\ 転職を本気で考えるなら /

  • 3つの有名スクールを現役エンジニアの視点で厳選!
  • あなたに合ったおすすめのスクールを紹介!

TECH CAMP エンジニア転職」…最短10週間で転職ができる年齢無制限のスクール!

TechAcademy Pro」…オンラインで全て完結のコスパ最強スクール!

DMM WEBCAMP」…給付金の対象講座もある実力派スクール!