Winフォームアプリで、複数のボタンのイベントに1つのイベントハンドラを割り当てる

たくさんのボタンを並べて、どれを押してもほとんど実行コードが変わらない場合、無駄にコードが長くなるのが気になりますね。VisualStudioを使ったイベントの登録でこのケースを考えてみましょう

オブジェクトごとにそれぞれのイベントハンドラを割り当てる

ボタン1つのサンプル

フォームにボタンを1つ置いて、クリックすると色が変わるサンプルを作ってみましょう

実行結果

作り方

フォームに1つボタンコンポーネントを置きます
ボタンのTextプロパティを「ボタン1」に変更します
ボタンのクリックイベントハンドラを作成します

ボタンをダブルクリックして、次のコードを記述します

button1.BackColor = Color.Black;
button1.ForeColor = Color.White;

ボタン3つのサンプル

同様の作業で、あとボタンを2つ追加して各ボタンの色が変わるようにしてみます

実行結果

フォームクラスのコード(全体のコード)

まず、考えられるのは次のようなコードですね
このコードでももちろん実行可能です
ただ、変更されるオブジェクトが違う(button1かbutton2かbutton3の違い)だけで同じコードにも見えます

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ButtonEventSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            button1.BackColor = Color.Black;
            button1.ForeColor = Color.White;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            button2.BackColor = Color.Black;
            button2.ForeColor = Color.White;
        }

        private void button3_Click(object sender, EventArgs e)
        {
            button3.BackColor = Color.Black;
            button3.ForeColor = Color.White;
        }
    }
}

イベントハンドラを共通で使う

イベントハンドラをまとめてみる

イベントハンドラのシグネチャ部分をみてみましょう

private void button1_Click(object sender, EventArgs e)

引数が2つありますね
今回は、このうちのsenderを使ってみます。senderは、どのオブジェクトから実行されているかが代入されています
型は、object型ですね。これは、全てのオブジェクトが継承しているクラスになります(ルートクラスと呼ばれます)
この型を宣言しておけば、全てのインスタンスが代入できるので間違いはないのですが、派生クラスのメンバーがそのままでは使えません
今回は、BackColorプロパティとForeColorプロパティですね

Buttonクラスの継承関係

Buttonクラスの継承関係は図のようになっています
ButtonBaseを継承していて、そのButtonBaseはControlを継承していて・・・・Objectクラスを継承。・・・そして終わり
全てのクラスの最後(継承元のスタート)は、Objectクラスに行き着きます
ちなみに、BackColorとForeColorプロパティは、ButtonBaseクラスのメンバーです

sender(objectクラスのインスタンス)をダウンキャストする

難しいことを書いていますが、つまりは、このsenderをButtonクラスに変換するということです

例えば、float型の変数をint型に変換(キャスト)する場合、次のようになります

float numberFloat = 1.3f;
int numberInt = (int)numberFloat;

同じように変換するには次のように記述します

(Button)sender

参考)(Button)senderは、sender as Buttonとしても動作します

ボタン1のクリックイベントを変更する

sender変数には、ボタンをクリックしたオブジェクトが代入されています
実行してみて、違いがないことを確認しましょう
senderには、button1オブジェクトが代入されているので同じように動くのです

Button button = (Button)sender;
button.BackColor = Color.Black;
button.ForeColor = Color.White;

ボタン1にクリックイベントを新しく作成する

方法1

ボタン1を選択して、イベント(雷マーク)タブをクリックして、Clickイベントのところで、ColorChangeEventHandlerとして追加します

方法2

ColorChangeEventHandlerと名前を変更します
この場合、次の「共通で使うイベントハンドラの作成」をパスして、「ボタン2、ボタン3のイベントハンドラの登録を共通化する」に進んでください

共通で使うイベントハンドラの作成

イベントハンドラの内容を追加します

private void ColorChangeEventHandler(object sender, EventArgs e)
{
    Button button = (Button)sender;
    button.BackColor = Color.Black;
    button.ForeColor = Color.White;
}

ボタン2、ボタン3のイベントハンドラの登録を共通化する

ボタン2のイベントハンドラを先程作ったColorChangeEventHandlerに再度選択し直します
ボタン3についても同じようにします

実行してみる

最初と同じようにボタンをクリックすると色が変わるのを確認します

フォームクラスのコード(全体のコード)

ずいぶんスッキリしましたね
イベントとイベントハンドラの関連付けを無くすとイベントハンドラは削除できます
button1_Click、button2_Click、button3_Click
button1クリックは、上記で名前の変更すすると残っていません

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ButtonEventSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void ColorChangeEventHandler(object sender, EventArgs e)
        {
            Button button = (Button)sender;
            button.BackColor = Color.Black;
            button.ForeColor = Color.White;
        }
    }
}

応用

反転ボタンの自作にもチャレンジしてみましょう
このチャレンジは、今のページの作りをベースにカスタマイズしたものになります