VB のたまご

作成日: 2015/12/03, 更新日: 2015/12/03


イベントの仕組みを知って、自分たち専用のイベントを作ってみよう

しょっちゅう使うけど、どうなっているのか実はよく分かっていないイベント

  •  クライアントアプリケーションを作成する際、画面上に、ボタンやテキストボックス等のコントロールをペタペタ貼り付け、 ボタンクリックした時の処理や、テキスト入力欄に文字を入力した時の禁止文字チェック処理等、各処理を実装していくことになります。 ある程度作成できたところで、デバッグ実行すると、画面が表示されて、ボタンを押すと処理が走りますね。
  • Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Console.WriteLine("ボタンが押されました。")
    End Sub
    
    ' ボタンをクリックすることで、以下を表示
    ボタンが押されました。
    

  •  イベントの内部的な仕組みは分からないけど、イベントはよく使う。という方は多いのではないでしょうか。 何かの動きをさせたい場合は、イベント発信で処理を行う仕組みなので、必ずイベントの実装が必要になります。 今回は、イベント処理の舞台裏を覗いてみたいと思います。

スポンサーリンク


実際に作って、体験してみよう!

  •  それでは実際に、作ってみます。
  • ' ボタンを真似た、疑似ボタン
    Class EmulateButton
    
        'Public Delegate Sub ClickDelegate()
        'Public Event Click As ClickDelegate
        Public Event Click()
    
        Public Sub PerformClick()
            RaiseEvent Click()
        End Sub
    
    End Class
    
    Public Class Form1
    
        ' 疑似ボタンのインスタンス生成
        Private WithEvents btn As New EmulateButton
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            ' 疑似ボタンの、ボタンクリックのエミュレート
            btn.PerformClick()
    
        End Sub
    
        Private Sub btn_Click() Handles btn.Click
            Console.WriteLine("疑似ボタンのクリックイベントです。")
        End Sub
    
    End Class
    
    ' 出力結果
    疑似ボタンのクリックイベントです。
    

  •  どうでしょうか。あまりイベントっぽさが無くて、実感が湧かなかったでしょうかね?これが基本的な命令となります。

  •  イベント処理というのは、2人の担当者がいて、それぞれ協力することで成り立っています。 イベントがきっかけ担当で、メソッドが実際の処理担当です。よく、イベントハンドラという言葉を聞きますが、 イベントハンドラとは、イベントに紐づいている、イベント用のメソッドの事を言います。2人の打ち合わせは以下の通りです。

  •  何らかのアクションに対する反応は、コントロールクラス側(例、Button )で対処しますね。 この時コントロールクラス側から連絡を出しますので、コントロールを使っているクラス側(例、Form1 )では、 反応した時に行いたい処理を実装してくださいね。また、コントロールクラス側から、コントロールを使うクラス側を見た場合、 メソッドがいくつかあるけど、どのメソッドに連絡すればいいのか分からないから、メソッドに目印( Handles キーワード)を付けておくか、 イベントとメソッドを紐づける契約( WithEvents、AddHandler、RemoveHandler )をしておいてね。というルールを決めています。

  •  例えば、入力欄に入力したとか、ボタンを押したとか、何らかのアクションを起こしてイベントを発生させたら、 このメソッドを呼び出そう、という決め事をします。

  •  まず最初に、コントロールクラス側の解説です。
  • Class EmulateButton
    
        'Public Delegate Sub ClickDelegate()
        'Public Event Click As ClickDelegate
        Public Event Click()
    
        Public Sub PerformClick()
            RaiseEvent Click()
        End Sub
    
    End Class
    

  •  コントロールクラス側では、①発生させたいイベント自体と、②イベント発生のきっかけとなるメソッドを準備します。 ①は、Event キーワードの部分です。実はわざとコメントアウトしている2行がありますが、この2行と下の1行は同じ意味になります。 この2行の命令に、Delegate が入っていることにお気づきでしょうか。実は、Event は Delegate が裏方になって支えている機能なんですね。 イベントを定義したら、そのイベントを発生させるための命令を書きます。それが②で、RaiseEvent キーワードとなります。

  •  実際のコントロールの場合、画面上に配置した Visual な部分へ、マウスやキーボードからのアクションがあって、そこから、 イベント発生させるメソッドが呼ばれて、イベントが発生します。見た目の Visual な部分は作るのが難しいため、今回は省略しています。


  •  次に、コントロールを使うクラス側の解説です。
  • Public Class Form1
    
        ' 疑似ボタンのインスタンス生成
        Private WithEvents btn As New EmulateButton
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            ' 疑似ボタンの、ボタンクリックのエミュレート
            btn.PerformClick()
    
        End Sub
    
        Private Sub btn_Click() Handles btn.Click
            Console.WriteLine("疑似ボタンのクリックイベントです。")
        End Sub
    
    End Class
    

  •  コントロールを使うクラス側では、イベント発生した時に呼び出したいメソッドの準備をします。 そのコントロールのインスタンスを生成する際、WithEvents キーワードを付けることで、イベントを持った変数なんだと知らせます。 このように定義することで、メソッドの後ろに、Handles キーワードとともに、イベント名を記載することができるようになり、 目印の準備が完了します。

  •  実行時、疑似ボタンクラスの PerformClick メソッドを呼び出して、ボタンをクリックしたという前提でイベント発生させます。 すると、疑似ボタンクラス側で、イベントが発生して、連絡を受けた btn_Click メソッドが走ることになります。


  •  それでは、このサンプルに、引数を追加してみましょう。
  • ' ボタンを真似た、疑似ボタン
    Class EmulateButton
    
        Public Property Name As String = String.Empty
    
        'Public Delegate Sub ClickDelegate(sender As Object, e As EventArgs)
        'Public Event Click As ClickDelegate
        Public Event Click(sender As Object, e As EventArgs)
    
        Public Sub PerformClick()
            RaiseEvent Click(Me, New EventArgs)
        End Sub
    
    End Class
    
    Public Class Form1
    
        ' 疑似ボタンのインスタンス生成
        Private WithEvents btn As New EmulateButton With {.Name = "EmulateButton1"}
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            ' 疑似ボタンの、ボタンクリックのエミュレート
            btn.PerformClick()
    
        End Sub
    
        Private Sub btn_Click(sender As Object, e As EventArgs) Handles btn.Click
            Dim ctrlName As String = CType(sender, EmulateButton).Name
            Console.WriteLine(ctrlName & " のクリックイベントです。")
        End Sub
    
    End Class
    
    ' 出力結果
    EmulateButton1 のクリックイベントです。
    

  •  「 sender As Object, e As EventArgs 」と書くと、イベントで見慣れた引数なので、いかにもイベントっぽい感じですよね。 ロードイベントとも大差が無く見えます。基本的に、第一引数の sender には、呼び出したコントロールがセットされています。 第二引数の e には、補足資料みたいな関連データがセットされていることが多いです。

