製品版のVisual Basic 5.0については、原稿執筆時の97年1月初頭にも正式なアナウンスはないが、すでに前回お知らせしたように、MicrosoftのインターネットWWWサイトからVisual Basic 5.0 Control Creation Editionが公開されている。本来ならばMicrosoftのActiveXコントロール普及作戦の一環と考えられるが、未発表のVisual Basic 5.0についてもさまざまな新機能をかいま見ることができる。特に、従来はVisual Basicから使えなかったWindows APIを活用するための言語仕様が追加されているのは注目に値する。
今回は、Wizardを使ってWindows APIをカンタンに使うことができるActiveXコントロールを作成するほか、このようなVisual Basic 5.0についての新しいトピックについても述べることにしよう。
酒井 法雄
前回のレポートでは、VB5CCE手に入れて数日で書いてしまったということもあり、脱稿後に調査してみると、あるとは思っていたがやはりハズしていた部分が少なからずあった。
内容的にはおおむね重大な問題はないのだが、あんなに面倒なコーディングをいちいちになくても、実は「ActiveX Control Interface Wizard」を使えば、面倒な部分のテンプレートを作ってくれたのだ。
ドキュメントやヘルプ、サンプルプログラムの拾い読みをしながら、どのような仕組みでActiveXコントロールを作ればよいかを念頭に調べたものだから、自分なりにコードを解析するという作業になってしまったので、Wizardなどという怪しいものは後回しになったのだ。実際には、このデキのよいWizardの出したコードを解析した方が、VB5CCE入門記事としては適切だったかもしれない。しかし、仕組みを理解するという意味では、最初はWizardに頼らないのがいいことにこしたことはない。というような弁解をいつまでしていてもしかたないので、まずはこのWizardを使ってみることにしよう。
Visual Basic 4.0から、Visual BasicでもAdd-Inが使えるようになった。これは、Visual Basic自体が持っている開発環境に関するオブジェクトを公開してあり、それを操作するOLE Serverを起動し、開発環境をプログラムで操作させてしまおうというものだ。これで何ができるかと言えば、Wizardのようなものである。もちろん、Wizard自体もVisual Basicで書くことができる。 VB5CCEにも、もちろんこのAdd-Inの仕組みが用意されている。そして、 Proerty Page WizardとActiveX Control Interface Wizardが含まれている(図1)。
図1:Propaty Page WizardとActiveX Contol Wizard
前者は、作成したActiveXコントロールにプロパティシートを追加するためのもので、これは前回もちょっとだけいじってみた。
後者は、前回に調査していなかったActiveX Control Interface Wizardで、ActiveXコントロールの外部インターフェイスの仕様を規定すれば、スケルトンを吐いてくれるというもので、現実的に開発する場面では非常に便利なものだ。
流れとしては、プロパティ、メソッド、イベントの名前や型、引数などを順に決めてやれば、対応するコードが自動生成されるというものだ。もちろん、できるのはスケルトンなので、これだけでは動かないので、必要なコードを追加してやる必要があるし、動きに応じて内容を大幅に書き換える必要があるケースもあるだろう。
この手のウィザードの吐くコードというのは、内容を理解していないと後でどうにもならないものであるという点では、AccessなどのWizardと同じである(いや、AccessなどのWizardでは分かっていてもやっていられないところがある)が、必要なコードを追加して完成されるという点では、むしろVisualC++のWizardに似ている。
しかし、あらかじめ配置しておいたコントロールなどのオブジェクトのプロパティやメソッドなどに、作りたいプロパティやメソッドをマッピングしていくような機能を持っているので、ゼロから作ってくれるわけではない。
いずれにしても、Wizardを使いこなすためには、前回に紹介したようなVB5CCEでのActiveXコントロールが動く仕組みを理解している必要がある。
このWizardは、一度作成した後にプロパティなどのメンバーを追加したり削除したりすることもできるが、内容を大幅に変更した後だと、うまく整合性が取れなくなる。したがって、最初にしっかりとした仕様を決定しておく必要がある。そこで、全体の流れとしては、次のようになる。
|
仕様の決定 ↓ ActiveXコントロールプロジェクトの新規作成 ↓ 必要なコントロールなどの配置 ↓ オブジェクトのプロパティなどの設定 ↓ ActiveX Control Interface Wizardでスケルトン作成 ↓ コードの追加 ↓ コントロールの暫定的な完成 ↓ Proerty Page Wizardでプロパティシートを作成 ↓ プロパティシート用のコードを追加 ↓ Standard Exeプロジェクトの追加 ↓ ActiveXコントロールの貼り付け ↓ デバッグ用プログラムの作成 ↓ デバッグ ↓ 完成 |
この流れからも分かるように、Wizard自体は便利なものではあるが、全体の作業量から見ると、Wizardがやってくれるのは、実はささいな分量でしかない。
VB5CCEでのActiveXコントロールの仕組みについては、前回に説明したので、今回はWizrdを使うこともあり、このへんの話は一切しない。その代わり、 Wizardを使いこなすためのポイントを中心に述べることにしよう。
今回は、いわゆるインターネットで使えますみたいなマヤカシものではなく、むしろVisual Basicを拡張して使う上で、実際に役に立つモノであることを念頭に置き (今回のVisual Basic Magazineの特集がWindows APIということもある) 、Windows APIをカンタンに使えるものとした。
ご存じのように、Windows APIを使えば、Visual Basicの生産性は非常に高くなる。しかも、後で述べるようにVB5CCEでは従来は使えなかったCallback関数などが使えるようになっている。はっきり言って、もう何でもアリなのだ。したがって、ネタは無限に出てくるのだが、サンプルとして適切な内容ということもあり、あまり複雑すぎてはいけないということで、ここでは比較的カンタンに実現できるマウスまわりのAPIを題材にしてみよう。
| GetCursorPos | マウスのスクリーン座標を得る |
| SetCursorPos | マウスのスクリーン座標を設定する |
| mouse_event | マウスの移動やボタンをシミュレートする |
これだけあれば、マウスについては一通りのことができそうだ。さらに関連するものとして、次のようなものがある。
| WindowFromPoint | スクリーン座標位置にあるウィンドウのハンドルを得る |
| SetCursorPos | 指定したウィンドウハンドルからタイトルの長さを得る |
| mouse_event | 指定したウィンドウハンドルからタイトルを得る |
本当は、この他にもウィンドウのクラス名や実行ファイル名、参照カウント、コントロールID、関連するメニュー関係などの情報などを得たりできるといいのだが、これをやり出すと切りがないし、何でも入っているActiveXコントロールというのも、かえって不便そうだ。そこで、この程度のものにしよう。
これらのWindows APIからできること、やりたいことを考えて、次のような仕様を決定した。なお、本稿はWindows APIの使い方を解説するものではないので、Windows API自体の使い方の説明はさっくり省いてある。悪しからずご了承いただきたい。
これらを実現するために、次のようなプロパティ、イベント、メソッドを作ることにしよう。
property TargetWindowTitle As String ターゲットのウィンドウのタイトル TargetWindowhWnd As Long ターゲットのウィンドウのハンドル Event OnTargetWindow(x, y, hWndX,, title) ターゲットウィンドウの上に移動すると発生 ChangeOnWindow(x, y, hWndX, title) マウス下のウィンドウが変化すると発生 Method GetPos(x, y) マウスポインタの位置を得る SetPos(x, y) マウスポインタの位置を絶対位置指定で移動 MovePos(x, y) マウスポインタの位置を相対位置指定で移動 DoMouseClick(button) マウスボタンのクリックをシミュレート DoMouseDblClick(button) マウスボタンのダブルクリックをシミュレート DoMouseDown(button) マウスボタンのダウンをシミュレート DoMouseUp(button) マウスボタンのアップをシミュレート
では、この仕様に合わせてActiveXコントロールを作っていくことにしよう。
ここでは、次のようにプロパティを設定した。
Project (Name) Mptr UserControl (Name) Mouse Picture Icon mouse01.ico(VB4付属のもの) InvisibleAtRuntile True BorderStyle Fixed Single
コントロールはピクチャーサイズに手動で適当に合わせておこう。図のようになる。
ウィザードで重要なのは、プロパティなどのメンバーを決定することだが、往々にしてこれらはユーザーコントロール自体やそれに配置したコントロールなどのオブジェクトのメンバーと同じ名前のものであり、実際に同じものであることも多い。たとえば、BackColorやテキストボックスなどのFontといったものは、そのままこのActiveXのプロパティにしたいこともあるだろう。このように、基本的なプロパティは、あらかじめリストされている。しかし、ここではすべて必要ないので、右側のリストボックスのアイテムを全て選択して[<<」ボタンを押して削除する。必要なときには、それだけを残せばよい。
先ほどのようなストックメンバー以外のメンバーが必要なときには、その名前とプロパティ、メソッド、イベントのいずれであるかを指定する。[New]ボタンを押すと、ダイアログが開いてこれらの設定ができる。[Edit]で修正、[Delete]で削除ができる。追加されたメンバーはリストされる。
ここでは、先ほど仕様として決定したメンバーをすべて指定する。
ストック、追加のメンバーは、先に述べたように、すでにあるオブジェクトのメンバーにそのまま対応していることがある。このようなときには、それぞれのメンバー、どのオブジェクトのどのメンバーと同じものであるかをマッピングすることができる。
ここでは、必要がないので特に指定しない。
プロパティ、メソッド、イベントには、それぞれ型や引数がある。この画面ではそれらを指定する。
プロパティであれば、Return Typeにデータ型、Default Valueに初期値、Run Time, Design Timeは、実行時およびデザイン時に参照や代入ができるかを指定する。
メソッドであれば、Return Typeにデータ型、Argumentsに引数を指定する。戻り値のないメソッドのときには、Return TypeにEmptyを指定する。
イベントであれば、Argumentsに引数を指定する。
このように、できるのはスケルトンのみであるが、あとは必要なコードを追加するだけで、ActiveXコントロールができるというワケだ。
Option Explicit
Type POINTAPI
x As Long
y As Long
End Type
Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Boolean
Declare Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Boolean
Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Public Const MOUSE_MOVED = &H1
Public Const MOUSEEVENTF_ABSOLUTE = &H8000
Public Const MOUSEEVENTF_LEFTDOWN = &H2
Public Const MOUSEEVENTF_LEFTUP = &H4
Public Const MOUSEEVENTF_MIDDLEDOWN = &H20
Public Const MOUSEEVENTF_MIDDLEUP = &H40
Public Const MOUSEEVENTF_MOVE = &H1
Public Const MOUSEEVENTF_RIGHTDOWN = &H8
Public Const MOUSEEVENTF_RIGHTUP = &H10
Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
まずは、このUserControlモジュール全体で使う変数などを宣言しておこう。マウスまわりのWindows APIでは、xとy座標をまとめて扱うPOINT型を使う。Visual BasicではPointは予約語になっているので、先ほど追加した定義でも、POINTAPI型になっている。まずは、このPOINTAPI型の変数ptと、関数呼び出し時の戻り値を得るLong型ダミー変数dumを宣言する。
' Add Private Variables: Private dum As Long Private pt As POINTAPI
マウスポインタの指定座標への移動には、SetCursorPosを使う。これは、まったくそのまま使えばよい。
Public Sub SetPos(x As Long, y As Long)
SetCursorPos x, y
End Sub
マウスのスクリーン座標を得るには、GetCursorPosを使う。取得された値はptに入るので、xとyに分解して返してやればよい。
Public Sub GetPos(x As Long, y As Long)
GetCursorPos pt
x = pt.x
y = pt.y
End Sub
マウスポインタの相対座標指定での移動には、mouse_eventを使う。このWindows APIはマウスボタンのシミュレートなど多彩な機能を持っているが、その中からこの機能を使うために、 MOUSEEVENTF_MOVEを指定する。その後には相対移動する移動量をx, y方向について指定する。残り二つはオプションであり、ここでは必要ない。
Public Sub MovePos(x As Long, y As Long)
mouse_event MOUSEEVENTF_MOVE, x, y, 0, 0
End Sub
これらのメソッドも、いずれもmouse_eventを使う。引数には、どのボタンがどうされたという指定だけである。したがって、メソッド自体の引数としてボタンがあるので、渡された引数Buttonによってmouse_eventの第一パラメータを指定してやるだけだ。
DoMouseDownでは、MOUSEEVENTF_xxxDOWNを呼ぶだけでよい。
Public Sub DoMouseDown(Button As Integer)
Select Case Button
Case vbLeftButton
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
Case vbRightButton
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
End Select
End Sub
DoMouseUpでは、MOUSEEVENTF_xxxUPを呼ぶだけでよい。
Public Sub DoMouseUp(Button As Integer)
Select Case Button
Case vbLeftButton
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
Case vbRightButton
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
End Select
End Sub
DoMouseClickでは、 MOUSEEVENTF_xxxDOWNの後に連続してMOUSEEVENTF_xxxUPを呼べばよい。
Public Sub DoMouseClick(Button As Integer)
Select Case Button
Case vbLeftButton
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
Case vbRightButton
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
End Select
End Sub
DoMouseDblClickでは、DoMouseClickでやったことを二回繰り返せばよい。厳密には、クリックの間隔時間などを設定しないといけないような気がするが、私の環境ではこれで問題なくダブルクリックとして認識された。
Public Sub DoMouseDblClick(Button As Integer)
Select Case Button
Case vbLeftButton
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
Case vbRightButton
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0
End Select
End Sub
イベントについては、テンプレートはない。イベントのプロトタイプ宣言をしてくれるだけである。これは当然で、どのようなタイミングでイベントを起こすかはプログラマの決めることである。
ここで必要なイベントは、マウスポインタ位置およびの下にあるウィンドウの監視であるから、タイマーで移動を監視する必要がある。そこで、UserControlオブジェクトMouseにタイマーコントロールをひとつ貼り付けて、Intervalプロパティを200とした。このタイマーイベントで、マウスポインタの現在位置を監視するのである。
実際には、次のような手順で実現している。
以前のタイトルの値Static lastTitleと、現在得たウィンドウのタイトルが同じかを調べ、違ったときにはChangeOnWindowイベントを発生させる。引数は、すでにすべて求めてあるので、そのまま与えられる。
このとき、さらにあらかじめ指定されたタイトルまたはハンドルのウィンドウであるかを調べ、合致したときには、OnTargetWindowイベントを発生させる。
以上のコードは次のようになる。
Private Sub Timer1_Timer()
Dim hWndX As Long
Dim l As Long
Dim s As String
Static lastTitle As String
' Get Position
GetCursorPos pt
hWndX = WindowFromPoint(pt.x, pt.y)
' Get Text Length
l = GetWindowTextLength(hWndX) + 1
' Get Text
s = String$(l, 0)
dum = GetWindowText(hWndX, s, l)
s = Left(s, InStr(s, Chr$(0)) - 1)
' Change On Winodw?
If s <> lastTitle Then
RaiseEvent ChangeOnWindow(pt.x, pt.y, hWndX, s)
lastTitle = s
' Find Target ?
If m_TargetWindowhWnd <> -1 Or m_TargetWindowTitle <> "" Then
If hWndX = m_TargetWindowhWnd Or InStr(s, m_TargetWindowTitle) <> 0 Then
RaiseEvent OnTargetWindow(pt.x, pt.y, hWndX, s)
End If
End If
End If
End Sub
作成したActiveXコントロールのモジュールのウィンドウをクローズすれば、ツールボックスにActiveXコントロールのアイコンが出てくる。これを通常のやりかたでフォームに貼り付けよう。図のようになる。

図8:完成したプログラム
VB5CCEというと、どうしてもActiveXコントロールを作るための道具のように見られがちだが、実際にはこれはVisual Basic 5.0のサブセットである。したがって、執筆時にはまだ正式に発表されていないVisual Basic 5.0の機能などが数多く含まれている。特にヘルプやドキュメントをよく読むと、VB5CCEでは使えないActiveX Documentsなどの新しいアーキテクチャーについても、しっかりと書いてある。
それらの中から、VB5CCEでも使える新しい機能はないだろうかと、探してみると意外にも使えるモノが多い。中でも面白いのは、クラスモジュールでもイベントが使えることや、Windows APIのCallback関数を使えるようになっているあたりだ。
気づいたモノを全部紹介したいところだが、時間もページも限られている。気づいたことは、時間ができ次第順次PCDN(http://pcdn.int21.co.jp/pcdn/)などで発表することにして、ここでは、Callback関数について述べることにしよう。
Visual Basicを使うと、Windowsアプリケーションが実にカンタンにできるというのは、すでに読者諸氏はご存じの通りだろうが、もう一歩先のことをしようとすると、どうにもならないというのも、もちろん先刻ご承知のことと思う。
こんなときには、Visual Basicを拡張する手段として、Windows APIとOCX、すなわちActiveXコントロールの二つがある。
ActiveXコントロールは、高機能なものが市販されており、やりたいことをVisual Basicの中だけで実現できるので、非常に便利なものだ。しかし、バグがあったときにはお手上げだし、機能が豊富すぎて理解するのに時間がかかるといった問題もある。
一方、Windows APIはWindowsプログラミングの基本要素である。Windowsプログラミングは、Windows APIとメッセージが基本であり、それを独自の概念でカンタンに扱えるようにしたのがVisual Basicなのだ。したがって、ドライバーを作らなくては実現できないようなこともあるが、ふつうはWindows APIが使えればすべての処理はできるのである。
Visual Basicでは、このWindows APIを呼び出す仕組みが用意されている。Visual Basic自体の言語仕様ではないため、関数のプロトタイプや定数、型などをVisual Basicに合わせて宣言または定義してやらなくてはならない。もちろん、C言語やOSについての知識、Windows API自体の知識が必要になるので、半端なものではない。しかし、その面倒ささえクリアできれば、Windowsプログラミングの新天地が開けているといっても過言ではない。
Windows APIの魔力に取りつかれると、気づいたらVisual BasicのはずがWindows APIだらけのコードとなっており、何のためにVisual Basicを使っているのかわからない、果ては、Visual BasicでできることをわざわざWindows APIを使ってやっているというようなことになってしまう。
ところが、そこまでやっても実はできないことがあった。それが、Callback関数である。
Visual Basic 4.0までの言語仕様では、SubやFunctionなどの関数のアドレスを知ることはできなかった。これは、危ないことはやらせないというBasic言語の大原則がある。また、Windowsではメモリのアドレスが動的に変化するため、管理が非常に面倒であり、そのへんはまとめてVisual Basicがやってしまうからユーザーは知る必要がないということだったのだ。ところが、Callback関数と言うモノを使うには、この関数のアドレスを知ることが必須だったのである。
Callback関数はいろいろな場面で使われる。たとえば、EnumFontXXXやEnumWindows系の関数は、Windows上にあるフォントやウィンドウのような情報を、順に取得して、毎回ある関数に渡してやる。その関数が、たとえばリストボックスや配列にその情報を追加していったり、特定のもののときに、ある処理をしてやるというようなものだ。つまり、インストールされているフォントの数や、存在するウィンドウの数だけ、その関数が呼び出されるようにするのである。このとき、その関数のアドレスを指定してやる必要があるのだ。
ところが、前述のようにVisual Basicでは関数のアドレスという概念がないので、これらのWindows APIを呼び出すことはできても、肝心の処理先の関数を指定することができなかったというワケだ。
このように、Callback関数が必要となるものは、他にウィンドウプロシージャをサブクラス化するSetWindowLongや、メッセージチェーンをフックするSetWindowsHookExなどがある。これらは、用途の違いはあるが、Visual Basicではイベントとして知ることのできないメッセージを得るときに必要になるものだ。したがって、MessageBlasterやSpyWorksのようなVBXやOCXとして実現するしかなかったのである。
VB5CCEのヘルプなどを見ていて発見したのが、これを解決するAddressOf演算子である。これは、指定した関数のアドレスを得るための演算子であり、まさにCallback関数を使うためのモノであった。
MicrosoftはWindows APIの使用はサポート範囲外と明言していた(なまはんかな知識ではWindows APIをちゃんと使いこなせないので、これは当然だろう)ハズだが、ヘルプにしっかりとサンプルまで掲載されているではないか。
これならば、存在するウィンドウの一覧など、EnumXXX系関数が全部使えるし、SetWindowLongを使ってサブクラス化して、Visual Basicのイベントでは知ることのできないメッセージも知ることができる。もう、何でもアリではないか。
というわけで、今回はこれをちょっとだけ使ってみることにしよう。
こんな見出しを書くことができるとは思わなかった。これは、本当に興奮モノである。
しかし、話はそんなにカンタンではない。制限もしっかりとあるのだ。
AddressOf演算子はどこからでも使えるが、この後に指定する関数は、標準モジュールでなくてはならないのである。クラスやフォーム、ユーザーコントロール、ユーザードキュメントなどのモジュールでは使うことができない。
また、関数の戻り値は、Long型でなくてはならない。
他にもどこかをAny型を使わなくてはならないといった記述があるが、サンプル自体がそうなっていないし、実際にAny型にしなくても動いた。このあたりは、正式版になったときに変更になる可能性もある。したがって、ここで紹介するのは、あくまでもテストとしてのサンプルであることをご了解いただきたい。
こういった制限さえクリアできれば、実際に使うのはカンタンである。ここでは、EnumWindowsを次のように使った。
★Windows APIの宣言
Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, lParam As Any) As Long
★標準モジュールの変数宣言
Public m_WinNum As Integer
Public hWnds() As Long
★ UserControlモジュールEnumWin.CtlのRefreshメソッド
Public Sub Refresh()
Dim dumb As Boolean
ReDim hWnds(0)
m_WinNum = -1
dumb = EnumWindows(AddressOf EnumWinProc, 0&)
End Sub
★ 標準モジュールのEnumWinProc
Public Function EnumWinProc(ByVal hWndX As Long, lParam As Long) As Boolean
m_WinNum = m_WinNum + 1
ReDim Preserve hWnds(0 To m_WinNum)
hWnds(m_WinNum) = hWndX
EnumWinProc = True
End Function
たったこれだけで、Refreshメソッドを実行すれば、すべてのウィンドウのハンドルは、hWnds()配列に入ってしまうである。 あとは、ウィンドウの個数のプロパティ(これはすでに変数としてある)と、個別に取り出すメソッドなどを用意すればよい。
★ ウィンドウハンドルを得るhWndメソッド Public Function hwnd(Num As Integer) As Long If Num < 0 Or m_WinNum < Num Then hwnd = -1 Else hwnd = hWnds(Num) End If End Function ★ウィンドウのタイトルを得るGetWinTextメソッド Public Function GetWinText(hWndTarget As Long) As String Dim l As Long Dim s As String Dim dum As Boolean l = GetWindowTextLength(hWndTarget) + 1 s = String$(l, 0) dum = GetWindowText(hWndTarget, s, l) GetWinText = s End Function
こうして作成したActiveXコントロールを、フォームに貼り付けて、呼び出すプログラムを作成してみた。実行画面および、フォームのコードを示す。リストボックスに表示されたタイトルをダブルクリックすると、そのウィンドウがアクティブになる。

図9:EnumWindowサンプル画面
★EWTest.Frm
Option Explicit
Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
Private Sub Command1_Click()
Dim i As Integer
EnumWin1.Refresh
lblTotl.Caption = EnumWin1.WinNum & " Windows"
List1.Clear
For i = 0 To EnumWin1.WinNum
If chkVisible.Value = 0 Or IsWindowVisible(EnumWin1.hwnd(i)) Then
List1.AddItem Hex$(EnumWin1.hwnd(i)) & ":" & EnumWin1.GetWinText(EnumWin1.hwnd(i))
End If
Next i
End Sub
Private Sub List1_DblClick()
AppActivate Mid(List1.Text, InStr(List1.Text, ":") + 1), True
End Sub
この他にも、SetWindowLongを使ったActiveXコントロールも作成してみた。この辺はデバッグにかなり手間取った上、動作がいまいちおかしいので、画面だけ紹介するが、図のように、システムメニューで選択中のメニューIDを得ることができた。また、WIN.INIの変更や時刻の変更なども、知ることができた。つまり、サブクラスも成功したわけである。ただし、すべてをVisual Basicで記述したときには、パフォーマンスの点などで問題が出る可能性もある。このようにActiveXコントロールにするのではなく、その場で書いてしまった方が良さそうだ。

図10:SetWindowLongを使ったサンプル
Microsoftは、商売がうまい会社だ。いろいろな技術を買ってきては取り込み、うまく行かなかったモノは捨てる。しかし、その中核となる技術は研究し続けている。
だから、たとえばWinGなどは期待したらバカを見たが、Visual BasicやOLEは、やっていて損はなかったハズだ。特に、Visual BasicはMicrosoftの戦略上重要な位置にあり、これがなくなったら、Microsoftもなくなるのではないかという程のものである。
Microsoftは、OS提供するサービスすべてをOLEで、すなわちVisual Basicからできるようにすると公言してきた。そのOSがCairoだと言ってきたわけだが、いつまで経ってもCairoは出ないばかりが、Cairoは一連の技術の総称であり、そんなOSはないのだと言い出した。
バカな私はずっとOLEを信じてきたので、これはちょっとショックだった。もう、Win32 APIも使う必要もなくなると、あまりいじっていなかったからだ。しかし、現状では、そしてまだしばらくはWindows APIの活躍する機会は多いに違いない。だが、Visual BasicからWindows APIを使うのは手続きが面倒くさいし、制限が多い。
ところが、VB5CCEでは、これが見事に結合した形になった。Windows API使用の制限が緩和された。そして、それらをActiveXコントロールとしてOLEベースで再利用できるコンポーネントとして作ることができるようになったのだ。
今後の方向性としては、たしかにOLEなのだろうが、現実的な解決がVisual Basic 5.0でできるようになる見込みがついてきたのだ。
だが、急がなくていい。製品として安定したVisual Basic 5.0の発売を、VB5CCEをいじりながら私は待ちたい。