Excelオブジェクト
MS-Excelは,ワークブックからセルの条件付き書式までExcelのあらゆる構成要素をVBAから管理,操作するためのオブジェクトモデルを提供しています。これをExcelオブジェクトモデルと呼びます。開発環境(VBE)のプロジェクトエクスプローラーには,各プロジェクトの下にMicrosoft Excel Objectsというグループがあり,ワークブック,ワークシートなどに対応するオブジェクトモジュールが表示されます。プロジェクトはワークブックごとに割り当てられ,Excelオブジェクトモデルは現実のワークブックの構成を反映します。たとえば,Excelの画面からマニュアル操作でワークシートを追加すると対応するワークシートオブジェクトが自動的に用意され,ワークシートを削除すると対応するオブジェクトも消滅します。
Excelオブジェクトはいつでも使用可能です。たとえば,Excelのウィンドウで1枚目のワークシート(上の例ではシート見出し"Q1")が表示されているときにイミディエイトウィンドウから次のステートメントを実行すれば,2枚目のワークシート(上の例ではシート見出し"Q2")が前面に出てきます。これは,Sheet2が2枚目のワークシート"Q2"に対応するオブジェクトで,Selectがそのワークシートにフォーカスを移すメソッドだからです。
Sheet2.Select
また,次のステートメントは1枚目のワークシートのシート見出しをイミディエイトウィンドウに出力します。
Debug.Print Sheet1.Name
VBEのコードエディタでオブジェクトモジュールにユーザー定義プロシージャを編集します。このとき,エディタ上部の2つのリストボックスからオブジェクトとイベントを選択することでイベントハンドラを自動で追加することができます。次のコードでは,ワークシートオブジェクトのSheet1モジュールにWorksheet_Activeイベントハンドラとユーザー定義のPropertyプロシージャReportTypeを追加しています。
|
Private Sub Worksheet_Activate() Msgbox "This sheet was activated." End Sub Public Property Get ReportType() As String ReportType = "Sales Result" End Property |
||
1~3行目のイベントハンドラは,Excel本体においてこのシート名"Q1"のワークシートにフォーカスが移るたびに呼び出され,メッセージボックスを表示します。5~7行目のPropertyプロシージャは別のコードから呼び出して使います。たとえばイミディエイトウィンドウで次のステートメントを実行すると,"Sales Result"という文字列を出力します。
Debug.Print Sheet1.ReportType
一見すると,モジュールに追加したプロシージャがこのように使用できるようになるのは,当然のように思えます。しかし,一般的なクラスとインスタンスとの関係に当てはめてみると,実はいくつか不思議な点があります。一点目は,そもそもSheet1とは何かということです。Sheet1オブジェクトモジュールを編集してプロシージャを追加しているのだから,ここでのSheet1とはクラスだと考えられます。一方,Sheet1.ReportTypeとしてプロパティが取得できるということは,ここでのSheet1は必然的にオブジェクト参照です。つまり一概にSheet1といっても,状況によって型であるクラスと実体であるオブジェクトという異質のものを指しているのです。
もう一つは,Sheet2.SelectのSelectメソッドやSheet1.NameのNameプロパティなどは,それぞれのモジュールでは定義されていないのになぜ使用できるのかということです。
2点とも,Excelが特別にそういう仕組みを提供しているのだ,の一言で片づけることもできますが,もう少し掘り下げてExcelオブジェクトの実装が,VBAの言語体系の枠組みの中でどのように実現されているのかについて考えてみたいと思います。
Excelオブジェクトの解釈
Sheet1クラスとSheet1オブジェクトについては,両者を別物と考えれば説明がつきます。VBAではクラス名と同じ変数名を定義することができるので,Excel側はSheet2型のグローバルオブジェクト参照変数Sheet2を用意しておき,常にこの変数がワークシートSheet2に対応するオブジェクトを参照するようにしておけばよいのです。これについて,次のようにすると,Excelオブジェクトを管理するためにExcelが内部で何をしているかを垣間見ることができます。
まず,次のコードをSheet2モジュールに追加します。ObjPtrは,アンサポート関数で,オブジェクトが置かれているメモリ上のアドレスを返します。
|
Private m_Dummy As Integer Public Sub PrintPointer() Debug.Print " ObjPtr of Me: " & Hex(ObjPtr(Me)) End Sub |
||
Sheet2.PrintPointer続いて,Sheet2モジュールの1行目を削除してからもう一度上記のステートメントを実行します。すると,今度は最初とは違うアドレスが表示されるはずです。
Sheet2.PrintPointer ObjPtr of Me: 70BCAF0 Sheet2.PrintPointer ObjPtr of Me: 70BB980つまり,Sheet2は常にワークシートSheet2に対応するオブジェクトを参照していますが,参照先は必要に応じて入れ替わっているのです。削除した1行目はクラスのメンバー変数定義でした。メンバー変数の定義が変わればオブジェクトのデータ構造が変化するので,以前に存在したオブジェクトは無効になり,新たなオブジェクトを作り直すしかないのです。
実は,まったく同じ仕組みをユーザー定義クラスで提供することもできます。ユーザー定義クラスに上記のコードを追加してポインタ表示後,変数宣言部分を削除してから再度ポインタを表示します。もちろんユーザー定義クラスの場合はオブジェクトが自動的に生成されないので,標準モジュールでオブジェクト変数を明示的に宣言しておく必要があります。下のようにNewキーワード付きなら必要に応じてオブジェクトが自動生成されます。
1: |
Public g_Object As New MyClass |
||
Excelオブジェクトがこのようなグローバルオブジェクトと違うのは,オブジェクトが対応付けられた「本尊」(ここでの例ではSheet2ワークシート)に同期して生成・解放されるという点だけです。(※補足)
さて,Excelオブジェクトといえども通常のクラスオブジェクトと本質的な差はないことが分かってきたところで,次のプロシージャを試してみましょう。Sheet1モジュールがSheet1クラスの定義であるならば,Sheet1型のオブジェクト変数を定義することも,Sheet1型の新しいオブジェクトを作ることもできるはずです。
|
Public Sub TestSheetObject1() Dim sh As Sheet1 Set sh = Sheet1 Debug.Print sh.Name End Sub Public Sub TestSheetObject2() Dim sh As Sheet1 Set sh = New Sheet1 End Sub |
||
TestSheetObject2は,既存のオブジェクト参照Sheet1をコピーする代わりに新たなSheet1型オブジェクトを生成しようとしたものです。が,これは9行目が「Newキーワードの使用法が不正です。」というコンパイルエラーになります。これは,Excelオブジェクトが参照設定された別プロジェクト(Microsoft Excel Object Library)で定義されていることが理由で,VBAの仕様としてプロジェクト外から生成済みのオブジェクトにアクセスすることはできても,新規オブジェクトの生成はできないのです。もっとも同じワークブックに対応するオブジェクトをいくつも作られてもシステムとしては管理に困るでしょう。
もう一つ,定義されていないSelectやNameといったメンバーメソッドやメンバープロパティがなぜSheet1オブジェクトやSheet2オブジェクトで使用できるのかという疑問を検証するために以下のコードを試してみましょう。
|
Public Sub TestSheetObject() Dim sh As Worksheet Set sh = Sheet1 Debug.Print sh.Name Set sh = Sheet2 Debug.Print sh.Name End Sub |
||
このプロシージャを実行するとSheet1とSheet2のNameプロパティが出力されます。つまり,Worksheet型オブジェクト参照のshは,Sheet1型オブジェクトもSheet2型オブジェクトも参照することができるのです。これは,まさにSheet1クラスやSheet2クラスがWorksheetクラスから派生している場合の振る舞いです。VBAの言語使用としては継承はサポートされていませんが,それはユーザー定義クラスについての話で,システムが提供するExcelオブジェクトモデルにおいては散見されるようです。少なくともそのように理解すると,実装されていないはずのメソッドやプロパティがSheet1クラスやSheet2クラスのオブジェクトについて使用できることの説明がつきます。つまり,それらは基底クラスであるWorksheetクラスで定義されているのです。実際にVBEのオブジェクトブラウザで確認すると,WorksheetクラスのすべてのメンバーメソッドやメンバープロパティがSheet1やSheet2等の個別ワークシートに対応するクラスでも使用可能になっていることがわかります。
同様の継承は,他のオブジェクトクラスにおいても見られます。主なところは次の通りです。
Excelオブジェクト | 基底クラス名 | 派生クラス名 |
ワークブック | Workbook | ThisWorkbook |
ワークシート | Worksheet | Sheet1,Sheet2,... |
グラフシート | Chart | Graph1,Graph2,... |
ツールバーコマンド, ショートカットメニュー |
CommandBarControl | CommandBarButton CommandBarComoboBox CommandBarPopup |
まとめ
以上をまとめると次の通りです。- Microsoft Excel Objectsに表示されているのは,Excelオブジェクトに対応するクラス名であると同時に,唯一のインスタンスを参照するオブジェクト変数名でもある。
- Excelオブジェクトクラスは派生クラスであり,その基底クラスであるWorksheetクラスやWorkbookクラスが同種のオブジェクトに共通の機能を実装する。