スポンサーリンク


既存ボタンを、いじってみよう!

  •  次に実際のボタンに新機能を付けて、拡張ボタンを作ってみましょう。 ここでは、ボタンクリックイベントの後で発生する、クリック後イベントを作成して、実際に使ってみます。
  • ' イベント発生時に渡す関連情報データについて、専用データを準備します。
    Public Class ClickedEventArgs
        Inherits EventArgs
    
        Public Property ElapsedTime As TimeSpan
    
    End Class
    
    ' ボタンを拡張した独自ボタンを作成します。
    Public Class ButtonEx
        Inherits Button
    
        Public Event Clicked(sender As Object, e As ClickedEventArgs)
    
        Protected Overrides Sub OnClick(e As EventArgs)
    
            Dim stopwatch As New Stopwatch
            stopwatch.Start()
            MyBase.OnClick(e)
            stopwatch.Stop()
    
            Dim eData As New ClickedEventArgs
            eData.ElapsedTime = stopwatch.Elapsed
            RaiseEvent Clicked(Me, eData)
    
        End Sub
    
    End Class
    
    ' 拡張ボタンを作成した時点で、いったんビルドすることで、拡張ボタンを画面に貼ることができるようになります。
    Public Class Form1
    
        Private Sub ButtonEx1_Click(sender As Object, e As EventArgs) Handles ButtonEx1.Click
            System.Threading.Thread.Sleep(1000)
            Console.WriteLine("ボタンのクリックイベントです。")
        End Sub
    
        Private Sub ButtonEx1_Clicked(sender As Object, e As ClickedEventArgs) Handles ButtonEx1.Clicked
            Console.WriteLine("ボタンのクリックイベントが終わったよ。")
            Console.WriteLine("クリックイベントでかかった時間:{0} 時間 {1} 分 {2} 秒", e.ElapsedTime.Hours, e.ElapsedTime.Minutes, e.ElapsedTime.Seconds)
    
        End Sub
    
    End Class
    
    ' 出力結果
    ボタンのクリックイベントです。
    ボタンのクリックイベントが終わったよ。
    クリックイベントでかかった時間:0 時間 0 分 1 秒
    

  •  クリック後イベントは、クリックイベントの後で、自動的に発生するイベントです。また、クリックイベントでかかった時間を提供する機能を設けています。 拡張ボタンクラス内の処理は結構難しい話なので詳細は省略しますが、簡単に言うと、MyBase.OnClick メソッドを呼び出すことで、 ボタンのクリックイベントを発生させています。ボタンのクリックイベント(に紐づいているメソッドの処理)が終了すると、ここに戻ってきます。 その後、時間計測したデータを渡して、クリック後イベントを発生させています。

  •  突然ですが、ここで問題です。
  • イベントに対応するメソッドが無い場合、イベントはどうなるでしょうか? 例えば、ボタンを配置したけども、ボタンイベントは使わないや、へへっ。みたいな事があった場合、どうなると思いますか?

  •  答えは、何も起きない。です。画面上の Visual な部分へアクションがあったので、コントロールクラス側はイベントを発生させます。 コントロールを使うクラス側では、誰も受け取るメソッドが無いため、何もしないで帰ります。


  •  続いては、イベントの動的な紐づけ方、外し方についてです。
  • Class EmulateButton
    
        Public Event Click()
        Public Sub PerformClick()
            RaiseEvent Click()
        End Sub
    
    End Class
    
    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            Dim btn As New EmulateButton
            btn.PerformClick()
    
            ' 動的に紐づけます。
            AddHandler btn.Click, AddressOf Me.MyClick
            btn.PerformClick()
    
            ' 動的に外します。
            RemoveHandler btn.Click, AddressOf Me.MyClick
            btn.PerformClick()
    
        End Sub
    
        Private Sub MyClick()
            Console.WriteLine("クリックしました。")
        End Sub
    
    End Class
    
    ' 出力結果
    クリックしました。
    

  •  このサンプルでは、3回クリックのエミュレートをしています。しかし、出力されたのは1回だけです。 1回目の疑似クリックでは、イベントとメソッドが紐づけられていないため、イベント発生させても受け取ってくれるメソッドがありません。 2回目の疑似クリックでは、直前で、イベントとメソッドが紐づけられたため、イベント発生することで受け取ってくれるメソッドがあり実行されます。 3回目の疑似クリックでは、イベントとメソッドが外されたため、イベント発生させても受け取ってくれるメソッドがありません。 このように、処理のタイミングに合わせて付け外しができます。 例えば、コンボボックスのデータ登録時に、イベントが走ってしまうのを抑制する時などに使います。


  •  続いては、複数イベントを1つのメソッドに紐づける、というサンプルです。 これは、場合によっては扱いやすいのですが、イベント毎にメソッドが走るため、紐づけたコントロール全体を考えながら作りこまないと、 意図しない動きになる場合があるので注意が必要です。このサンプルではあらかじめ画面上に2つのラジオボタンを配置しています。
  • Private Sub RadioButtons_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged,
                                                                                      RadioButton2.CheckedChanged
    
        Dim radio As RadioButton = CType(sender, RadioButton)
        If radio.Checked Then
            Console.WriteLine(radio.Name & " がチェックされました。")
        End If
    
    End Sub
    
    ' チェックを切り替えるたびに、出力
    RadioButton1 がチェックされました。
    RadioButton2 がチェックされました。
    


  •  続いては、逆に、1つのイベントに複数のメソッドを紐づける、というサンプルです。
  • Dim btn As New EmulateButton
    
    ' 複数のメソッドを紐づけます。
    AddHandler btn.Click, AddressOf Me.BeforeClick
    AddHandler btn.Click, AddressOf Me.AfterClick
    
    ' 1回だけクリックします。
    btn.PerformClick()
            
    Private Sub BeforeClick()
        Console.WriteLine("クリックします。")
    End Sub
    
    Private Sub AfterClick()
        Console.WriteLine("クリックしました。")
    End Sub
    
    ' 出力結果
    クリックします。
    クリックしました。
    

  •  このような処理は、滅多に使わないかなと思います、参考までにとどめておいてください。

まとめ

  •  さて、駆け足での説明でしたが、イベント処理の舞台裏が見えたでしょうか。ほとんど組み込まれたイベントで事足りることが多く、 自前イベントを作成しなければいけないという要求は少ないかと思います。しかし、イベントの仕組みを分かった上で使う方が、楽しいと思いませんか?

  •  最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク