昨日追加したメニュー項目には、
まだ処理を関連付けていないため、
選んでも何も起こらない。

メニューに処理をさせるには 2 種類の方法がある。
ひとつは、CommandBarButton の OnAction プロパティ、
もうひとつは、CommandBarButton の Click イベントだ。

前者は、簡単に利用できる手法である。
OnAction プロパティに、マクロの名前を指定すれば
メニューが選択されたときに自動的に呼び出される。

ここには、Excel で利用できる参照式や、
VBA のサブルーチン呼び出し構文などが利用でき、柔軟だが、
文字列による指定となるので、名前が重なる場合は困る。

後者は、従来のようにイベントを受け取って処理する方法だ。

アドインとして作ることを考えるならば、
後者の方法を使うほうが確実であり、無難である。

では、どこでイベントを受け取るか。
イベントを受け取るためには、クラスが必要だ。
また、いつメニューが押されるかは分からないわけだから、
常時存在しているクラスが必要となる。

実は、このような用途にうってつけのクラスがある。

Excel のワークブックやアドインには、
ThisWorkbook という Workbook から派生したクラスがあり、
これはブック自体を表すクラスとなる。
このクラスのインスタンスは常に存在するのだ。

また、ThisWorkbook は、ブック自体のイベントを持つので、
ブックやシートで起きたイベントを処理することもできる。

では、イベントを拾ってみよう。

ThisWorkbook のフィールドとして、
CommandBarButton をイベントつきで宣言する。

    Private WithEvents m_oSolveButton As CommandBarButton

定数を幾つか定義しておく。これらは後で利用する。

    Private Const SOLVE_CAPTION As String = _
            "ののぐらむ自動解答(&N)"
    Private Const SOLVE_TAG As String = _
            "{96caf1c3-2c99-4032-94f2-7522dc8fabdb}"


ブックが開かれたときには、Open イベントが発生するので、
それにあわせてメニューを登録する。

    ' アドイン/ワークブックが開かれた
    Private Sub Workbook_Open()
        On Error Resume Next
        Call RegisterMenuItem
    End Sub

メニューの登録は、ヘルパメソッドである
RegisterMenuItem で実行する。

    Private Sub RegisterMenuItem()

        Dim oMenuBar        As CommandBar
        Dim oToolsMenu      As CommandBarPopup
        Dim oToolsMenuBar   As CommandBar
        Dim oMacroMenu      As CommandBarControl
   
        ' ワークシート用メニューを取得
        Set oMenuBar = Application.CommandBars("Worksheet Menu Bar")

        ' メニュー項目が存在するかどうか調べる
        Set m_oSolveButton = oMenuBar.FindControl(, , SOLVE_TAG, , True)
   
        ' 存在した場合は終了
        If m_oSolveButton Is Nothing = False Then Exit Sub
   
        ' 「ツール」メニュー項目を取得
        Set oToolsMenu = oMenuBar.Controls("ツール(&T)")
   
        ' 「ツール」メニュー項目のサブメニューを取得
        Set oToolsMenuBar = oToolsMenu.CommandBar
   
        ' 「マクロ」メニュー項目を取得
        Set oMacroMenu = oToolsMenu.Controls("マクロ(&M)")

        ' 「マクロ」メニュー項目の直前に新しいメニュー項目を追加
        Set m_oSolveButton = oToolsMenuBar.Controls.Add( _
                msoControlButton, , , oMacroMenu.Index)
   
        ' メニューの情報を設定
        m_oSolveButton.Caption = SOLVE_CAPTION
        m_oSolveButton.Tag = SOLVE_TAG
   
    End Sub

基本は、昨日やったメニュー登録と同じだが、
Tag プロパティに一意のデータを登録しておき、
メニューがすでに格納されているかどうか調べる。
FindControl メソッドを使うと、Tag を元に、
メニュー項目の参照を探し当てることができる。

Excel にメニューを登録した場合、
それは記憶され、Excel を終了しても残るのだ。
なので、すでに項目が存在するのに追加してしまうと、
同じ名前のメニューが無限増殖してしまう。
それを避けるためのコードを入れたわけだ。

また、m_oSolveButton に参照を格納することで、
m_oSolveButton の Click イベントを受け取ることができる。


    Private Sub m_oSolveButton_Click( _
            ByVal Ctrl As Office.CommandBarButton, _
            CancelDefault As Boolean)

        Dim oAction As SolveAction
   
        On Error GoTo ErrorHandler
   
        Set oAction = New SolveAction
        Call oAction.Execute
    Exit Sub

    ErrorHandler:
        Call MsgBox("エラーが発生しました。" & _
                Err.Description, vbExclamation)
    End Sub

イベントハンドラには引数があるが、
今のところは無視してかまわない。
これらは、既定のボタンの処理を上書きする際に利用する。

ハンドラさえ呼び出されれば後は同じだ。
ここから Solve マクロを呼び出しても構わないが、
アドインとしてはマクロは使えないので、
将来 Solve マクロを削除することを視野に入れておこう。

また、最後の砦となるエラー処理コードも入れておく。
基本的に、イベントハンドラではエラー処理は必須だ。

いったん保存し、Excel を終了させる。
改めて Excel を開き、Nonogram.xls と、
問題が入ったブックを開く。

Nonogram.xls を開いたときにメニューに項目が追加され、
問題が入ったブックをアクティブにしても、
Excel のメインメニューに登録されている。

解答欄を選択し「ツール」⇒「ののぐらむ自動解答」と選ぶ。

よし、実行された。