2019年10月18日金曜日

【.netFramework】フォーム継承利用時のイベントハンドラの処理順

ある程度のシステムになると、基底フォームを作成して共通処理を実装し、個別は継承先のフォームで実装する。というケースが多くなると思います。

キーボードイベントの処理順がうまくいかないと技術者から聞かれ、過去の記憶を確認すべく、サンプル書いて確認しました。(サンプルコード(zip)は記事の一番下に置いてあります)

【結果】

OnKey****の場合、継承先から順番に処理される。(上書き用途)
Key****のイベントハンドラの場合、基底から処理される

なお、本検証結果は「一般的な実装方法を用いた場合」の結果であり、色々とコードで工夫をなさった場合にはこの限りではありません。例)On***系の実装でbase.On***を呼ばない。とか、基底のイベントハンドラを登録するタイミングを継承先で指定するなど、振る舞いを変える方法はあると思います。

【検証した方法】

1.以下3つのフォームを用意する
  ・基底フォーム (TestBaseForm)
  ・基底を継承したフォーム (InheritForm1)
  ・さらに継承したフォーム(InheritInheritForm2)
2.それぞれに、同じメソッド、イベントを実装
3.プログラムを実行して、順番を把握

【実装イメージ】

【テストコード】


/// 同じコードを継承先のフォームにも記載
public partial class TestBaseForm : Form
{
    string _myFormName = "TestBaseForm"; // ここは実装するフォーム名にする
    public TestBaseForm()
    {
        InitializeComponent();
        this.KeyPreview = true;
        this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form_KeyDown);
        this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.Form_KeyPress);
        this.KeyUp += new KeyEventHandler(this.Form_KeyUp);
    }
    protected override void OnKeyDown(KeyEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
        base.OnKeyDown(e);
    }
    protected override void OnKeyPress(KeyPressEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
        base.OnKeyPress(e);
    }
    protected override void OnKeyUp(KeyEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
        base.OnKeyUp(e);
    }
    private void Form_KeyDown(object sender, KeyEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
    }
    private void Form_KeyUp(object sender, KeyEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
    }
    private void Form_KeyPress(object sender, KeyPressEventArgs e)
    {
        System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _myFormName, System.Reflection.MethodBase.GetCurrentMethod().Name));
    }
}

【実行ログ】

Log内容 Method 基底 継承1 継承2
InheritInheritForm2:OnKeyDown OnKeyDown    
InheritForm1:OnKeyDown OnKeyDown    
TestBaseForm:OnKeyDown OnKeyDown    
TestBaseForm:Form_KeyDown KeyDown    
InheritForm1:Form_KeyDown KeyDown    
InheritInheritForm2:Form_KeyDown KeyDown    
InheritInheritForm2:OnKeyPress OnKeyPress    
InheritForm1:OnKeyPress OnKeyPress    
TestBaseForm:OnKeyPress OnKeyPress    
TestBaseForm:Form_KeyPress KeyPress    
InheritForm1:Form_KeyPress KeyPress    
InheritInheritForm2:Form_KeyPress KeyPress    
InheritInheritForm2:OnKeyUp OnKeyUp    
InheritForm1:OnKeyUp OnKeyUp    
TestBaseForm:OnKeyUp OnKeyUp    
TestBaseForm:Form_KeyUp KeyUp    
InheritForm1:Form_KeyUp KeyUp    
InheritInheritForm2:Form_KeyUp KeyUp    

【まとめ】

フォームを継承して利用する際には、継承元に実装するロジックの用途、継承先での振る舞いを考慮したうえで実装を決めることをお勧めします。

【ソース】

20191018_KeyboardEventOrderTest_source.zip

【追記:個人的意見】

※あくまでも、イベントベースの共通ロジックを基底フォームで実装する際のお話。
・基底に実装する場合、Onで実装すべき。それにより、継承先で振る舞いを変えられる。
・Onメソッドで実装するとした場合、Onメソッド内に直接実装してしまうと継承先で処理を変えられないので、基底のOnメソッドでは継承可能なメソッドを呼び出すだけとし、継承先では必要に応じてメソッドをOverrideできるようにした方が良い。
この場合にMethodA()とMethodB()に依存関係があるようであれば、そもそもの実装見直したほうがよい。(この辺は基本的なクラス設計の概念に準ずる)

・理想で言えば、継承先でパラメータ(プロパティ)指定により振る舞いを変えられる実装が良いと思う。(やりすぎると基底フォームの機能が複雑になってきて、使いづらい基底フォームになってしまうので塩梅は状況により調整かと思います)
 →とはいえ、プロパティを利用した設計するのであれば、デフォルトはtrueにして通るようにしておいて、継承先で不便を感じる際にfalseに変えれば日常的には困らない。


【個人的意見のまとめ】

・メソッドの呼び出し順を考慮して実装しましょう。
・基底に記載する共通ロジックの分割単位は適切に。

0 件のコメント: