Add-In機能は開発環境を変える

 

オブジェクト指向のもう一つの効用


ちまたのアプリケーションでは、一時「アドイン」なるモノが大はやりした。これは、
アプリケーションに足りない機能を追加するものであった。何と、開発ツールである
VisualBasicにもアドインを使うことができるようになったという。一体どういうものなのか? 何ができるのか? その可能性を探ってみよう。

酒井 法雄

●アドインとは?

よく、DOSアプリケーションなどにアドインとかアドオンとか言っていたモノがあった。アプリケーション本体には元々ない機能だが、あたかも内蔵しているかのように呼び出して使うことができる小さなアプリケーションをこう呼んでいた。これは、DDEOLEあるいは最新のOLE 2.0などを使えばなんてこともないモノではあるのだが、シングルタスクのDOSの時代には重宝されたものらしい。らしいというのは、私はそういうモノを使ったことがなかったからである。だって、DOSでそういうコトをするなんて信用できないではないか。いや、そういう理由ではなかったかもしれない。単に某社製の難しいアプリケーションを使うことができなかったからだろう。

それはともかく、VisualBasic 4.0にはアドインの機能が付いたという。一体それは何のことだ? 私は首をひねった。Windowsでアドインというからには、OLE 2.0がらみのコトだろうか? それともOCXのコトか? だったら、素直にOLE Serverを使えるとか、OCXコントロールを追加できるとかそういう表現でいいのではないか? そうじゃないアドインがあるとするならば、一体何なのだろう?

実際にアドインに触れて、私は再び驚いた。確かに予想通りにOLE 2.0がらみではあった。しかし、単にOLE Serverを使うだけならアドインとは呼ばない。やはり、OLE Serverを作れ、それを呼び出せるという表現で十分である。アドインと言うのは、VisualBasicをユーザーがどう操作したかを調べ、必要に応じてVisualBasicでのユーザーの操作をコーディングでシミュレーションしてしまう機能があるからなのだ。つまり、デザイン時にプログラミングの手伝いをするツールを作るとか、バージョン管理ツールを作るとか、主としてそういった開発補助ツールを作ることに向く仕組みなのである。

このようなアドイン独自の機能は、いくつかのオブジェクトというカタチで用意されている。このオブジェクトとOLE Serverを作るクラスなどの機能を組み合わせると、アドインを作成することができるというワケだ。したがって、アドインはあくまでもOLE Serverであるが、通常のOLE Serverと違って、VisualBasicの開発環境を監視、制御するものである。

●アドインオブジェクト

アドインオブジェクトは、大きく分けて次の4つに分類される。

AddInManagerオブジェクト

これらのオブジェクトの関係は、図のようになっている。

Applicationオブジェクト

ここからも分かるように、すべてのオブジェクトはApplicationオブジェクトを親としている。このオブジェクトには、次のようなプロパティがある。ここではいちいちその内容を記述しないが、プロパティ名からも分かるように、開発環境で実行されるプロジェクトについてのさまざまな情報を知ることができる。

次に、それぞれのオブジェクトについて概要を述べよう。

Applicationオブジェクト

プロパティ

アドインの初期化を管理するオブジェクト

AddInManagerオブジェクトには、ユーザーがアドインマネージャダイアログボックスなどでアドインを起動または終了したときに発生するイベントを含むオブジェクトである。このイベントには初期化コードを記述する。

AddInManager オブジェクト

イベント

VisualBasic開発環境に新しいメニューを追加したり、対応するコードを実行するためのオブジェクト

Menuオブジェクトはメニューそのものを表す。実際にシステム全体のメニューにアクセスするには、MenuItemsコレクションを使う。MenuLineオブジェクトは、ユーザーが実行した最後のメニュー選択項目を表す。

★Menuオブジェクト

プロパティ

MenuItems

Parent

MenuItemsコレクション

プロパティ

メソッド

MenuLineオブジェクト

プロパティ

メソッド

イベント

VisualBasicのプロジェクトに関係したファイル操作を管理するオブジェクト

FileControl オブジェクトは、VisualBasicでのファイル管理に関わるすべての情報を知ることができる。

FileControl オブジェクト

プロパティ

