現在ログインしていません。
新規アカウント作成
ログイン

デリゲート変数をイベントに紐づけるには

先日のこと

私はあるプログラムのメソッド集をひたすら書き写して実行するということをしていました。

画面上にボタンを貼りつけて、ボタンを押したときにそのプログラムが実行される、という形式のものなのですが、量が多く、いちいちボタンを画面に貼りつけていては面倒だし、かといってプロジェクトをある程度の個数ごとにわけるのもおっくうだと感じていました。

そこで、ボタンの元となる連続するデータより動的にボタンを生成する、という方式にしてはどうかと思いつきました。

Public Class Form1
    ''' <summary>
    ''' ボタンクリックイベントのデリゲート定義
    ''' </summary>
    ''' <param name="s"></param>
    ''' <param name="e"></param>
    Private Delegate Sub btnClickEv(ByVal s As ObjectByVal e As EventArgs)

    ''' <summary>
    ''' ボタンの元となるデータ
    ''' </summary>
    Private Class ButtonSeedDatum
        Public Property caption As String
        Public btnEv As btnClickEv
    End Class

    ''' <summary>
    ''' 連続したボタンのもととなるデータの取得
    ''' </summary>
    ''' <returns></returns>
    Private Iterator Function GetBtnData() As IEnumerable(Of ButtonSeedDatum)

        Yield New ButtonSeedDatum() With {
        .caption = "イ",
        .btnEv = Sub(s, e)
                     MsgBox("イロハのイ!")
                 End Sub
        }

        Yield New ButtonSeedDatum() With {
        .caption = "ロ",
        .btnEv = Sub(s, e)
                     MsgBox("イロハのロ!")
                 End Sub
        }

        Yield New ButtonSeedDatum() With {
        .caption = "ハ",
        .btnEv = Sub(s, e)
                     MsgBox("イロハのハ!")
                 End Sub
        }

    End Function

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    Public Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        Const ROW_HEIGHT As Integer = 30
        Const COL_WIDTH As Integer = 150
        Dim row As Integer = 0
        For Each btnDatum In GetBtnData()
            Dim btn As New Button()
            btn.Top = row * ROW_HEIGHT
            btn.Height = ROW_HEIGHT
            btn.Width = COL_WIDTH
            btn.Text = btnDatum.caption
            ' ボタンのイベントを紐づける
            AddHandler btn.Click, AddressOf btnDatum.btnEv '←★ここでエラーが出る。
            ' ボタンをあらかじめ貼りつけておいたパネルに追加する。
            ' パネルの AutoScroll プロパティは True にしておく。
            Me.Panel1.Controls.Add(btn)
            row += 1
        Next
    End Sub
End Class
■リスト1:当初の目論見

上記ソースコードの★のコメント中にエラーが出ました。

いわく、「'AddressOf'オペランドはメソッドの名前でなければなりません。かっこは不要です」とのことです。

つまり、btnDatum.btnEv の箇所がメソッドだと認識されていないようなのです。

しかし、一般的にデリゲートのメソッドは以下のように呼ばれます。

    Private Delegate Sub Dlg()

    Private Sub CallDelegate()
        Dim d As Dlg
        d = Sub()
                MsgBox("ア!")
            End Sub
        d() 'これで呼び出し
    End Sub
■リスト2:一般的なデリゲートメソッドの呼び出し

たしかにこれでも呼べるのですが、より堅苦しい(というかこちらが正式で、上記はシンタックスシュガー?)呼びかたとしては、

    Private Delegate Sub Dlg()

    Private Sub CallDelegate()
        Dim d As Dlg
        d = Sub()
                MsgBox("ア!")
            End Sub
        d.Invoke() 'これで呼び出し
    End Sub
■リスト3:堅苦しいデリゲートメソッドの呼び出し

と、Invoke をつけます。

Invoke はメソッドですから、そうなると、これを使えばメソッドとして認識してもらえそうです。

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    Public Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        Const ROW_HEIGHT As Integer = 30
        Const COL_WIDTH As Integer = 150
        Dim row As Integer = 0
        For Each btnDatum In GetBtnData()
            Dim btn As New Button()
            btn.Top = row * ROW_HEIGHT
            btn.Height = ROW_HEIGHT
            btn.Width = COL_WIDTH
            btn.Text = btnDatum.caption
            ' ボタンのイベントを紐づける
            AddHandler btn.Click, AddressOf btnDatum.btnEv.Invoke '←★エラーはもうでない。
            ' ボタンをあらかじめ貼りつけておいたパネルに追加する。
            ' パネルの AutoScroll プロパティは True にしておく。
            Me.Panel1.Controls.Add(btn)
            row += 1
        Next
    End Sub
■リスト4:修正したコンストラクタ

上記のように修正して実行すると、パネルにボタンが縦に並び、押すとそれぞれのメッセージボックスが表示されるようになります。

私はたまたま Invoke メソッドの存在を知っていましたが、知らなければ「デリゲート変数をイベントに紐づけることはできないんだ」と結論づけるところでした。

もっとも、このようなコーディングをするのはレアケースと思われます。

もしこの情報がお役に立てたなら嬉しいです。