Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Visual Studio C#でデリゲートを使い超シンプルなコールバックイベントを実装する

More than 5 years have passed since last update.
2015/08/27追記:コメントから「デリゲートとコールバックイベントをわざと分かりにくくしていないか?」とのご指摘があり、ソースコードの方を整理し直しました。

デリゲート=委譲

などと、よく説明されますが、そもそも「委譲」ってなに?と、辞書で調べ直した人は、僕だけではないはず(笑)。

もう用語からして分かりにくい。いろいろなウェブサイトを巡回してみても、サンプルソースも分かりにくい。

じゃあ、もういいや。コピペして手探りで、イベントハンドラとして使おうとすると、ふと途中で、自分が何をしているのか分からなくなってくる始末。ソースがゲシュタルト崩壊していく。

これではマズイと、今回は、自分自身がイベントハンドラ、デリゲートの理解の整理と、超必要最低限のサンプルプロジェクトを作ってみようというのが今回の企画です。

これ作ります。↓

event_handler.png

赤色のように、From1(ここではメインフォーム)で受け取りたいコールバックイベントを定義し、イベントハンドラとして、Form2へ埋め込みます。

ここでの「デリゲート」とは、イベント処理を「差し込む」ための方法 と言えるでしょうか。

今回は、Form2 に寄生するような形で入っていますが、クラスとして独立していても良いでしょう。処理は、連番と現在日時を返すという単純なものにしました。

Form2.cs
// 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 クラスは、容易に拡張できないものかと思い込んでいましたが、いろいろ調べると、かなり自由度高く作れます(っていうかクラスなので、継承して拡張すれば良し)。

Form2.cs
// 渡せるイベントデータ引数、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をロードするときにイベントハンドラを追加します。

Form1.cs
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();
}

実際のイベントを受ける、コールバックイベントはこちら。

Form1.cs
//-----------------------------------
// コールバックイベント
//-----------------------------------
private void CallBackEventProgress(Form2.MyEventArgs e)
{
  textBox1.AppendText(e.TestNumValue.ToString() + ":" + e.TestStringValue);
}

これで、あとは図にあるように、青色の流れでイベントが実行されます。

Form1にある「実行」ボタンを押してはいますが(イベント発生させてますが)、実際は、Form2を表示するだけのものです。

Form2の中にあるTimer1が、フォームロードとともに、断続的にイベントを起こし、Form1はコールバックイベントとして受け取り、ログとして表示していきます。

doing.png

意外と呆気なくできました。簡単ですね。

Form1.csと、Form2.csのソースコードは以下の通りです。

Form1.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);

    }

  }

}
Form2.cs
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」で動作を確認済みです。

hibara
主にWindowsフリーソフトで、「アタッシェケース」という暗号化ツール、Markdown記法に対応した「MarkDown#Editor」などを開発・公開しています。
https://hibara.org/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away