2015/08/27追記:コメントから「デリゲートとコールバックイベントをわざと分かりにくくしていないか?」とのご指摘があり、ソースコードの方を整理し直しました。
デリゲート=委譲
などと、よく説明されますが、そもそも「委譲」ってなに?と、辞書で調べ直した人は、僕だけではないはず(笑)。
もう用語からして分かりにくい。いろいろなウェブサイトを巡回してみても、サンプルソースも分かりにくい。
じゃあ、もういいや。コピペして手探りで、イベントハンドラとして使おうとすると、ふと途中で、自分が何をしているのか分からなくなってくる始末。ソースがゲシュタルト崩壊していく。
これではマズイと、今回は、自分自身がイベントハンドラ、デリゲートの理解の整理と、超必要最低限のサンプルプロジェクトを作ってみようというのが今回の企画です。
これ作ります。↓
赤色のように、From1(ここではメインフォーム)で受け取りたいコールバックイベントを定義し、イベントハンドラとして、Form2へ埋め込みます。
ここでの「デリゲート」とは、イベント処理を「差し込む」ための方法 と言えるでしょうか。
今回は、Form2 に寄生するような形で入っていますが、クラスとして独立していても良いでしょう。処理は、連番と現在日時を返すという単純なものにしました。
// Form1へ伝えるイベントハンドラを定義
public event MyEventHandler MyProgressEvent;
// Form1へイベントを伝える関数を定義
private void UpdateProgress(int TestNumValue, string TestStringValue)
{
MyProgressEvent(new MyEventArgs(TestNumValue, TestStringValue));
}
//-----------------------------------
//イベントハンドラのデリゲートを定義
public delegate void MyEventHandler(MyEventArgs e);
Microsoftが用意しているイベントハンドラ用の、EventArgs クラスは、容易に拡張できないものかと思い込んでいましたが、いろいろ調べると、かなり自由度高く作れます(っていうかクラスなので、継承して拡張すれば良し)。
// 渡せるイベントデータ引数、EventArgsを継承したクラスを拡張
public class MyEventArgs : EventArgs
{
private readonly int _TestNumValue;
private readonly string _TestStringValue;
public MyEventArgs(int TestNumValue, string TestStringValue)
{
_TestNumValue = TestNumValue;
_TestStringValue = TestStringValue;
}
public int TestNumValue { get { return _TestNumValue; } }
public string TestStringValue { get { return _TestStringValue; } }
}
あとは、From1 から、Form2をロードするときにイベントハンドラを追加します。
private void buttonExe_Click(object sender, EventArgs e)
{
// Form2の表示
Form2 frm2 = new Form2();
// メインウィンドウ(Form1)の横に表示
frm2.Left = this.Left + this.Width;
frm2.Top = this.Top;
//-----------------------------------
// イベントハンドラの追加(コールバックイベントの追加)
//-----------------------------------
frm2.MyProgressEvent += new Form2.MyEventHandler(CallBackEventProgress);
frm2.ShowDialog();
frm2.Dispose();
}
実際のイベントを受ける、コールバックイベントはこちら。
//-----------------------------------
// コールバックイベント
//-----------------------------------
private void CallBackEventProgress(Form2.MyEventArgs e)
{
textBox1.AppendText(e.TestNumValue.ToString() + ":" + e.TestStringValue);
}
これで、あとは図にあるように、青色の流れでイベントが実行されます。
Form1にある「実行」ボタンを押してはいますが(イベント発生させてますが)、実際は、Form2を表示するだけのものです。
Form2の中にあるTimer1が、フォームロードとともに、断続的にイベントを起こし、Form1はコールバックイベントとして受け取り、ログとして表示していきます。
意外と呆気なくできました。簡単ですね。
Form1.csと、Form2.csのソースコードは以下の通りです。
using System;
using System.Windows.Forms;
namespace EventHandler
{
public partial class Form1 : Form
{
public static TextBox textBox = new TextBox();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void buttonExit_Click(object sender, EventArgs e)
{
//アプリケーションを閉じる
Application.Exit();
}
private void buttonExe_Click(object sender, EventArgs e)
{
// Form2の表示
Form2 frm2 = new Form2();
// メインウィンドウ(Form1)の横に表示
frm2.Left = this.Left + this.Width;
frm2.Top = this.Top;
//----------------------------------------------------------------------
// イベントハンドラの追加(コールバックイベントの追加)
//----------------------------------------------------------------------
frm2.MyProgressEvent += new Form2.MyEventHandler(CallBackEventProgress);
frm2.ShowDialog();
frm2.Dispose();
}
//----------------------------------------------------------------------
// コールバックイベント
//----------------------------------------------------------------------
private void CallBackEventProgress(Form2.MyEventArgs e)
{
textBox1.AppendText(e.TestNumValue.ToString() + ":" + e.TestStringValue);
}
}
}
using System;
using System.Windows.Forms;
namespace EventHandler
{
public partial class Form2 : Form
{
//イベントで表示するカウンタの初期化
public int i = 0;
//-----------------------------------
// Form2コンストラクタ
public Form2()
{
InitializeComponent();
//イベントで表示する内容の初期化
labelNumber.Text = "0";
labelDateTime.Text = "-";
}
//-----------------------------------
// フォームをロード
private void Form2_Load(object sender, EventArgs e)
{
//タイマーイベント・スタート
timer1.Enabled = true;
}
//-----------------------------------
// タイマーイベントで定期的にイベントを発生させる
private void timer1_Tick(object sender, EventArgs e)
{
DateTime dt = DateTime.Now;
i++;
// Form2側にも表示
labelNumber.Text = i.ToString();
labelDateTime.Text = dt.ToString("yyyy/MM/dd (ddd) HH:mm:ss" + "\n");
//-----------------------------------
// Form1側のアップデート(コールバック)
//-----------------------------------
UpdateProgress(i, labelDateTime.Text);
}
//-----------------------------------
// フォームを閉じるボタン
private void buttonExit_Click(object sender, EventArgs e)
{
this.Close();
}
//-----------------------------------
// Form1へ伝えるイベントハンドラを定義
public event MyEventHandler MyProgressEvent;
// Form1へイベントを伝える関数を定義
private void UpdateProgress(int TestNumValue, string TestStringValue)
{
MyProgressEvent(new MyEventArgs(TestNumValue, TestStringValue));
}
//-----------------------------------
//イベントハンドラのデリゲートを定義
public delegate void MyEventHandler(MyEventArgs e);
//-----------------------------------
// 渡せるイベントデータ引数、EventArgsを継承したクラスを拡張
public class MyEventArgs : EventArgs
{
private readonly int _TestNumValue;
private readonly string _TestStringValue;
public MyEventArgs(int TestNumValue, string TestStringValue)
{
_TestNumValue = TestNumValue;
_TestStringValue = TestStringValue;
}
public int TestNumValue { get { return _TestNumValue; } }
public string TestStringValue { get { return _TestStringValue; } }
}
}
}
これらのプロジェクトファイルを含むサンプルファイルは、GitHubの方へ上げておきました。実際に動かしながら見ると、その流れも掴みやすいかと思います。
ライセンスは、MITライセンスで、「Visual Studio Express 2013 for Windows Desktop」で動作を確認済みです。