メソッド

イベント

プロジェクト内のフォームとコントロールを操作するオブジェクト

ProjectTemplateオブジェクトは、VisualBasicのプロジェクトに関する処理を操作する。

SelectedComponetsコレクションは、プロジェクト内ぶ現在選択されているすべての構成要素(フォーム、標準、クラスなどのモジュール)の集合を示す。

Componentオブジェクトは、VisualBasicのプロジェクトの一つの構成要素を示す。

FormTemplateオブジェクトは、VisualBasicのプロジェクト内のフォームを表す

ControlTemplatesコレクションは、フォーム上のすべてのコントロールを表す。

SelectedControlTemplatesコレクションは、FormTemplatesのプロパティで、現在選択されているコントロールを表す。

ControlTemplateオブジェクトは、フォーム上の一つのコントロールを表す

Propertiesコレクションは、プロパティのコレクション。

Propertyオブジェクトは、フォームまたはコントロールのプロパティを表す。

★ProjectTemplateオブジェクト

プロパティ

メソッド

★SelectedComponetsコレクション

プロパティ

メソッド

★Componentオブジェクト

プロパティ

メソッド

SaveAs

★FormTemplateオブジェクト

プロパティ

メソッド

ControlTemplatesコレクション

プロパティ

メソッド

ControlTemplateオブジェクト

プロパティ

ControlTemplatesコレクションと同じ

メソッド

ControlTemplatesコレクションと同じ

SelectedControlTemplatesコレクション

Propertiesコレクション

プロパティ

メソッド

★Propertyオブジェクト

プロパティ

Propertiesコレクションと同じ

メソッド

Propertiesコレクションと同じ

このように、アドインのオブジェクトを使えば、VisualBasicでのユーザーの動作を知ったり、開発環境の状態を知ったり、フォームなどのモジュールやコントロールについてのプロパティを得たりといった情報取得ができる。さらには、環境やフォームをはじめとするオブジェクトに対して操作をすることもできるわけだ。

●アドインの使い方

ここでは、アドインのサンプルとして用意されているいくつかのサンプルプログラムを使って、実際にアドインの動作をさせてみよう。

図は、VisualBasicの[アドイン]メニューを開いたところである。データマネージャとレポートデザイナがすでにあるが、これは正確にはアドインではない。VisualBasicから起動する独立したアプリケーションである。ここで、[アドインマネージャ]を選んでみると、図のようなダイアログが開く。ここには、3つのアドインが表示されている(最初は一つしかなかったのだが、サンプルをコンパイルしてEXEファイルを作成したので、このように3つ表示されている)。ここから、アドインとして実行したいモノをチェックして<OK>ボタンを押す。ここでは、Align Sample Add-Inを選んだ。

 

再び[アドイン]メニューを選んでみると、図のようにメニュー項目が増えている。これは、アドインの実行が開始されてメニューが追加されたからである。しかし、この動作はアドインによって違っている。アドインは必ず起動はされるのだが、File Event Spyならばすでにダイアログは開いており、実際にSpy機能をするかどうかをチェックで指定する。データフォームデザイナならば、このメニューを選ばれたときに初めてダイアログが表示されるといった具合だ。

 

ちなみに、こうして起動したアドインはOLEアプリケーションであるから、タスクリストにもきちんと表示されるし、[ツール][参照設定]メニューを選べば、図のように参照設定して呼び出すことができる。

 

 

 

 

 

これらのサンプルアドインの実際の動作であるが、File Event SpyはVisualBasicでのファイル操作に関するイベントを調べてリストボックスに表示するものだ(実行図)。VisualBasic 3.0までで同様のことをするプログラムを作ったとしたら、これはかなりスゴいことなのだが、先ほどの有用なオブジェクトを見てからだと、これは結構カンタンそうだなと思ってしまう。

また、データフォームデザイナーは、データソース、テーブル、フィールドを指定してカンタンなメンテナンスをするプログラムを作ってしまうというモノだ(実行図)。こうした指定をすると、図のようなフォームが作成され、それぞれのボタンに対応した処理のコードまで自動的に作られる。実行すれば、きちんと動作する。最近 Accessなどに装備されているなんとかWizardみたいなものも、このようにアドインを使って作ることもできるわけだ。

