3.4 イベントクラスの管理と実装(後編)

前項では、

以上の機能を実件するために、

を実装しました。

本章では、特にあるシートから別のシートを操るための処理に注目して話を進めたいと思います。

さて、まずは前章のDecoratorとfoo.xlsをリンクさせるための標準モジュールをもう一度示します。


public bdeco As New BookDecorator

Sub Test()
    bdeco.Initialize Workbooks("foo.xls")
End Sub

このプログラムには、以下の2つの問題点があります:

後者について少し補足します。もし、foo.xls自体にマクロを実装した場合、ガーベジコレクション機能によって、参照されなくなったとき、すなわちfoo.xlsが閉じられたときにDecoratorは解放されます。しかし、別に実装した場合は、実装したブックを閉じるまでDecoratorは解放されません。ここでは簡便のために1つのブックしか扱わないので問題ではありませんが、複数のブックを扱う場合に問題になります。

この2つの問題点は、以下のような機能を実装すれば解決できます:

これらの機能は、ブックを開いたり、閉じたりするイベントをフックする事で実現できます。ブックを扱うイベントは、前章で示した3つのレベルで言えば、Excelアプリケーションのレベルになります。したがって、アプリケーションに機能を追加するAppDecoratorクラスを作成し、必要な処理を実装すればよいことになります。

実装の方に話を移しましょう。まずは、AppDecoratorの実装を全て示します:


Private WithEvents app As Application
Private bookDeco As BookDecorator

Public Sub Initialize()
    Set app = Application
    Dim wb As Workbook
    For Each wb In Workbooks
        If wb.Name = "foo.xls" Then
            Set bookDeco = New BookDecorator
            bookDeco.Initialize wb
        End If
    Next
End Sub

Private Sub Class_Terminate()
    Set bookDeco = Nothing
End Sub

Private Sub app_WorkbookOpen(ByVal wb As Workbook)
    If wb.Name = "foo.xls" Then
        Set bookDeco = New BookDecorator
        bookDeco.Initialize wb
    End If
End Sub

Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    If wb.Name = ("foo.xls") Then
        Set bookDeco = Nothing
    End If
End Sub

では部分ごとに見ていきましょう。まずはメンバ変数です。


Private WithEvents app As Application
Private bookDeco As BookDecorator
ここで、appはイベントをフックする先のオブジェクトで、ここではApplicationオブジェクトを指しています。bookDecoはfoo.xlsのBookDecoratorを保持しておくための変数です。


Public Sub Initialize()
    Set app = Application
    Dim wb As Workbook
    For Each wb In Workbooks
        If wb.Name = "foo.xls" Then
            Set bookDeco = New BookDecorator
            bookDeco.Initialize wb
        End If
    Next
End Sub

初期化の部分です。まずapp変数でアプリケーションとマクロのリンクを設定し、その後foo.xlsが開いていたらBookDecoratorを確保します。ここで、foo.xlsの存在はWorkbooksコレクションで調べています。


Private Sub Class_Terminate()
    Set bookDeco = Nothing
End Sub

Class_という始まりのメソッドは、クラスに関するイベントを表します。Class_Initialize()はそのクラスのインスタンスが確保された直後に、Class_Terminate()は逆に解放される直前に呼び出されます(前者がコンストラクタ、後者がデストラクタですね)。これらの処理はイベントなので、イベントの中身だけを書いておけば勝手に呼び出されるので、呼び出し忘れを未然に防ぐことができます。

ここではClass_Terminate()イベントのみをフックしています。ここでは、解放が失敗したときのために、Decoratorを明示的に解放しています。


Private Sub app_WorkbookOpen(ByVal Wb As Workbook)
    If Wb.Name = "foo.xls" Then
        Set bookDeco = New BookDecorator
        bookDeco.Initialize Wb
    End If
End Sub

Private Sub app_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
    If Wb.Name = ("foo.xls") Then
        Set bookDeco = Nothing
    End If
End Sub

この部分は非常に単純です。ApplicationのWorkbookOpenイベントとWorkbookBeforeCloseイベントをフックし、変数bookDecoの確保・解放をおこなっています。

これまで、AppDecoratorの実装について説明してきました。AppDecoratorはBookDecoratorを、BookDecoratorはSheetDecoratorを(前項参照)それぞれ管理していますから、マクロを書いたブックは、AppDecoratorだけを管理すれば十分です。そこで、マクロを書いたブックを開いたときにAppDecoratorの取得と初期化をするルーチンを追加すればよいことになります。しかし、自分自身の初期化はClass_Initializeイベントで設定されていますから、単純にAppDecoratorのインスタンスを作るだけで大丈夫です:


[AppDecoModule]
Public appDeco As New AppDecorator

Sub Initialize()
	appDeco.Initialize
End Sub

これで、全ての機能を実装することが出来ました。