効率よいプログラミングのために
VisualBasic 4.0のオブジェクト指向はCairoに向かう
長い沈黙を破り、ついにVisualBasic 4.0が発表された。この新しいバージョンのVisualBasicには数多くの特色がある。ここでは、その中でももっとも注目したいオブジェクト指向に関わる言語仕様について、その概要や目的を紹介しよう。
酒井 法雄
●オブジェクト指向の目指すもの
VisualBasic 4.0が装備しているオブジェクト指向の機能について述べる前に、まずはオブジェクト指向とは何であるか、ざっと復習しておこう。というのも、オブジェクトとかオブジェクト指向といったコトバは便利なためによく使われるものの、その本質は理解されていないフシがあるからだ。
まず、最初に押さえておきたいのは、オブジェクト指向の目的である。端的にいうならば、オブジェクト指向とは、能率よくプログラミングするために考えられたものだ。オブジェクト指向に代表されるような新しくて聞いたこともないようなコトバが出てくると、スグに最新の技術だとかソフトウェア理論だとかいうモノにひっくるめられてしまい、つい構えてしまうのだが、決してそういう権威主義に結びつくものではない。すべては、プログラマーがラクをするため、効率よく開発をするための仕組みなのである。
オブジェクト指向に至る前段階として、構造化というモノが挙げられるだろう。一昔前に大脚光を浴びたご存じC言語の特徴のひとつが、この構造化であった。当時、Cができなきゃプログラマーにあらずといった風潮もあったくらいだが(今もそうだが)、こういう権威主義に日本人は弱いのかもしれない。このC言語とか構造化というのもクセモノで、もてはやされた割りにはちゃんと本質を理解している人が少なかったようだ。思えば、構造化と同じ道をオブジェクト指向も通っているのかもしれない。
どうしてそう思うかと言えば、C言語あるいは構造化制御構文、トップダウン手法などというモノは、いずれもプログラマーがラクをするための仕組みであったからだ。開発効率とメンテナンス性をいかに向上させるかがテーマであったという点では、オブジェクト指向とまったく同じなのである。
ところが、実際の現場を見ると、C言語を使ったとか、構造化制御構文を使ったとか、そういうカタチだけなのだ。実際に書かれたコードを見れば、「N88 Basicじゃないんだからネ」と言いたくなるようなお粗末なモノが目に付く。なぜこうなのか? それは、ラクしようという目的が分かっていないのに、カッコをつけるためにC言語を使っているのである。目的がわかっていないのに、構文がどうだとかポインタとはこういうモノだとかいう小難しい本を読むのだから、そういう目先のコトばかり気になってしまう。いつまでたっても、本当に使いこなしている実感がわかない。当然なのである。なにせ、目的が分かっていないのだから、使いこなせるワケがないのだ。
というわけで、過去と同じ間違いをする人が少しでも少ないように、ここでは目的から入ったワケだ。もう一度、確認しておこう。オブジェクト指向とは、ラクをするための仕組みのひとつである。
オブジェクト指向以前のラクをする仕組みとして、構造化を挙げた。オブジェクト指向は、この構造化の延長上にある。したがって、構造化について正しく理解できていないと、オブジェクト指向を理解するのは難しい。
この正しくというのが難しいトコロである。構造化制御構文の文法を知っていれば正しく理解しているコトになるのだろうか? いや、そうではない。構造化をうまく使って、アナタはプログラミングをラクにしたか? そしてメンテナンスもラクにできるようにしたか? していれば、正しいのである。つまり、この手の仕組みなどというモノは、目的のために使われていなければ意味がないのである。逆に、そんな仕組みがない言語であっても、工夫して開発効率やメンテナンス性を向上させようと工夫していれば、そのプログラミングは「構造化」や「オブジェクト指向」の香りがするモノだろう。つまり、カッコヨク言えばソフトウェア工学的発想、私流にいうならばラクをするためにはどうしたらよいかを考えるコトが一番重要なのである。
●オブジェクト指向の仕組み
では、ラクな方向のためにどのような努力がされてきたかと言えば、プログラムを部品化するコトである。大きなプログラムも小さなパーツを組み合わせて作ろうというのだ。これは、プロシージャの考え方が基本になっている。それぞれのプロシージャには段階があって、低レベルなものから高レベルなものまである。低レベルから作るのがボトムアップ手法、高レベルから作るのがトップダウン手法と呼ばれる。こうして部品化をすれば、再利用や仕様変更などのメンテナンス時にも柔軟に対処できるようになるのである。ここまでが構造化である。
オブジェクト指向では、プログラミングの二つの要素で相互に影響が大きいデータとコードを一体化させたクラスというモノを考え、さらに部品化を押し進めている。クラスは、そこに属する、すなわちメンバーとなる定数や変数などのデータとメンバーとなるプロシージャから成るひとつの型である。ひとつの目的に作成されたクラスを元にして、さらに目的を絞ったり変えたりしたクラスを作成することができる。このとき、元になるクラスの持っている属性を受け継いだり、変更したりすることができる。これを継承とオーバーライドと呼ぶ。メンバーとなるデータやプロシージャの変更や追加などをするわけだ。このように、より部品として使い回しがしやすいように工夫されているのである。
部品化するということは、部品はブラックボックス化しておいた方がよい。もし、CDプレイヤーのフロントパネルに回転速度調節のボリュームがついていたら、ユーザーはつい勝手にいじってしまうし、いじられてうまく動かなくなったときにメーカーもサポートするのが大変だ。ユーザーが使うことのできるインターフェイスは最小限にしておくべきである。
プログラミングでもこれはまったく同じである。部品として使うものであれば、その内部動作は外から知る必要もないし、知られない方がいいのだ。このように内部動作を隠してしまうことを、隠蔽するとかカプセル化するとか言う。オブジェクト指向では、クラスの内部動作に関わる変数や関数は、内部用と外部用に明確に分けて考える。内部用はカプセル化して外部から手を出せないようにするのである。
このように、オブジェクト指向ではデータとコードから成るクラスを部品として考え、部品の内部動作と外部とのインターフェイスを明確に分けるということが重要な点である。しかし、いずれも部品化を進めて効率よく開発やメンテナンスをするための仕組みであるワケだ。
オブジェクト指向の考え方は、WindowsなどのGUIに非常にマッチするものだ。というのも、ひとつひとつのウィンドウは属性とコードから成っており、人間からはひとつのモノとして捉えられている。さらに、内部動作は隠蔽されていて、外部とのインターフェイスは決まっている。まさにオブジェクト指向にピッタリのモノである。
●VisualBasic 3.0まではオブジェクト指向ではなかった
オブジェクト指向の概略はお分かりいただけたと思う。では、従来のVisualBasicではそういったアプローチはなかったのだろうか。もちろんあった。そもそもが、すでに述べたようにWindows自体がオブジェクト指向に馴染むモノである。たとえば、フォームやコントロールは、プロパティ、メソッド、イベントといった固有のデータやコードから成るオブジェクトである。また、外部のDLLがその実体であるVBXカスタムコントロールも同様だ。違いは、VisualBasicの内部か外部かというコトである。
つまり、VisualBasic自体がオブジェクト指向をある程度意識して作られたものであったと考えられる。しかし、オブジェクト指向と言うには仕様として足りない部分も少なくなかった。たとえば、クラスを新たに作ることはできなかったし、既存のフォームなどのオブジェクトにメンバとなるプロパティを追加することもできなかった。また、メソッドは追加できるものの、他のフォームにあるメソッドを実行することができなかった。プロパティとメソッドからクラスというモノがあるというところまではいいのだが、それをカスタマイズするあるいは作るという概念が欠如していたのである。これでは効率よく部品化するコトができないのである。
そもそも、クラスとオブジェクトの区別が明確でなかったことからしてヘンだ。クラスとは型であり、実際に実行したときにその型からできるモノをオブジェクトと言う。オブジェクトは実体であるから、型を元にして複数作ることができる。VisualBasicでは、フォームを元にしてそのインスタンス(フォームオブジェクトのコピー)を作ることができたが、そのフォーム自体も実体として定義されているという摩訶不思議なモノだったのだ。
しかし、2つの点で画期的なオブジェクト指向の考え方があったことを忘れてはならない。
ひとつ目は、VBXカスタムコントロールを使うことができるということだ。VisualBasicからVBXを作ることはできなかったが(開発にはC + SDK + CDKが必要)、再利用するという面だけで言えば、これは完璧なものであった。結局のところ、サ−ドパーティ製のライブラリと同じようなモノなのではあるが、こういった部品販売の商売が成り立ち、部品のアップグレードで機能もアップできるということは画期的であった。開発効率も当然アップする。しかし、そのVBXは16bitのための規格であり、今後はOLE 2.0ベースのOCXへバトンタッチされることになった。
二つ目は、OLE Automationである。これは、VisualBasic 3.0からのサポートだから、残念ながら日本語版VisualBasic 2.0では使えない機能だ。これは、OLE 2.0の機能のひとつであり、外部にあるOLEサーバーの持つプロパティやメソッドを呼び出すことができるというものだ。つまり、Excelをフォームに貼り付けて、自分が作ったアプリケーションの一部として使うコトができるのだ。これをさらに押し進めたのが、先ほど述べたOCXである。OCXは、OLEサーバーのDLLであり、VBXと同様にイベントを用意することができる。もちろん、プロパティやメソッドをOLE Automationで呼び出すことができるから、VBX以上に柔軟性のある(VBXではメソッドのカスタマイズができなかった)プログラミングができるのである。しかし、OCXはVisualBasic 3.0ではサポートされなかったし、OLE Automationまわりの文法にもかなり制約があった。
総じて、VisualBasic 3.0まではオブジェクト指向的エッセンスが入ってはいたものの、部品化ができない言語仕様はオブジェクト指向とは言えないものだったのである。
●オブジェクト指向への二つの道
VisualBasic 4.0では、部品化ができる仕組みが用意された。そして、OCXやOLE Automationについてもサポートされることになった。これは、ラクしてプログラミングしようという向きにはとても嬉しいコトなのだが、いろいろと増えた新しい言語仕様やクセを覚えるのは大変であるという矛盾も抱えている。しかし、これもラクをするためと思って乗り越えてほしい。
さて、すでにオブジェクト指向についての話をしてきたが、今一度整理すると、クラスを作っていくという話に加えて、OLEやOCXを使うという話も出てきている。OLEやOCXなどというモノは、オブジェクト指向には関係ない話だと考えがちなのだが、決してそんなことはない。むしろ、こちらの方が重要なトピックであると言えそうだ。というのも、OLEやOCXはOS自体が持つオブジェクト指向の機能と考えられるからだ。オブジェクト指向には、二つの道があるのである。
クラスを作っていくというオブジェクト指向の考え方は、あくまでもその言語に閉じた世界の中で繰り広げられるものだ。たとえば、VisualC++やBORLAND C++などのC++言語では、MFCやOWLと呼ばれるクラスライブラリがある。これはクラスをコンパイルしてライブラリ化したものである。したがって、を他のC++言語、たとえばWatcom C++でコンパイルすれば、確かにWatcom C++で他のクラスライブラリを使うことはできる。だが、これまでである。VisualBasicをはじめとするRAD(Rapid Application Development)ツールから、これらのクラスライブラリを使う手段はないのである。同様に、Power ObjectsやLotus Notes ViP、Visual Objectsなどのクラスも、相互に互換性はないし、C++から使うこともできないのだ。ということは、いくら便利なクラスを作っても、開発ツールを変えた瞬間に使えなくなってしまうというコトである。
さらに、オブジェクト指向開発ツールには、リポジトリデータベースが不可欠である。これは、クラスの一部を変更したときに、そのクラスを使っているアプリケーションが自動的に変更に合わせて書き換えられるというものだ。C++ならばmakeがその役目を果たしているから、変更部分をコンパイルしなおせばよいが、インタープリタ型などの場合には、リアルタイムに変更されないといけない。これにはメモリや実行速度などについても難がある。Power Objectsなどはまさにこれをやっているワケだが、中にはこういったことをさぼっているツールもあるから気をつけないといけない。
ところが、OLEやOCXは違う。これらはいずれもOSレベルでのオブジェクト指向である。つまり、OLEコンテナから部品としてOLEサーバーやOCXカスタムコントロールをランタイムに使うというものだ。ランタイムに使うのだから、OLEサーバーやOCXはどんなツールで開発されたものでも構わない。通信はVBA(Visual Basic for Applications)を使ったOLE Automationで行われることになっているからだ。しかも、リポジトリなどというものもランタイムに必要になるだけだ。これはレジストリデータベースとしてWindowsが管理している。
さらに、Windows NT 4.0となると言われているCairoでは、このようなオブジェクトが集まってOSのカタチになるという。つまり、ネットワーク資源までも含めたWindows上のすべてのプログラミングが、 OLE Automationでできるようになると言うのだ。
そう考えてくると、OLEベースのOSレベルのオブジェクト指向の方が、実際の部品化には適しているようにも思えてくるのだ。
だが、システムではなく自分のライブラリとしてプログラミング時に使いたいような、部品にするまでもない小さな処理もある。そんな小さなものまでOLEサーバーにするのはオーバーヘッドが大きすぎるし、サーバーの命名などに問題が発生することも考えられる。
そうなると、開発時にはクラスを作ったりして効率よくプログラミングしていき、ある程度以上の規模の部品になったら、OLEサーバーあるいはOCX化できるようになっていることが望ましいわけだ。しかし、そんな都合のいい話があるだろうか。いや、あるのだ。たとえば、VisualC++などではまさにそういうことができる。
「でも、VisualBasicじゃ...」という声が聞こえてきそうだ。VisualC++でできて、どうしてVisualBasicだとできないと考えるのだろうか。そもそも、Cairoになったら主役はVisualBasicになることが予定されているのである。いつまでもできないワケはないのだ。
VisualBasic 4.0では、OCXこそ作れないものの、OLEサーバーアプリケーションを作ることができる。しかし、インプレイスアクティべーションができないという制限付きではある。だが、新しくサポートされたクラスを使ってプログラミングしていき、ある時点でクラスを主としたモジュールをOLEサーバーとして作成することができるのだ。もちろん、そのサーバーはVisualBasicで作られる複数のコンテナアプリケーションから利用することが可能である。
このように、VisualBasic 4.0のサポートするオブジェクト指向は、単なる言語仕様だけのオブジェクト指向ではなく、OLEベースのOSに密着したオブジェクト指向なのである。こういった考え方をした開発ツールは、もちろん初めてだ。そういった意味では、VisualBasic 4.0は大きく期待を上回るものであると言えよう。
●PrivateとPublic
オブジェクト指向では、クラスが作成できればいいというモノではない。必要に応じて隠蔽化、カプセル化することも重要なトピックである。クラスを作成する前に、この隠蔽化をするため、そして外部に公開するためのキーワードを紹介しておこう。
このキーワードは2つある。ひとつはPrivate、もうひとつはPublicである。VisualBasicでは、従来から変数の有効範囲が明確に決められていた。プロシージャの内部に記述された変数はローカル変数であり、他のプロシージャからアクセスすることができないとか、モジュールレベルに記述された変数は他のモジュールから見えないとか、どこからでも見える変数はGlobalキーワードを使ってコードモジュールに記述しないといけないといった決まりがあった。
これに加えて、PrivateとPublicキーワードが使われることになった。しかも、変数だけではなく、定数、Declare文、Type文、プロシージャ、後述するプロパティなどにも使われる。ここで重要なのは、従来はデータである変数についての有効範囲が、コードであるプロシージャにまで拡大されたということだ。実際には、次のステートメントと組み合わせて使われる。
Private、Publicともにフォームモジュール、標準モジュール(従来のコードモジュールから名称が変更された。コード以外も記述できるようになったからだろう)、後述するクラスモジュールに記述する。変数や定数につけて宣言するときには、プロシージャレベルではなく、モジュールレベルで宣言する。つまりは、すべてモジュールレベルに記述するということだ。
PrivateかPublicかということは、そのモジュール内だけで有効か、他のモジュールからアクセスすることができるかというコトになる。図を見ていただきたい。
モジュールAにPrivateで宣言したXは、他のモジュールからアクセスすることはできない。しかし、Publicで宣言したYには、モジュール名を明示してアクセスすることができる。従来は、このようにモジュール間でする共有変数は、コードモジュールにGlobalを使って宣言した。これは新しいVisualBasicでも同じであり、標準モジュールに記述されたGlobal変数は、モジュール名を指定しない変数名のみでアクセスすることができる。
ちなみに、PrivateもPublicも指定しないときには、変数ではPrivateと見なされる。
●カスタムプロパティの作成
オブジェクト指向の第一歩は、クラスである。クラスとはデータとコードの両方をまとめたものである。つまり、プロパティとメソッドを持っているフォームやコントロールみたいなモノだと考えられる。VisualBasic 4.0では、もちろんクラスを作成できるのだが、その前にチェックしておきたいことがある。それは、従来からあったフォームやコードモジュールに、プロパティやメソッドを追加することができるということだ。これはクラスの作成ではなく、オブジェクトのカスタマイズと言った方が近い。
たとえば、ある特定の用途に作ったフォームや標準モジュールなどの部品を、アプリケーション側から使いたいといったときには、グローバル変数を使ってデータをやり取りするのではなく、用途にあったプロパティがあった方がいい。また、そのフォームを開けばForm_Loadイベントからスタートして何かが実行されるというよりは、外部から呼び出せるメソッドが目的に応じて複数あった方が便利である。そういったものを作ることができるようになったわけだ。
・PROCS.VBP
Form=PROC1.FRM
Object={F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.0#0; COMDLG16.OCX
Form=PROC2.FRM
Module=Module1; PROCS.BAS
ProjWinSize=84,689,248,129
ProjWinShow=2
IconForm="Form1"
HelpFile=""
Name="Procs"
HelpContextID="0"
StartMode=1
Description="Proc Test"
VersionCompatible="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
ServerSupportFiles=0
VersionCompanyName="Int21 Corp."
Reference=*\G{BEF6E001-A874-101A-8BBA-00AA00300CAB}#1.0#0#C:\WINDOWSJ\SYSTEM\OC25.DLL#Standard OLE Types
Reference=*\G{00025E01-0000-0000-C000-000000000046}#2.5#0#C:\WINDOWSJ\SYSTEM\DAO2516.DLL#Microsoft DAO 2.5 Object Library
・PROC1.FRM 主要部分
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Private Sub cmdCallModule_Click()
Module1.Proc
End Sub
Private Sub cmdCallProc_Click()
Form2.Proc
End Sub
Private Sub cmdGet_Click()
txtGet.TEXT = Form2.Pathname
End Sub
Private Sub cmdGetFile_Click()
txtGet.TEXT = Module1.FileName
End Sub
Private Sub cmdLet_Click()
Form2.Pathname = txtLet.TEXT
End Sub
Private Sub cmdMakeInst2_Click()
Dim f As New Form2
f.Caption = "Instance of Form2"
f.Show
f.Proc
Set f = Nothing
End Sub
Private Sub cmdSetFile_Click()
Module1.FileName = txtLet.TEXT
End Sub
Private Sub cmdShowForm2_Click()
Load Form2
Form2.Show
End Sub
Private Sub Form_Unload(Cancel As Integer)
End
End Sub
・PROC2.FRM 主要部分
Attribute VB_Name = "Form2"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Public Pathname As String
Public Sub Proc()
MsgBox "I belong to " & Me.Caption
End Sub
Private Sub Command1_Click()
Unload Me
End Sub
Private Sub Form_Initialize()
MsgBox "Form initialize"
End Sub
Private Sub Form_Load()
MsgBox "Form Load"
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
MsgBox "Form QueryUnload"
End Sub
Private Sub Form_Terminate()
MsgBox "Form Terminate"
End Sub
Private Sub Form_Unload(Cancel As Integer)
MsgBox "Form Unload"
End Sub
・PROCS.BAS
Attribute VB_Name = "Module1"
Option Explicit
Private tFileName As String
Property Get FileName() As String
FileName = tFileName & ">"
End Property
Property Let FileName(fn As String)
tFileName = "<" & fn
End Property
Sub Proc()
MsgBox "I belong to Module1"
End Sub
ここでは、サンプルプログラムPROCSをご覧いただこう。ちなみに、従来のプロジェクトファイル.MAKは廃止され、.VBPという拡張子になった。それと同時に、このファイルの中身も大きく変貌している(リスト)。OCXのレジストリ、プロジェクトのいちやサイズ、バージョン、OLEサーバーに関する情報といったデータが入っている。このプロジェクトには、Form1、Form2、Module1の三つのモジュールがある。
名前からも分かるように、フォームモジュールと標準モジュールである。
まずは、プロパティを作ってみよう。これには二つの方法がある。
カンタンなのは、Publicな変数を宣言する方法だ。Form2には、次のようにPublicな文字列型変数が宣言されている。
Public Pathname As String
先ほどの例にも出てきたように、この変数には他のモジュールからも、「Form2.Pathname」と記述すればアクセスすることができる。Form1のcmdLet_Click()やcmdGet_Click()プロシージャにも記述がある通りだ。これらは<Set Name>と<Get Name>ボタンに対応するコードだ。やっていることはテキストボックスtxtLetやtxtGetと変数とのコピーだけである。しかし、記述方法ともプロパティそのものだし、事実上独自のプロパティがちゃんと追加されている。
このように、モジュールにPublicな変数を記述すれば、それはプロパティとして使うことができるわけだ。しかし、特定のプロパティ値が設定されたときに何かするとか、プロパティを参照された瞬間にそのときの状態に合わせた値を返したいということはできない。言うならば、Public変数によるプロパティは、Tagプロパティのようにプロパティらしくないプロパティなのである。
次に、わりあいキチンとしたプロパティ、つまり代入時や参照時に特定の動作をするモノを作ってみよう。特定の動作というコトからも想像が付くように、これにはプロシージャを作ることになる。実際には、SubやFunctionによく似たPropertyプロシージャを使うのだ。これにも、次の3つのプロシージャがある。
Property Get プロパティの値を取得するときに呼び出されるプロシージャ
Property Let プロパティの値を設定するときに呼び出されるプロシージャ
Property Set オブジェクト型プロパティの値を設定するときに呼び出されるプロシージャ
これらのいずれかもプロシージャを作成すれば、それに対応したプロパティが作成される。
この例は、標準モジュールModule1のコードをご覧いただきたい。
Property Let FileNameプロシージャは、新たにFileNameという名前のプロパティを作成し、FileNameプロパティの値を設定されたときに呼び出される。ここでは、引数fnが代入された文字列ということになる。たとえば、プロシージャでは、モジュールにPrivateな文字列型変数tFileNameに、文字"<"にfnを追加した文字列を設定している。
Property Get FileName プロシージャは、プロパティの型を戻り値として持つプロシージャである。プロパティの値を参照されたときに呼び出される。ここでは、モジュールにPrivateな文字列型変数tFileNameの値に">"を追加した文字列を返すようにしている。
このFileNameプロパティを呼び出しているForm1の<Set
File>および<Get File>ボタンに対応したcmdLetFile_Click()、cmdGetFile_Click()の内容を見れば、先ほど<Set Name>などとほとんど同じであることが分かる。しかし、こちらのボタンを押したときには、入力した「test」という文字列が「<test>」になって帰ってくることがわかる。これは、カスタムプロパティプロシージャで設定、参照したときに操作されたものである。
このように、Property Get, Let, Setの各プロシージャを必要に応じて作成すれば、オブジェクトの内部動作に絡ませた値をうまく外部にインターフェイスするようにできるわけだ。なお、Prooerty LetおよびSetの引数には配列の添字その他の引数を自由に設定することができる。このとき、最後の引数がプロパティのデータ型になる。もちろんこのときには、Property Getの引数として同じモノを設定する必要がある。この関係を図に示す。
Property プロシージャの定義
Property Get XXX(1,...,n) As Type
Property Let XXX(1,...,n, n+1)
Property Set XXX(1,...,n, n+1)
Getで設定したプロパティのデータ型は、LetやSetの最後の引数のデータ型と同じなければならない。
呼び出し側との関係
XXX(a, b, c) = d
Property Get XXX(a, b, c) As {dのデータ型}
Property Let XXX(a, b, c, d)
PropertyプロシージャにもPublicかPrivateを指定することができる。何も指定しなかったときには、Publicになる。変数のときとは動作が逆なので注意してほしい。また、Private指定をしたときには、そのモジュール内部でのみ有効なプロパティとなる。ちなみに、Propertyプロシージャから脱出するには、Exit Propertyを使う。
そういえば、VisualBasic 4.0のプロシージャでは、省略可能な引数をサポートし、かつ引数の数が定まっていないものもサポートしている。さらには、仮引数を指定する名前付き引数までもサポートしている。これもおもしろいトピックなのだが、また別の機会に述べることにしよう。
●カスタムメソッドの作成
このように、フォームや標準モジュールにプロパティを追加することができたワケだが、VisualBasic 4.0ではメソッドも追加できるようになっている。といっても、何も難しいことはない。従来からあったように、そのモジュールにSubやFunctionプロシージャを記述すればよいのだ。それでは何が変わったのかといえば、それを別のモジュールから呼び出すことができるようになったというコトだ。
今までも、コードモジュールに記述されたプロシージャは、他のモジュールから呼び出すことができた。しかし、フォームモジュールに記述されたコードはそうはいかない。逆の言い方をすれば、コードモジュールに記述したプロシージャは隠したくても他のモジュールから呼び出されてしまったとも言える。これはカプセル化を考えたときによくない仕様であった。
そこで、VisualBasic 4.0では、フォーム、標準、後述するクラスといったモジュールに属するプロシージャにもPublicかPrivateかのキーワードをつけて管理することができるようになったわけだ。Privateのプロシージャは標準モジュールに記述してあっても他のモジュールから見えない。Publicのプロシージャはフォームモジュールに記述してあっても、他のモジュールからアクセスすることができる。PROCS.VBPでは、<Call
Form2 Proc.>ボタンを押すと、Form2のProcプロシージャを呼び出している。また、<Call Module1 Proc.>ボタンを押すと、Module1のProcプロシージャを呼び出す。もちろん、オブジェクト名を明示しないで単にProcをコールすれば、Module1のプロシージャが呼び出されるのは、従来と変わりはない。
●新しいイベントとオブジェクトのインスタンス
このように、VisualBasic 4.0では、プロパティとメソッドを、モジュールに追加してカスタマイズすることが可能になったのである。ここまでがVisualBasic 4.0のオブジェクト指向の入門編といったところで、この後に基礎編が待っている。さっさと基礎編に入りたいところだが、ここでちょっと寄り道をしておきたい。というのも、ひっかかるコトがあるからだ。
それは、フォームモジュールがインスタンス化するタイミングと、それに関係するイベントの発生についての疑問である。従来のVisualBasicでは、他のフォームモジュールにあるプロパティにアクセスしたときには、そのフォームのForm_Loadイベントが発生してメモリに読み込まれた。やり方によっては、Showメソッドを実行しなくても表示されることもあった。しかし、今回のようにプロパティ変数やプロパティプロシージャ、カスタムメソッドを呼び出したとき、一体イベントはどうなっているのだろうか。この疑問のため、Form2には先ほどのリストのようにイベントごとにメッセージボックスを表示するコードを記述したのである。
結論から言うと、まずフォームのインスタンス化、すなわち実行時の実体化とその終了時に対応する二つのイベントが追加されており、それが微妙に関係しているということだ。この二つのイベントとは、InitializeとTerminateイベントで、それぞれインスタンス化するときと、インスタンスが終了するときに発生する。
まず、プロパティにアクセスするときだが、Form2では必ずInitializeイベントが発生してからプロパティにアクセスすることができるようになる。ちなみに、Property プロシージャを記述したときにも同様であり、Loadイベントは発生しない。
しかし、メソッドを実行したときには、Initializeイベント、Loadイベントが発生してからメソッドが実行される。この違いは場合によって非常に重要なコトになりそうなので、覚えておくとよいだろう。
ちなみに、通常のフォームをUnloadしたりプログラムを終了しても、Terminateイベントは発生しない。このイベントはいわゆるオブジェクト変数などを使ったインスタンスがNothinを代入されたとき、すなわちオブジェクトのインスタンスが解放されたときにのみ発生する。したがって、
Dim frm As New Form2
とか
Dim frm As Form
Set frm = New Form2
あるいは
Dim frm As Object
Set frm = New Form2
※Object型は本来OLEオブジェクト型を意味するので、Formオブジェクトを代入できないハズなのだが、なぜか動いた。β版のためチェックが甘いのかもしれない。なお、さすがにCreateObjectでForm2のインスタンスは作成できなかった。
として作成したオブジェクトが
Set frm = Nothing
として解放されたとき、あるいはそのオブジェクト変数のスコープが終了したときにのみ、Terminateイベントは発生する。つまり、<Show Form2>ボタンに対応するコードではTerminateイベントは発生しないが、<Make Instance 2>ボタンのときには発生するわけだ。
いずれにしても、VisualBasicではクラスを元にインスタンスを作るといったイメージでない、そのまま実行時のオブジェクトになってしまうようなフォームや標準モジュールがあり、さらにそれを元にして別のインスタンスを作れるといった妙な仕様になっているコトに問題がある。本来、クラスとクラスから作成されるインスタンス(実体化されたコピー)であるオブジェクトは明確に区別されるべきなのだ。分かりにくいのはVisualBasicのあいまいな仕様のせいである。
それはともかく、ここで注目しておきたいのは、InitializeイベントとTerminateイベントである。これらは何度も述べているように、インスタンスが作成されるときと終了するときに発生するイベントであり、いわゆるコンストラクタとデストラクタの役回りがある。もちろん、フォームだけではなく後ほど述べるクラスオブジェクトにも用意されているイベントである。何らかの初期化や解放などの後処理を必要に応じて記述することができるわけである。
●VisualBasic 4.0のクラスモジュール
さて、いろいろな話がでてきてゴチャゴチャしてきた。もう一度思い出そう。オブジェクト指向はラクをするためのモノである。で、大事なのはクラスを作れることと、カプセル化だった。カプセル化はずいぶんと話が出たが、いずれも従来と同じフォームやコードモジュールの話だ。だが、これもクラスについて述べる上で重要なことなのだ。
VisualBasic 4.0では、クラスモジュールというモジュールが使えるようになっている。クラスモジュールは、[挿入][クラスモジュール]メニューから追加することができる。実際には、図のように標準モジュールのようにコードウィンドウである。しかし、オブジェクトには(General)の他にClassがあり、InitializeとTerminateイベントが用意されている。
また、クラスには図のようにプロパティがあり、プロパティウィンドウから指定できるようになっている。Nameプロパティの他には、InstancigとPublicの二つのプロパティがある。これらには、つぎのような意味になる。
Not Creatable クラスのインスタンスはプロジェクトの内部でのみ作成可能
Create Single Use クラスのインスタンスはプロジェクトの内部、外部ともに作成可能だが、複数のコンテナから作成することはできない。
Create Multi Use クラスのインスタンスはプロジェクトの内部、外部ともに作成可能で、複数のコンテナから作成することができる。
この二つのプロパティはよく似ているが、違うモノである。Instancingプロパティはインスタンスを他のアプリケーションから作成できるかを示すもので、Publicプロパティは他のアプリケーションから利用できるかを示すものである。
つまり、ここで作成したクラスはVisualBasicのクラスを含むプロジェクト内でのみ使うこともできるのだが、OLE Serverとして外部のアプリケーションから利用することもできるようにするのがPublicプロパティである。また、そのサーバーアプリケーションが起動していないときに、外部からインスタンスを新たにつくることができるかを示すのがInstancingプロパティである。なお、原稿執筆時にはβ版を使用しているので、このへんの細かい仕様は最終的に変更される可能性がある。
では、先ほどフォームや標準モジュールに記述したようなプロパティやメソッドを、クラスに記述してみよう(リスト)。なお、拡張子が.CLSなのがクラスモジュールである。
クラスモジュールのコードを見ると、プロパティについては先ほどの例とほとんど同じであることが分かる。
メソッドについては、MsgBox関数/ステートメントを呼び出すものを二つ用意した。MsgBoxQYNメソッドは、?のアイコンを出して<はい>か<いいえ>のボタンから選択させ、その結果を返す関数である。つまり、MsgBox関数の二つ目の引数を固定にしてしまったワケだ。同様にMsgBoxInfメソッドは、情報アイコンを出す標準的なメッセージボックスである。いずれもクラスを作成すること自体に難しいことは何もない。
難しいのは、むしろ呼び出す側かもしれない。いや、呼び出す側が難しいならどうして部品化などと言えるだろうか。カンタンである。ただ、従来のVisualBasicとは少々扱い方に違いがあるというコトだ。
呼び出し元のフォームには、モジュールレベルに次のような記述がある。
Dim tf As New TextFile
これは、クラスTextFileのインスタンスを新たに(New)作成し、tfという名前のオブジェクト変数とするという意味である。
先ほども似たようなコードがあったことを思い出してほしい。先ほどの例ではフォームでこのようなコードを記述した。しかし、フォームはこんなことをしなくても、だまってRunすれば表示されるものだ。ただ、そのコピーを作るというコトをしたわけだ。先ほども述べた通り、これはヘンな仕様なのである。むしろ、このクラスでの使い方が正しい。
つまり、クラスとはオブジェクトの元を定義したものである。それを使うためには、定義を元にして実体化しなくてはならない。実体化することをInstancing、すなわちインスタンス化すると言い、実体化されたものはインスタンスあるいはオブジェクトと呼ばれるのである。たとえば、VisualBasicのツールボックスにあるコマンドボタンのアイコンは枠であるクラスであり、実際にフォームに配置されたコマンドボタンはインスタンスあるいはオブジェクトと呼ばれる実体なのである。この違いに気をつけてほしい。
つまり、VisualBasic 4.0で作成されたクラスは、あくまでも枠であり、実際にそれを使うためにはオブジェクト変数に代入するなどして、実体化しなくてはならないのだ。この方法は、このように一行で書いてもいいし、先ほどのようにオブジェクト変数に後からSet文を使って代入してもいい。
ここでは、tfというオブジェクト変数が、TextFileクラスのインスタンスになったから、今後オブジェクトtfのメンバー変数であるプロパティ、メンバー関数であるメソッドにアクセスするには、ピリオドを使えばよい。
例:
Text2.TEXT = tf.filename
tf.MsgBoxQYN "Can you see me?",
"Test YN"
実際にこのプログラムを実行してみると、当然ながらプロパティはキチンとコピーされているし、メッセージボックスも動作する。
Class
Text 1
MsgBoxQYNメソッドによる(はい・いいえ)の選択
MsgBoxInfコマンドによる結果の表示
●究極のクラスライブラリ化...OLE Server
それにしても、このプロパティはいかにもテストという感じで意味がないようなサンプルだが、メッセージボックスは結構使えるのではないだろうか。<中止>、<再試行>、<無視>などのボタンを表示するMsgBoxQARIなんかもスグに作れるし、あらかじめ用意してあれば、いちいち「あの定数なんだっけ」なんて考えなくても済むからラッキーである。こういう汎用に使えるプロシージャなどは、さっさとクラスにしていつでも使えるようにライブラリ化してしまいたい。
ところが、このクラスはこのままではこのアプリケーションでのみしか使うことができない。では、他のアプリケーションから使うときにはいちいちクラスのモジュールを読み込むというのか? それはいくらなんでもカッコ悪すぎる。しかし、クラスをライブラリ化するとかリポジトリデータベースとか、そんなものはVisualBasic 4.0には装備されていない(Power Objectsには装備されている。だから遅いのだろう。しかしVisualBasic 4.0も遅いぞ。β版だからか?)。
では、そんな中途半端なオブジェクト指向だったのだろうか。などといまさらとぼけてみても、みなさんすでにお気づきのように、ライブラリ化はOLEを使って行うのだ。つまり、クラスを元にしたOLEサーバーを作成し、OLE Automationで呼び出して使うワケだ。これは、まさにMicrosoftだから考えついたワザかもしれない。
では、実際に先ほどのクラスをOLEサーバーにしてみよう。
まず、フォームモジュールは必要ないから解放する。もちろん、クラスに密接に関係して部品の一部としてフォームや標準モジュールが必要なときには解放してはいけない。その部分だけはとっておく。
次に、標準モジュールを追加して、Sub Mainを作成する。内容は空で構わない。というのも、ここでは最終的にはOLE ServerとしてEXEファイルを作成するからだ。残念ながら、VisualBasic 4.0からDLLやOCXは作成することができないので、本体は何もしないEXEファイルを作ることになるわけだ。
次に、クラスのInstancingプロパティを「Createble Multi Use」に、Publicプロパティを「True」に設定する。これで、サーバーアプリケーションからインスタンスを作成して利用することができるようにプロパティを設定できたワケだ。
忘れてはいけないのは、OLE Serverとなるための設定がさらに必要であることだ。これは、[ツール][オプション]メニューの「プロジェクト」の設定である。スタートアップフォーム、プロジェクト名などを指定した上で、実行開始モードを「OLEサーバー」にしておく。さらに、「アプリケーションの説明」に分かりやすい一文を記入しておこう。これは、このサーバーを利用するかを選択するときに見えるものだ。
最後に、プロジェクト名をサーバーらしい別の名前にしておこう。ここでは、TFSRVにした。ちなみに、こうして設定したTFSRV.VBP、TXTFIL1.BAS、TEXTFIL2.CLSの内容は次のようになった。TEXTFIL2.CLSは、当然ながらプロパティの設定が変わっただけである。
・TXTFIL1.FRM
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Dim tf As New TextFile
Private Sub Command1_Click()
tf.filename = Text1.TEXT
End Sub
Private Sub Command2_Click()
Text2.TEXT = tf.filename
End Sub
Private Sub Command3_Click()
' tf.MsgBoxQYN "Can you see me?", "Test YN"
If tf.MsgBoxQYN("Can you see me?", "Test YN") = 6 Then
tf.MsgBoxInf "You selected ""Yes""", "Yes"
Else
tf.MsgBoxInf "You selected ""No""", "No"
End If
End Sub
・TXTFIL1CLS
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "TextFile"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Private tFileName As String
Property Get FileName() As String
FileName = tFileName
End Property
Property Let FileName(fn As String)
tFileName = fn
End Property
Function MsgBoxQYN(msg As String, title As String) As Integer
MsgBoxQYN = MsgBox(msg, 32 + 4, title)
End Function
Sub MsgBoxInf(msg As String, title As String)
MsgBox msg, 64, title
End Sub
<\PRE>
●OLE Serverのデバッグ
では、さっそくこのOLE Serverがきちんと動作しているか確認してみよう。
まずは、このプロジェクトを実行する。これで、OLE Serverは走り出した。しかし、これを呼び出すコンテナアプリケーションがないといけない。また作らなくてはならないのか...いや、そんな必要はない。先ほど解放したフォームが、そのままコンテナアプリケーションのソースになる。というわけで、もうひとつVisualBasic 4.0を起動する。こういうときのデバッグのために、VisualBasic 4.0からはマルチインスタンスで起動するようになっているのだ。
コンテナ側の参照設定
二つ同じエントリがあるが、上はEXEで下がVBPである。
先ほどのフォームを読み込んでチョチョイと起動フォームにしたら、さっさと実行しよう。しかし、「ユーザー定義型が定義されていません」というよくわからないエラーが発生する。これは、TextFileなるデータ型なんか知らないと言っているのである。つまり、外部にあるOLEサーバーの型なので、VisualBasicに対してどこのなんというファイルにある型なのかを知らせてやる必要があるのだ。これは、[ツール][参照設定]メニューを選ぶと、図のようなダイアログが現れるから、さきほどOLEサーバーに指定したアプリケーションの説明文字列を見つけ出せばよい。すでにもうひとつのVisualBasicで実行中であれば、「場所」には.VBPファイルが表示されるハズだ。ちなみに、EXEファイルを作成してあれば、同じ説明が表示されるが「場所」が違うのでわかる。

これで、正常に実行できるハズだ。ここでは、プロパティのGetに関わるコードについて、コンテナ側にもサーバー側にもブレークポイントを設定して動作を見てみた。図のように、コンテナ側では文字列をプロパティに代入したところで停止し、OLEサーバー側ではPrivateな内部変数に代入した値が、確かにコンテナ側で設定された文字列であったことが分かる。
ちなみに、このときOLE ServerでのPublicなメンバー変数や関数は、そのアプリケーション内でPublicなのではなく、Windowsのシステム全体でPublicである。つまり、OLE Serverを呼び出すことができる、OLE Automationをサポートしたツールからは、どんなものからでも呼び出される可能性があるのだ。それだけ汎用的なサーバーを作成できるわけだ。
このように、単体アプリケーションの一部クラスとして動いていれば、あっと言う間にOLE Serverにしてしまうことができるのだ。つまり、システムに登録されたクラスライブラリである。ちなみに、EXEを作成したときには、OLE Serverのインスタンスが作成されたときに初めてEXEファイルが実行されるように設定することができる。しかし、忘れてはいけないのは、OLE Serverに必要なモジュールが他に必要なときには、それがOLE Server側以外に使われるコードが混じっていないかの確認である。OLE Serverにする予定があるならば、最初からそれを見越した設計にしておく必要があるだろう。
●デバッグとエラー対策
OLE Serverアプリケーションに限らず、できる限りエラーが発生しないようにしておくことが望ましい。しかし、当然ながらエラーがまったくないプログラムを作るのは難しい。また、あらかじめメソッドの引数やプロパティの値に正しくない値が入力されたときには、呼び出し元のコンテナ側にエラーを知らせてやる必要もでてくる。
このようなときには、ErrオブジェクトのRaiseメソッドを使うとよい。これは、エラーを擬似的に発生させるメカニズムなのだが、サーバー側でエラー番号であるNuber引数に定数値vbObjectErrorをベースとした数値を指定する。こうすると、エラーはコンテナ側でエラーが発生する。なお、ここで「:=」という演算子を使っているが、これが名前付きの引数指定である。

ここでは、次のようなコードで、エラーの内容を示す文字列も指定してみた。空文字列がプロパティに指定されたときにエラーにするコードである。実際には、サーバー側をインタープリタで実行したときには、どうしてもサーバー側でエラーが発生してしまう。サーバー側がEXEになっていれば、コンテナ側に「OLEオートメーションのエラーです」と指定した文字列とは違った表示になるものの、番号は指定したものが表示される(図)。この動作は、β版固有のものである可能性が高い。
ともあれ、このようにOLE Serverでのクリティカルなエラーが発生する前に、引数の整合性などを調べて事前にエラーをコンテナ側に伝えるという仕組みも用意されているのは、現実的なアプリケーション開発を想定していることがわかって頼もしい。
・サーバー側
Property Let FileName(fn As String)
If fn = "" Then
Err.Raise Number:=(vbObjectError + 128), Description:="ファイル名を指定してください!"
Else
tFileName = fn
End If
End Property
・コンテナ側
Private Sub Command1_Click()
On Error Resume Next
tf.filename = Text1.TEXT
If Err Then
MsgBox Err.Description & " エラー番号 " & CStr(Err - vbObjectError), 48, "Error"
End If
End Sub
●コレクション
VisualBasicには、コレクションという概念がある。たとえば、FontsコレクションやControlsコレクションが有名どころだが、VisualBasic 3.0などにはデータアクセスに関係したコレクションもあった。たとえば、接続先にはいくつのテーブルがあって、それぞれのテーブルの属性はどうなっているか、さらにそのテーブルの要素には何という名前のフィールドがあって、データ型は何で...といったことが、すべてインデックスを使ってアクセスすることができる仕組みである。
VisualBasic 4.0では、このコレクションまでも作成することができる。コレクションは配列に似ているが、クラスを元にしたオブジェクトであること、メモリの使用量が少ないこと、メンバーの追加や削除をする言語仕様が用意されていることなど、メリットが多い。
実際には、コレクション型のオブジェクト変数を作成し、クラスのインスタンスかOLE Serverオブジェクトを作成し、そのエントリをコレクションに追加し、そこから削除、検索、参照することができるというモノだ。実際には、表のようなメソッドやプロパティを使うことができる。
この検索や削除といったメソッドは、そのまま外部から使えるように公開することもできるし、内部のみで使い外部へは独自のメソッドを公開することもできる。しかし、後者のような方法には、モノによってはずいぶんと複雑になってしまう。たとえば、DAO(データアクセスオブジェクト)などで使われているように、オブジェクトの一部をコレクションとして作成し、内部を隠蔽して公開し、外部からアクセスするインターフェイスを従来のVisualBasicライクなコレクションクラスとして作成するといったコトには、あまり向いた設計ではないように思う。というのも、あるクラスのオブジェクトのコレクションを作成するためのクラスを作るのが複雑になってしまうからである。
ここでは、コレクションをデータベースのように扱う例を示す。おそらく、コレクションはこういう使い方を想定して作られているように思う。
まず、Cameraクラスを作成する。このクラスには、リストのように3つのPublicなメンバ変数を宣言した。つまり、プロパティになるわけだ。単なる変数だから、プロパティに代入したり参照したりしても、それに対して特別な処理はされないTagプロパティみたいなものである。この変数は、name、year、memoで、それぞれカメラの名前、製造年、メモが対応するようにしてある。
・CAM1.CLS VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "Camera" Attribute VB_Creatable = False Attribute VB_Exposed = False Option Explicit Public name As String Public year As Integer Public memo As String
次に、フォームである。CAM1.FRMは、図のようなフォームである。モジュールレベルでPrivateにCamerasというコレクションオブジェクトを新規に作成している。
Private Cameras As New Collection
ここでのNewキーワードは、入れ物を用意するだけではなくて実体も作るという意味になる。もし、Newをつけない宣言ならば、クラスだけを作ったようなものでインスタンスとなるオブジェクトは作成されない。ここでは、スグにCollection型のオブジェクトを使いたいから、Newをつけたワケだ。
では、先ほどのクラスを元にしたオブジェクトを作成し、このコレクションに追加してみよう。<Add>ボタンを押されたときの処理
cmdAdd_Click()を見てみよう。ここでも、Cameraクラス型のオブジェクトを新規に作成している。
Dim cam As New Camera
そして、各プロパティにテキストボックスの内容を代入している。これで、オブジェクトの内容はセットされたわけだ。
このオブジェクトをコレクションに加えるには、CamerasコレクションのAddメソッドを使う。引数は、追加するオブジェクト名および検索キーである。検索キーはシーケンシャルな番号でも、オブジェクトの一部のプロパティでも構わない。
Cameras.Add cam, cam.name
これで、コレクションへの追加が完了した。つまり、テキストボックスに適当なデータを入力しては<Add>ボタンを押していけば、コレクションに追加されることになる。
同様に、特定のアイテムを削除するには、cmdRemove_Click()イベントにあるように、Removeメソッドを使う。引数はAddと同様である。
現在のコレクションの件数を得るには、cmdCount_Click()イベントにあるように、Countプロパティを参照すればよい。
特定のアイテムの内容を得るには、lstCameras_Click()イベントにあるように、Itemメソッドを使う。ここでは、リストボックスにCameraクラスのnameプロパティが一覧されており、そこから特定のアイテムを選ばれたときに、yearとmemoプロパティをテキストボックスに表示するコードが記述されている。
<Collection名>.Item(<キー>).<オブジェクトのメンバー>
というカタチで、その内容を検索して得ることができる。
こうして追加されたオブジェクトの内容一覧を得るには、どうしたらよいだろうか。もちろん、CountプロパティとItemメソッドを使ってFor〜Nextループで回すといった、従来のVisualBasicらしい方法もあるが、もっと便利な構文が用意されている。それが、For Each文である。
cmdList_Clickイベントを見てみよう。Camera型のオブジェクト変数が宣言されているが、Newキーワードが指定されていないから、実際のオブジェクトは入ってない。つまり空の入れ物である。リストボックスをクリアしてからFor Each文が実行されている。この文は次のような使い方をする。
For Each <変数> In <コレクションまたは配列名>
実行文
Next
unixのforeach文やDOSのFor文に使い方は似ている。ここでは、Camerasコレクションの各要素であるオブジェクトをひとつずつ取り出し、オブジェクト変数camに代入してくれる。そのひとつずつに対して何をするかを、実行文に記述すればよいわけだ。これもなかなか使える言語仕様の追加である。
最後に、コレクションの内容をファイルに書き込み、読み込みをするコードをご覧いただこう。
Saveは難しいことはない。cmdSave_Click()イベントを見ると、For Each文を使ってシーケンシャルファイルに各プロパティの内容をWriteしているだけだ。
LoadはcmdLoad_Click()イベントである。ここでは、For Eachで既存のコレクションの内容を削除している。次に、ファイルから各プロパティの内容となるべきデータを取得し、テキストボックスに表示して、cmdAdd_Clickイベントを呼んでいる。つまり、追加の処理をしているワケだ。
以上がVisualBasic 4.0で追加されたコレクション作成の機能である。一言追加しておくならば、コレクションされるオブジェクトは、その実体がなくなってもコレクションから取り出すことができるということだ。この例で言えば、cmdAdd_Click()イベントで宣言されているオブジェクトcamは、このプロシージャが終了すると同時になくなってしまう。しかし、内容はコレクションに保存されているのである。
・CAM1.FRM
VERSION 4.00
Attribute VB_Name = "frmMain"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit
Private Cameras As New Collection
Private Sub cmdAdd_Click()
Dim cam As New Camera
cam.name = txtName.TEXT
cam.year = txtYear.TEXT
cam.memo = txtMemo.TEXT
Cameras.Add cam, cam.name
cmdList_Click
End Sub
Private Sub lstCameras_Click()
txtName.TEXT = lstCameras.TEXT
txtYear.TEXT = Cameras.Item(txtName.TEXT).year
txtMemo.TEXT = Cameras.Item(txtName.TEXT).memo
End Sub
Private Sub cmdList_Click()
Dim cam As Camera
lstCameras.Clear
For Each cam In Cameras
lstCameras.AddItem cam.name
Next
End Sub
Private Sub cmdRemove_Click()
Cameras.Remove txtName.TEXT
cmdList_Click
End Sub
Private Sub cmdCount_Click()
MsgBox CStr(Cameras.Count) & "件あります", 64, "Count"
End Sub
Private Sub cmdLoad_Click()
Dim fn As Integer
Dim n As String
Dim y As Integer
Dim m As String
Dim cam As Camera
fn = FreeFile
Open "cam.txt" For Input As #fn
For Each cam In Cameras
Cameras.Remove cam.name
Next
Do Until EOF(fn)
Input #fn, n, y, m
txtName.TEXT = n
txtYear.TEXT = y
txtMemo.TEXT = m
cmdAdd_Click
Loop
Close #fn
MsgBox "Load Complete!", 64, "Load"
End Sub
Private Sub cmdSave_Click()
Dim fn As Integer
Dim cam As Camera
fn = FreeFile
Open "cam.txt" For Output As #fn
For Each cam In Cameras
Write #fn, cam.name, cam.year, cam.memo
Next
Close #fn
MsgBox "Save Complete!", 64, "Save"
End Sub
●実例OLEサーバーへの道のり
以上がVisualBasic 4.0でのオブジェクト指向の概要である。実際には、作成したクラスの親子関係などを定義してExcelやDAOのようなオブジェクトの親子関係を規定するといった機能など、すべては紹介しきれていないのではあるが、ほぼ全貌は見えてきたのではないだろうか。
しかし、機能として見えてもきても、ではいったいどうやって使ったらいいのかというところが見えてこない。これが冒頭でしつこく述べたように重要なところである。そこで、ここではプレビュウ機能付きのコモンダイアログボックスを作成し、従来の使い方からはじめて、VisualBasic 4.0の数々のオブジェクト指向に関する新機能を使って、どのように使いやすい、そしてメンテナンスしやすいプログラミングを目指すことができるかを実例を挙げて紹介しよう。
まずは、ここで作成したプレビュウ機能付きダイアログボックスを見てほしい(図)。通常のコモンダイアログボックスの機能に加えて、ピクチャー系のファイルのプレビュウとマルチメディア系ファイルの実行をその場でできる機能が追加してある。このフォームはこれだけで完結したアプリケーションとして使うことも可能だが、あくまでも他のフォームから呼び出して使うことを想定しよう。
そこで、ここでは図のようなフォームを作成し、
ボタンを押したらこのダイアログが開き、ダイアログで指定されたファイル名を表示できるようにしてみよう。
★従来の方法
まずは、従来のVisualBasicであればどう書くかというオーソドックスなコードを見てみよう。ダイアログボックスのコードは、PFDLG00.FRMである。一応一通りの内容を示すが、これはありがちなコードであり、特に目新しいものはない。
・PFDLG00.FRMから抜粋
VERSION 4.00
Private Sub cboSort_Click()
Dim tmp As String
tmp = Mid(cboSort.TEXT, InStr(cboSort.TEXT, "(") + 1)
File1.Pattern = Left(tmp, Len(tmp) - 1)
End Sub
Private Sub cmdCancel_Click()
tFileName = ""
Unload Me
End Sub
Private Sub cmdOK_Click()
If txtFileName.TEXT = "" Then
Beep
Exit Sub
End If
If Right(Dir1.Path, 1) <> "\" Then
tFileName = Dir1.Path & "\" & txtFileName
Else
tFileName = Dir1.Path & txtFileName
End If
Unload Me
End Sub
Private Sub Dir1_Change()
lblDir.Caption = Dir1.Path
File1.Path = Dir1.Path
End Sub
Private Sub Drive1_Change()
Dim oldPath As String
oldPath = Dir1.Path
On Error GoTo trapDrv:
Dir1.Path = Drive1.Drive
Exit Sub
trapDrv:
Beep
MsgBox "ドライブの準備ができていません!", 48, "エラー"
Dir1.Path = oldPath
Drive1.Drive = Left(oldPath, 2)
End Sub
Private Sub File1_Click()
txtFileName.TEXT = File1.filename
End Sub
Private Sub Form_Load()
lblDir.Caption = Dir1.Path
txtFileName.TEXT = File1.filename
cboSort.AddItem "All(*.*)"
cboSort.AddItem "Picture(*.bmp;*.wmf;*.ico;*.dib;*.cur)"
cboSort.AddItem "MultiMedia(*.wav;*.mid;*.avi;*.mmm)"
cboSort.ListIndex = 0
End Sub
Private Sub txtFileName_Change()
Dim dum As Integer
Dim tmpFileName As String
If Right(Dir1.Path, 1) <> "\" Then
tmpFileName = Dir1.Path & "\" & txtFileName
Else
tmpFileName = Dir1.Path & txtFileName
End If
On Error Resume Next
mmc.Command = "close"
On Error GoTo trapPreview:
Select Case LCase$(Right(txtFileName.TEXT, 3))
Case "mid", "wav", "avi", "mmm"
mmc.filename = tmpFileName
mmc.Command = "open"
mmc.Command = "play"
Case "bmp", "wmf", "ico", "dib", "cur"
imgPicture.picture = LoadPicture(tmpFileName)
Case "wri"
dum = Shell("write " & tmpFileName, 1)
Case "txt", "ini"
dum = Shell("notepad " & tmpFileName, 1)
End Select
resPreview:
Exit Sub
trapPreview:
MsgBox "プレビュウできません", 48, "エラー"
Resume resPreview:
End Sub
呼び出し元のCONT00.FRMをみてもこれと言った目新しいコードはない。しかし、この目新しいコードがないというところこそが特徴なのである。ここでは、実は次の4つのポイントがある。
1. ダイアログのタイトルを指定
直接ダイアログフォームのCaptionプロパティを指定している。
frmPreDlg.Caption = "プレビュウしてファイルを開く"
2. ダイアログのオープン
ダイアログのShowメソッドを使っている。
frmPreDlg.Show 1
3. <OK>、<キャンセル>のいずれのボタンが押されたか
グローバル変数tFileNameの内容が指定されているかどうかで判断している。
If tFileName <> "" Then
4. 指定されたファイル名の取得
グローバル変数tFileNameの内容である。
このように、標準(コード)モジュールに宣言したグローバル変数で、フォーム間で共有したい変数を指定しなくてはならなかった。また、自分以外のフォームのプロパティを参照する手もあったが、影響がないプロパティはTagなどに限定されている。また、他のフォームを表示するコードを記述した。これが、従来のコーディングである。
一番問題なのは、グローバル変数を使うために、わざわざコードモジュールを追加しなくてはならなかったことだ。さらに、このフォームを他のアプリケーションのプロジェクトに追加して使おうというときにも、フォームとコードの二つのモジュールが必要な上、この共通な変数がグローバル変数として、すでに使われていないかをチェックしなくてはならないなど、部品化するには問題があった。
今後、この4つの問題について改良を加えていくことにする。
・CONT00.FRMから抜粋
Private Sub Command1_Click()
frmPreDlg.Caption = "プレビュウしてファイルを開く"
frmPreDlg.Show 1
If tFileName <> "" Then
lblFileName.Caption = tFileName
Else
Beep
lblFileName.Caption = "Cancel"
End If
End Sub
・CONT00.BASから抜粋
Global tFileName As String
★グローバル変数の代わりにカスタムプロパティを使う方法
そこで、グローバル変数を使わない方法を考えよう。VisualBasic 4.0からはモジュールにプロパティを追加することができた。そこで、ファイル名をダイアログボックスのカスタムプロパティにしてしまうのである。といっても、単にPublicで変数を宣言するだけだ。
・PFDLG0.FRM
Public tFileName As String
呼び出し元では、frmPreDlgのtFileNameを使うと明示するだけでよい。
・CONT0.FRM
Private Sub Command1_Click()
frmPreDlg.Caption = "プレビュウしてファイルを開く"
frmPreDlg.Show 1
If frmPreDlg.tFileName <> "" Then
lblFileName.Caption = frmPreDlg.tFileName
Else
Beep
lblFileName.Caption = "Cancel"
End If
End Sub
たったこれだけの変更だが、余計な標準モジュールがなくなってすっきりした。たいした進歩であり、部品化も容易になったと言えるだろう。
★ダイアログにプロパティプロシージャを追加する
プロパティは有用であるが、プロパティプロシージャを使えば、もっと便利になる。直接他のフォームのCaptionプロパティを変更するなどというのは、本来あまりしてはいけないことだ。というか、このような標準プロパティはPrivateにする仕組みがあるべきで(もしかしたら気がついていないだけであるのかもしれない)、Publicと明示したプロパティのみに他のモジュールからアクセスできるようになっているべきだ。
というわけで、Titleプロパティを作成し、指定された文字列をCaptionプロパティに代入するようにした。また、tFileNameはPrivate変数にして内部ワーク用として隠蔽し、外部へのインターフェイスをとるプロパティとしてFileNameプロパティを作った。さらに、引数が1のときに自分自身をモーダル表示するプロパティActionを作成した。
・PFDLG1.FRMより抜粋
Private tFileName As String
Property Get FileName() As String
FileName = tFileName
End Property
Property Let Action(flag As Integer)
If flag = 1 Then
Me.Show 1
End If
End Property
Property Let Title(capt As String)
Caption = capt
End Property
これらのプロパティを使った呼び出し元のコードは、次のようになる。
標準プロパティを外部から操作しないので、なんだかだいぶカッコイイコーディングになってきた。しかし、まだまだだ。
・CONT1.FRMより抜粋
Private Sub Command1_Click()
frmPreDlg.Title = "プレビュウしてファイルを開く"
frmPreDlg.Action = 1
If frmPreDlg.FileName <> "" Then
lblFileName.Caption = frmPreDlg.FileName
Else
Beep
lblFileName.Caption = "Cancel"
End If
End Sub
★ダイアログにカスタムメソッドを追加する
どのへんがまだまだかと言えば、プロパティが動作に結びついている点だ。これではまるでメソッドをカスタマイズできないVBXみたいなコードではないか。OLE Automation時代のコードはこれではいけない。
VisualBasic 4.0ではカスタムメソッドも作成できるのだから、ファイルダイアログをオープンするメソッドを作ってしまおう。ついでだから、このメソッドの引数にタイトルを指定できるようにしてしまえば、もっとコードがスッキリするハズだ。さらに、このメソッドの戻り値として、<OK>か<キャンセル>のどちらが押されたかを返すようにすれば、さらにグッドである。
・PFDLG1.FRMより抜粋
Public Function OpenPreDlg(ByVal Title As String) As Integer
Me.Caption = Title
Me.Show 1
If tFileName = "" Then
OpenPreDlg = False
Else
OpenPreDlg = True
End If
End Function
このメソッドを呼び出すように変更したコードだ。今までになくスッキリしていて分かりやすい。
・CONT1.FRMより抜粋
Private Sub Command2_Click()
If frmPreDlg.OpenPreDlg("プレビュウしてファイルを開く") = True Then
lblFileName.Caption = frmPreDlg.FileName
Else
Beep
lblFileName.Caption = "Cancel"
End If
End Sub
★プロパティやメソッドをクラスに書き直す
これで呼び出すプロパティやメソッドは決定していいだろう。では、このフォームに属しているプロパティやメソッドをクラスに書き直し、OLE Serverとして汎用に使えるようにしたい。まずは、単独で動くように変更してみよう。
ここでは、クラスモジュールPREDLG.CLSを作成し、ワーク用変数とFileNameプロパティのGetプロシージャおよびOpenPreDlgメソッドを、PFDLG1.FRMから移動する。必要のないプロパティプロシージャは削除する。
基本的には、コードのコピー&ペーストだけの作業だから、ものの数十秒の作業だ。
・PREDLG2.CLS
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "PreviewDialog"
Attribute VB_Creatable = True
Attribute VB_Exposed = True
Option Explicit
Private PreFileName As String
Property Get FileName() As String
FileName = frmPreDlg.tFileName
End Property
Public Function OpenPreDlg(ByVal Title As String) As Integer
frmPreDlg.Caption = Title
frmPreDlg.Show 1
If frmPreDlg.tFileName = "" Then
OpenPreDlg = False
Else
OpenPreDlg = True
End If
End Function
呼び出し元は、PreviewDialogクラス型のオブジェクトを作成して、そのオブジェクトのプロパティやメソッドを使うというように書き換える必要がある。とはいっても、難しいことではない。Dim文でオブジェクト変数のインスタンスを作成して、そのまま使えばよい。この書き換えも一瞬だ。
・CONT2.FRM
Private Sub Command1_Click()
Dim pd As New PreviewDialog
If pd.OpenPreDlg("プレビュウしてファイルのオープン") = True Then
lblFileName.Caption = pd.filename
Else
lblFileName.Caption = "Cancel"
Beep
End If
End Sub
★OLE Serverにする。
ここまで動いたらしめたモノ。あとはOLE Serverにするだけである。これには、標準モジュールを追加して、Sub Mainをつける。
・PFDSRV.BAS
Attribute VB_Name = "modPfdsrv"
Option Explicit
Sub Main()
End Sub
さらに、[ツール][オプション]メニューでプロジェクトの実行開始モードおよびアプリケーションの説明などをOLE Serverとして使えるように設定しなおせばよい(図)。忘れていけないのは、呼び出し元のフォームをプロジェクトから外すことと、起動フォームを設定しなおすことだ。
ここまでできたら、例によって、VisualBasic 4.0を複数起動して動作を確かめてもいいし、さっさとEXEファイルを作ってシステムに登録してしまうのも手だ。
すでに汎用に使うことのできるOLEサーバーが、Windowsの一部のようにVisualBasicなどのOLE
Automationコンテナから呼び出すことができるようになったのである。
●オブジェクト指向でラクをしよう
このように、自分がよく使うようなルーチンやフォームなどは、外部とのインターフェイスだけうまく規定できれば、スグにOLEのサーバーにしてしまうことができる。今回紹介したように、単独のアプリケーションとして動作をチェックし、インターフェイスをクラスにし、さらにサーバーとコンテナに分けるといった手順を踏めばいいのだ。
ここでも、なかなか使えるダイアログがいつでも呼び出せるようになった。このような部品となるOLE Serverモジュールが増えてくれば、新たにアプリケーションを作ったときにも、部品として呼び出すだけでその機能が取りこめる。なんてラクなんだろう。もちろん、バグが見つかったときにも、問題のあったモジュールだけをバグフィックスするだけでよいのだ。メンテナンスもラクである。
このように、オブジェクト指向とはラクをするための仕組みである。ただ、ラクをするため、すなわち開発効率やメンテナンス性を上げるためには、各モジュールの機能の範囲設定や、外部とのインターフェイスを取り持つプロパティやメソッドなどの設計などには、それなりのセンスが必要になる。最初からすべてうまく行くわけではないだろうが、何度かトライしているうちに、カッコイイOLE Serverを作ることができるだろう。
VisualBasic 4.0のオブジェクト指向は強力である。今回紹介しきれなかったコトも少なくない。すべてを理解して使いこなすには、それなりに時間も努力も必要になるだろう。しかし、それもラクをするためなのだ。
しかし、そういう努力より今までの気楽なスクラッチ&ビルドのラクの方がいいという方もいるだろう。そういう方にも応えてくれるのがVisualBasic 4.0の本当にいいところなのかもしれない。
それにしても、ここまでよくできているだけに、やはりインプレースアクティべーションができないOLE Serverであるというコトだけが残念だ。もっとも、そこまでやってしまったら、OCXの立場がなくなってしまう。
VisualBasic 4.0の登場で、今後のコンポーネントパーツ市場には、OCXだけでなくこのようなカタチで作られたOLE Serverが登場してくるのかもしれない。