実行画面

●アドインのコーディング例

こうしてアドインを使うのはカンタンだが、作るのはそうカンタンではない。少なくともOLE Serverアプリケーションを作成できるようオブジェクト指向の考えに慣れ、VisualBasicの動作などについても十分に知っていなければならない。

そこで、サンプルプログラムから一部のコードを紹介して、実際にはどんなコーディングがなされているかを見てみよう。ここでは、先ほどの例にもあった Align アドインを使ってみる。

すでに紹介したAlignのメニューからも分かるように、このアドインはVisualBasicのフォーム上に配置した複数のコントロールをマルチセレクトし、アライメントやサイズを合わせるというモノだ。図のように適当に配置した複数のコントロールを選択し、右ぞろえにし、さらに高さを合わせてみた。こう言ったことがアドインで可能なのである。

Align前。選択状態になっている

Alignで右に合わせた

Alignで高さを合わせた

 

ALIGNでは3つのクラスを使っている。

ALIGN.CLSはアドインの起動時および終了時にメニューを登録/削除するものである。ConnectAddInイベントが起動時の処理をする起動されたVisualBasicのインスタンスなどの情報を得、VisualBasicのメニューにアライメントを指定する項目を追加する。

追加したメニューについては、それぞれ対応するALIGHALLおよびSIZEALLのクラスを割り当てる。これは、メニューが選択されたときに発生するのは対応するオブジェクトのAfterClick()イベントであるからだ。AfterClick()イベントは単一にあって引数でどのメニューが選ばれたかを知るというものではないことに注意してほしい。したがって、選ばれたメニューに対応した処理をするために、ALIGNALLとSIZEALLクラスにあらかじめ設定しておいたメンバー変数、すなわちプロパティに識別するための印を入れておく必要がある。


・ALIGN.CLS
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Connector"
Attribute VB_Creatable = True
Attribute VB_Exposed = True
Attribute VB_Description = "Align Sample Add-In"
'This class is used to connect this addin to the Visual Basic
'design environment.  It must the Public property set to True, and the
'Instancing property set to '2 - Createable MultiUse' (recommended for addins)
'or '1 - Createble SingleUse'
Option Explicit
'Storage for the menu and menuline objects created when the add-in adds its own menu
'and menulines to Visual Basic
Const AlignLeft = 0
Const AlignRight = 1
Const AlignTop = 2
Const AlignBottom = 3
Const AlignAbout = 4
Const SizeWidth = 5
Const SizeHeight = 6
Const SizeBoth = 7

Dim AlignMenu As VBIDE.SubMenu
Dim SizeMenu As VBIDE.SubMenu
Dim MenuLines(AlignLeft To SizeBoth) As VBIDE.MenuLine
Dim AlignHandler(AlignLeft To AlignBottom) As AlignAll
Dim SizeHandler(SizeWidth To SizeBoth) As SizeAll
Dim ConnectID(AlignLeft To SizeBoth) As Long
Dim VBInstance As VBIDE.Application

Sub ConnectAddIn(VBDriverInstance As VBIDE.Application)
Dim i%
    'This method is called when this add-in is installed in an instance
    'of Visual Basic.  Choose "Align Sample Add-In" from the Add-In manager
    'on another instance of VB. VBInstance will be the object representation
    'of the instance of Visual Basic which installed the add-in.  To change
    'the name of the addin in the addin manager, select the Align project in the
    'object browser, click on the Connector class, and choose Options.

    'Save the instance of Visual Basic so we can refer to it later.
    Set VBInstance = VBDriverInstance
    
    With VBInstance.AddInMenu.MenuItems
        'Add our Align menu and menu lines to the Visual Basic Add-In menu.
        Set AlignMenu = .AddMenu("A&lign")
        With AlignMenu.MenuItems
            Set MenuLines(AlignLeft) = .Add("&Left Align")
            Set MenuLines(AlignRight) = .Add("&Right Align")
            Set MenuLines(AlignTop) = .Add("&Top Align")
            Set MenuLines(AlignBottom) = .Add("&Bottom Align")
            Set MenuLines(AlignAbout) = .Add("About...")
        End With
        'Add our Align menu and menu lines to the Visual Basic Add-In menu.
        Set SizeMenu = .AddMenu("Si&ze")
        With SizeMenu.MenuItems
            Set MenuLines(SizeWidth) = .Add("&Width")
            Set MenuLines(SizeHeight) = .Add("&Height")
            Set MenuLines(SizeBoth) = .Add("&Both")
        End With
    End With
    
    'Connect the corresponding event handler object to the correct menu line.
    For i% = AlignLeft To AlignBottom
        'Create a new handler for each direction.
        Set AlignHandler(i%) = New AlignAll
        'Save the ID for each connected event.
        ConnectID(i%) = MenuLines(i%).ConnectEvents(AlignHandler(i%))
        'Pass the VBInstance to the child objects.
        Set AlignHandler(i%).VBInstance = VBInstance
    Next i%
    
    'Set the AfterClick handler for the about box.
    ConnectID(AlignAbout) = MenuLines(AlignAbout).ConnectEvents(Me)
    
    'Connect the SizeHandler events.  Same as hooking up Align events.
    For i% = SizeWidth To SizeBoth
        Set SizeHandler(i%) = New SizeAll
        ConnectID(i%) = MenuLines(i%).ConnectEvents(SizeHandler(i%))
        Set SizeHandler(i%).VBInstance = VBInstance
    Next i%
    
    'Initialize the directional Align instances.
    AlignHandler(AlignLeft).MainProp = "Left"
    AlignHandler(AlignRight).MainProp = "Left"
    AlignHandler(AlignRight).ShiftProp = "Width"
    AlignHandler(AlignTop).MainProp = "Top"
    AlignHandler(AlignBottom).MainProp = "Top"
    AlignHandler(AlignBottom).ShiftProp = "Height"
    
    'Initialize SizeFlags on Size instances.
    SizeHandler(SizeWidth).SizeFlags = 1
    SizeHandler(SizeHeight).SizeFlags = 2
    SizeHandler(SizeBoth).SizeFlags = 3
End Sub

Sub DisconnectAddIn(Mode As Integer)
Dim i%
Dim mnuItems As VBIDE.MenuItems
    'Remove AlignMenu items.
    Set mnuItems = AlignMenu.MenuItems
    For i% = AlignLeft To AlignAbout
        'Disconnect the event handlers from the menu lines
        MenuLines(i%).DisconnectEvents ConnectID(i%)
        'Remove the menu and menu lines we installed in Visual Basic
        mnuItems.Remove MenuLines(i%)
    Next i%
        
    'Remove SizeMenu items.
    Set mnuItems = SizeMenu.MenuItems
    For i% = SizeWidth To SizeBoth
        MenuLines(i%).DisconnectEvents ConnectID(i%)
        mnuItems.Remove MenuLines(i%)
    Next i%
    
    'Remove items from Addins menu.
    With VBInstance.AddInMenu.MenuItems
        .Remove AlignMenu
        .Remove SizeMenu
    End With
End Sub

'Use this class to provide an AfterClick handler for About...
Public Sub AfterClick()
    About.Show vbModal
End Sub

実際にメニューが選ばれると、AfterClick()イベントが呼び出される。初期化時に設定されているプロパティなどから、それぞれのオブジェクトにあるこのイベントは、自分が何をすべきなのかを判断することができるようになっているわけだ。

ここで注目したいのは、For Each〜Next構文と、With〜End With構文だ。

For Each〜Nextは、オブジェクト指向の項でも紹介したとおり、コレクションなどから一つずつのオブジェクトを取り出して、それぞれについて処理をするためのモノである。

一方、Width〜End Withは、あるフォームやコントロールなども含んだオブジェクトについて、それぞれのプロパティの内容にアクセスすることができるものだ。同じオブジェクトについての複数のプロパティにアクセスしたいときには、タイプする文字が少なくできる他、オブジェクト変数などと組み合わせると非常に効率よいコーディングができる。


・ALIGNALL.CLS
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "AlignAll"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
'Instances of this class handle all Alignment events.
'This class does not need its Instancing or Public
'properties set.

Option Explicit
Public MainProp As String 'Left or Top
Public ShiftProp As String 'Width or Height
Public VBInstance As VBIDE.Application

Public Sub AfterClick()
Dim TheseControls As VBIDE.SelectedControlTemplates
'Using 'As Object' for collection iterator is faster cross process.
Dim Control As Object 'VBIDE.ControlTemplate
Dim bDoShift As Boolean
Dim CheckValue As Long
Dim tmpValue As Long
Dim Controls() As VBIDE.ControlTemplate 'Don't cycle twice through collection
Dim i%, iControlCount As Integer
    
    'Shift properties are Height and Width.  bDoShift will be true for
    'Bottom and Right alignment.
    bDoShift = Len(ShiftProp)
    If Not bDoShift Then CheckValue = 99999 'Defaults to 0 for a shifted property
    Set TheseControls = VBInstance.ActiveProject.ActiveForm.SelectedControlTemplates
    
    'If a property access fails, then just ignore it.
    On Error Resume Next
    
    iControlCount = TheseControls.Count
    If iControlCount < 2 Then Exit Sub 'Nothing to do
    ReDim Controls(iControlCount - 1)
    
    'Scan all the selected controls, checking for the best value to align to.
    For Each Control In TheseControls
        'Keep a reference to the Control for the second pass through the collection.
        Set Controls(i%) = Control
        If bDoShift Then 'Right and Bottom alignment
            With Controls(i%).Properties 'Use the typed object for fastest performance.
                tmpValue = .Item(MainProp) + .Item(ShiftProp)
                If tmpValue > CheckValue Then CheckValue = tmpValue
            End With
        Else 'Left and Top alignment.
            With Controls(i%).Properties(MainProp)
                If .Value < CheckValue Then CheckValue = .Value
            End With
        End If
        i% = i% + 1 'Increment to maintain the position in the collection.
    Next
    
    'Use the best value found on the first pass to align each control.
    For i% = 0 To iControlCount - 1
        If bDoShift Then 'Right and Bottom alignment
            With Controls(i%).Properties
                .Item(MainProp) = CheckValue - .Item(ShiftProp).Value
            End With
        Else 'Left and Top alignment
            Controls(i%).Properties(MainProp) = CheckValue
        End If
    Next i%
End Sub

 
・SIZEALL.CLS
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "SizeAll"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

Private Const WidthProp = "Width"
Private Const HeightProp = "Height"
Public SizeFlags As Integer '1=Width, 2=Height, 3=Both
Public VBInstance As VBIDE.Application

Public Sub AfterClick()
Dim ControlIterator As Object
Dim Control As VBIDE.ControlTemplate
Dim NewWidth As Long
Dim NewHeight As Long
Dim bHaveNewValues As Boolean
    
    'If a property access fails, then just ignore it.
    On Error Resume Next
    
    bHaveNewValues = False
    For Each ControlIterator In VBInstance.ActiveProject.ActiveForm.SelectedControlTemplates
        Set Control = ControlIterator
        With Control.Properties
            If bHaveNewValues Then
                If SizeFlags And 1 Then .Item(WidthProp) = NewWidth
                If SizeFlags And 2 Then .Item(HeightProp) = NewHeight
            Else
                If SizeFlags And 1 Then NewWidth = .Item(WidthProp)
                If SizeFlags And 2 Then NewHeight = .Item(HeightProp)
                bHaveNewValues = True
            End If
        End With
    Next
End Sub

●アドインの使われ方

このように、アドインは面白い。従来は細かい処理ができなかったことが不満だったVisualBasicなのに、このアドインを使えばここまで面白いことができてしまう。面白いということは、実際に何ができるかはアイデア次第であり、いろいろな可能性があるだろう。

たとえば、サンプルにもあったようにデザイン時の開発効率を上げるものや、フォームやコードを自動生成してくれるもの、さらにはユーザーの操作を監視して記憶しておく、ありがちだがバージョン管理など、考えれば実にいろいろなことができそうだ。

この分野もOLE Serverであるから、実はVisualBasicでなくてもアドインを作成することはできる。そういう意味でも、今後面白い製品が考えられ、発売されそうな分野である。私も、今から何か面白いコトを考えてみたい。