Visual Basic 200%活用術
おしゃべりシステムインフォメーションの作成
〜あまり知られていないVBの機能を活用しよう
アイデア+テクニック=200%
Visual Basicはすでに何でもできる開発ツールである.しかし,それゆえに内容は多大であり,すべてを使いこなすことは難しい.特に,Visual
Basic 4.0以降には知っているととても便利な機能ながら,あまり知られていないものも少なくない.
ここでは,Visual Basicのあまり知られていない機能と,ActiveXコントロールやWindows
APIを駆使し,さらにちょっとしたアイデアを加えた楽しいプログラムを作ってみよう.
| ●200%活用するための指針 |
以前のVisual Basicはオモチャに近いものであり,あくまでもビギナー向けの開発ツールだった.Windowsネイティブならできることも,Visual
Basicからでは実現が難しいものも少なくなかった.
これを拡張するために,以前ならVBXコントロール,今ならActiveXコントロール(OCX)を使ったり,Windows
APIを駆使したり,サードパーティのライブラリなどを追加したりした.この方向性は,今でも基本的には変わっていない.
しかし,バージョン4.0からはC/S(クライアント/サーバー)型RDBMSへの対応がなされた他,ClassをベースとしたOLE(ActiveX)サーバーなどのコンポーネントをVisual
Basicだけで作れるようになった.この流れは,バージョン5.0でActiveXコントロールが作成できるようになったり,COMまわりが強化され,さらに推し進められた.また,AddressOfなどの演算子がサポートされ,Windows
APIの使い勝手も向上した.
これらの内容を駆使すれば,Visual Basicがもっている機能を100%活用しただけで,かなりのことができるようになっている.しかし,あなたはこれらの機能を100%活用できているだろうか?
膨大になった言語仕様やライブラリを使いこなせているだろうか?
もちろん,すべてを使いこなす必要はない.しかし,特に新しい概念の言語仕様やツールには,とても便利なものがある.そして残念ながらそれらは,派手なActiveXコントロールの作成などの影に隠れて,意外に知られていない.
そこで,ここではこういったトピックから,知っておくと便利なものや,プログラミングの幅を広げることができるものを紹介することにしよう.
今のあなたの活用状態を100%とすれば,これらの新しい機能を使いこなせれば,Visual
Basicを150%くらいは活用できるようになるかもしれない.しかしそれでは200%にはならない.200%にするためには,機能だけではなく,アイデアが重要なのだ.たとえば,あまり役に立たないプログラムでも,「えっ,これVisual
Basicで作ってあるの? 信じられない」というようにウケれば,作った人も満足できるし,これは200%活用できたと言ってもよいのではないだろうか.
というわけで,今回はあまり知られていないコトをいくつか紹介する.最終的にはこれらを駆使し,あまり役に立たないしょうもないプログラムながら,ウケだけは狙えるものを作ってみることにしよう.
| ●チェックボックス付きのリストボックス |
| コントロールの新機能 |
まずは,カンタンなところから始めよう.
意外に知られていない機能に,リストボックスのチェックボックス表示がある.これは,Styleプロパティを「1-チェックボックス」にするだけで実現できる(図1).この形式にしておけば,複数項目を選択が用意になる.たとえば,従来はチェックボックスをたくさん配置しなくてはならなかったケースでも,これならたったひとつのコントロールで済む点がよい.
プログラムからチェック状態を操作するには,Selectedプロパティを使う.たとえば,ListIndexが1番目のチェックボックスのチェック状態は,
List1.Selected(1)
がTrueかFalseかで知ることができる.また,コードからチェック状態を変更することも可能だ.
ここからも分かるように,MultiSelectプロパティを「1-標準」または「2-拡張」にしたときと操作は同じだ.いくつ選択されているかを示すSelCountプロパティも使える.
| ●デザイン時にリストボックスの初期値を設定する |
| コントロールの新機能の応用 |
Visual Basicで何が困るといえば,配列変数などの初期値を設定できないことだ.従来のBasicであれば,Data文に書いておき,Read文で配列に入れることができた.
Visual Basic 5.0からは,Enum文で新しいデータ型に属する定数を書くことができるようになったが,これはあくまでも複数の定数値であり,インデックスでアクセスできるようなものではない.
しかし,Visual Basic 5.0からは,リストボックスに初期値を入れておくことができるようになった.やり方はカンタンだ.プロパティウィンドウのListプロパティのドロップダウンリストボックスをクリックし,一行ずつ入力していく.ただし,改行は[Enter]ではなく,[Ctrl]+[Enter]キーを押すのがポイントだ.[Enter]だと,ドロップダウンリストが閉じてしまうのだ.カンタンにやるためには,メモ帳などにあらかじめ入力してあるデータをCopy&Pasteすればよいだろう(図2).
図1:リストボックスのチェックボックス表示![]() |
図2:リストボックスの初期値設定![]() |
| ● 配列の初期値をコーディングする |
| Variant型変数に配列を入れるツールを使ってお気楽コンバート |
リストボックスを使わないで,配列をうまく初期化する方法はないのだろうか.実は,とっておきのやり方がある.それは,Visual
Basic 4.0から可能になっていたのだが,Variant変数に配列として使うことだ.
使い方はこれもカンタンだ.ここでは,vArryというVariant型変数を宣言する.ここに,Array関数を使って,適当なデータの羅列を配列にしてしまい,代入するのである.データは文字列型でも数値型でもよい.
以後,vArryは配列として扱うことができるのである.
Dim vArry As Variant
Dim i As Integer
vArry = Array _
("睦月", "如月", "弥生", "卯月",
"皐月")
For i = 0 To UBound(vArry)
Debug.Print vArry(i)
Next i
この方法はちょっとしたことをするのにはとても便利である.リストボックスにデザイン時に入力するのと違って,コードして残るのもよい.ただし,Variant型である以上は,これも内部的には余分なメモリが使われることになる.あまり大規模なものには使わない方がよいだろう.
| ● 連想配列のように使えるCollection |
| オブジェクト指向言語仕様の活用 |
配列は便利なものである.何番目かが分かっていれば,それに対応するデータが得られる.しかし,逆はどうだろう?
つまり目的のデータが何番目にあるかを求めたいときだ.
このときには,必ずループを作ってひとつずつ中身を調べてゆかなくてはならない.大量のデータがあるときには,この処理は遅くなってしまうので,ソートやバイナリサーチなどを駆使しなくてはならない.
だが,本当に求めたいのは「何番目である」ということではなく,何番目かに付随する別のデータであることがほとんどだ.たとえば,名前をキーにして電話番号を知りたいというようなことである.
こんなときに便利なのが,連想配列である.残念ながらVisual Basicに連想配列はないが,似たような機能を提供してくれるのが,Collectionである.
Collectionは,オブジェクトの集合である.Visual BasicにもFontsやForms,ControlsなどがCollectionとして実装されているので,ご存じだろう.個々のオブジェクトの集合がCollectionである.実は,Visual
Basic 4.0からは,独自のCollectionを作ることができるようになっている.
次に例を示そう.ここでは,あるタイミングでWaveファイルを鳴らすか鳴らさないかを設定することを考えよう.要素としては,Waveファイルの名前と,鳴らすか鳴らさないかのフラグである.まずは,オブジェクトの元になるクラスを作る.次のように,clsWavesにはWaveNameとSpeakをメンバー変数,すなわちプロパティとして作成した.
【ClsWaves】
Option Explicit
Public WaveName As String
Public Speak As Boolean
Collectionオブジェクト自体は,次のように宣言する.
Private Waves As New Collection
Collectionに個々のオブジェクトを追加するには,Classをインスタンシングし,プロパティを設定した上で,Addメソッドを使う.このとき,第2パラメータは検索時のキーとなるプロパティである.ここでは,WaveNameで後から検索して,Speakするかどうかを調べるものとする.
Dim oWv As New clsWaves
oWv.WaveName = "へろへろ"
oWv.Speak = True
Waves.Add oWv, oWv.WaveName
たとえば,先ほどのチェックボックス付きリストボックスlstEventsで設定された内容をCollectionに追加するには,次のようにする.ループ中で使うためには,違うインスタンスを毎回作成しなくてはならない.このため,oWvの宣言にはNewはなく,ループ中でNewならびにNothingしていることに注目してほしい.
Dim oWv As clsWaves
Dim i As Integer
For i = 0 To lstEvents.ListCount - 1
Set oWv = New clsWaves
oWv.WaveName = lstEvents.List(i)
oWv.Speak = True
Waves.Add oWv, oWv.WaveName
Set oWv = Nothing
Next i
これで,リストボックスの内容はコレクションに入った.ここから,特定のWaveNameのもののSpeakプロパティがどうなっているかを知るには,次のように書けばよい.
Waves.Item("へろへろ").Speak
このように,ループを書かなくてもキーとなる内容を元にして一行で検索ができるようになる.
Collectionオブジェクトには,次のメソッドがある.
| Add | オブジェクトの追加 |
| Item | オブジェクトの取得 |
| Count | オブジェクトの数 |
| Remove | オブジェクトの削除 |
このように,Collectionは配列というよりは,データベース的な使い方ができるものだ.また,Collectionには,いろいろな型のオブジェクトを含めることができる.したがって,さまざまな構造のオブジェクトをひとつのコレクションにまとめることもできる.ただし,Itemで得た内容のプロパティ取得時などにエラーとならないような工夫が必要だ.
| ● ダイアログ表示と内部変数の受け渡しへの応用 |
| ちょっとしたテクニック |
オプションなどを設定するダイアログボックスでは,[OK]が押されればそこで設定された内容が反映され,[キャンセル]が押されれば以前の設定のままになる.ということは,ダイアログにあるコントロールの内容を設定情報そのものとしてはマズい.他にグローバル変数などが必要になるわけだ.
これは,フォームひとつだけのアプリケーションであっても,同じである.[キャンセル]時のために,別に変数を用意しておかなくてはならない.先ほどの例のように,チェックボックス付きリストボックスの内容を保存しようとするならば,配列変数が必要になる.これを,配列ではなくCollectionで実現してみよう.ダイアログでの受け渡し自体の手間は特に軽減されるわけではないが,実際に個々の設定を元に処理するかの判断をしてゆくには,配列よりCollectionが向いているからである.
ここでは,図3のようなデザインとした.全リストをリスト1に示す.
右側のリストボックスは,単にCollectionの内容を表示するためのものである.この表示をしているDispListでは,For
Each文を使ってCollectionの内容をひとつずつ取り出している.クリックされたときには,Waves
Collectionの内容を取得してメッセージボックスで表示する.[>>]ボタンが押されたときには,cmdOK_Clickイベントが発生し,リストボックスの内容をCollectionに持ってゆく.[<<]ボタンでは,cmdCancel_Clickイベントが発生し,逆の動作をする.
Option Explicit
Private Waves As New Collection
Private Sub cmdCancel_Click()
Dim i As Integer
' Resore from Collection to ListBox
For i = 0 To lstEvents.ListCount - 1
lstEvents.Selected(i) = Waves.Item(lstEvents.List(i)).Speak
Next i
DispList
End Sub
Private Sub cmdOK_Click()
Dim i As Integer
' Set from ListBox to Collection
For i = 0 To lstEvents.ListCount - 1
Waves.Item(lstEvents.List(i)).Speak = lstEvents.Selected(i)
Next i
DispList
End Sub
Private Sub Form_Load()
Dim oWv As clsWaves
Dim i As Integer
For i = 0 To lstEvents.ListCount - 1
Set oWv = New clsWaves
oWv.WaveName = lstEvents.List(i)
oWv.Speak = True
Waves.Add oWv, oWv.WaveName
lstEvents.Selected(i) = oWv.Speak
Set oWv = Nothing
Next i
DispList
End Sub
Private Sub List1_Click()
Dim sWaveName As String
sWaveName = Left(List1.Text, InStr(List1.Text, vbTab) - 1)
MsgBox Waves.Item(sWaveName).WaveName &_
":"& CStr(Waves.Item(sWaveName).Speak)
End Sub
Private Sub DispList()
Dim oWv As New clsWaves
List1.Clear
For Each oWv In Waves
List1.AddItem oWv.WaveName & vbTab & oWv.Speak
Next
End Sub
|
| 図3:変数の受け渡しダイアログボックス例 |
![]() |
| ●システム情報イベントを得るSysInfo.OCX |
| 知られていないActiveXコントロール |
Windowsではイベントドリブンのプログラミングをする.Visual Basicは,Windowsのメッセージをイベントという形で表現している.しかし,すべてのシステムからのメッセージに対応するイベントがあるわけではない.
そこで,Visual Basicでは知ることのできないメッセージを,AddressOfを使ったサブクラス化という手法を使って得ることになる.これについては,本誌1997年2月号に詳しく述べた.サブクラス化を使いこなすためには,それなりにWindowsネイティブな動作についての知識が必要であった.
しかし,実はこんな面倒なことをしなくても,Visual Basicには便利なActiveXコントロールが附属している.それが,SysInfo.OCXである.このコントロールは,実はVisual
Basic 4.0からUnSupportedとしてCD-ROMには含まれていたが,Visual Basic 5.0からは標準でインストールされる.
せっかくあるのだから,使ってみることにしよう.
[Ctrl]+[T]キーを押してコンポーネントダイアログを開いたら,「Microsoft
SysInfo Control 5.0」を選んで[OK]を押そう.これでSysInfoが使えるようになった.フォームにこのコントロールを配置し,ダブルクリックしてどんなイベントがあるのかを見てみよう.名前からだいたいどんなものなのかを予測することは容易だろう.実際には,イベントの他にもプロパティがいくつかある.次にこれらの一覧を示す(表1).
表1:SysInfo.OCXのイベントとプロパティ
■イベント
| 名称 | 解説 |
| ConfigChangeCancelled | ハードウェアプロファイルの変更が取り消されたというメッセージを,オペレーティングシステムがすべてのアプリケーションに送るときに発生 |
| ConfigChanged | システムのハードウェアプロファイルが変更されたときに発生 |
| DeviceArrival | 新しいデバイスがシステムに追加されたときに発生 |
| DeviceOtherEvent | 一般イベントに含まれない通知イベント |
| DeviceQueryRemove | デバイスをシステムから削除する前に発生 |
| DeviceQueryRemoveFailed | DeviceQueryRemove イベントのコードがデバイスの削除を取り消した場合に発生 |
| DeviceRemoveComplete | デバイスの削除後に発生 |
| DeviceRemovePending | すべてのアプリケーションがデバイスの削除を承認し,削除の準備ができたときに発生 |
| DevModeChanged | ユーザーがデバイスのモード設定を変更したときに発生 |
| DisplayChanged | システム画面の解像度が変更されたときに発生 |
| PowerQuerySuspend | システムの電源が中断される直前に発生 |
| PowerResume | システムが中断モードから解放されて,アプリケーションが正常な動作を再開したときに発生 |
| PowerStatusChanged | システムの電源ステータスに変化が生じたときに発生 |
| PowerSuspend | システムが中断モードに入る直前に発生 |
| QueryChangeConfig | オペレーティングシステムのユーザーインターフェイスを介して,あるいはシステムにドックまたはアンドックする前に中断モードを要求して,現在のハードウェアプロファイルの変更を要求したときに発生 |
| SettingChanged | アプリケーションがシステム全体のパラメータを変更したときに発生 |
| SysColorsChanged | アプリケーションから,またはコントロールパネルを介してシステムのカラー設定が変更されたときに発生 |
| TimeChanged | アプリケーションから,またはコントロールパネルを介してシステム時間が変更されたときに発生 |
■プロパティ
| 名称 | 解説 |
| ACStatus | システムがAC電源を使用しているかどうかを示す値を返す |
| BatteryFullTime | バッテリの完全充電寿命を示す値を返す |
| BatteryLifePercent | 全バッテリ電力の残存量をパーセントで返す |
| BatteryLifeTime | バッテリの残存寿命を示す値を返す |
| BatteryStatus | バッテリ充電量のステータスを示す値を返す |
| OSBuild | 現在アプリケーションを実行中のオペレーティングシステムについての情報を提供する値を返す |
| OSPlatform | 現在アプリケーションを実行中のオペレーティングシステムを識別する値を返す |
| OSVersion | 現在アプリケーションを実行中のオペレーティングシステムのバージョンを識別する値を返す |
| ScrollBarSize | スクロールバーの幅のシステムメトリックをtwipで返す |
| WorkAreaLeft, WorkAreaTop, WorkAreaWidth. WorkAreaHeight | タスクバー用に調整された表示デスクトップの属性を返す |
実は先ほどから例に出していたのが,このイベントなどなのであった.なお,詳しくは,ヘルプを参照していただきたい.一般的な使い方としては,次のパターンがある.
■いきなり使う
OSの情報などは,いつでも取得できるので,必要なときに使う.
■イベントに応じた処理をする
システムからの通知を元にして,イベントのパラメータや他のプロパティを参照してなんらかの処理をする.特に,Device系のイベントは引数が複雑である.
■イベントに応じてユーザーに確認する
Queryという文字がつくイベントは,ユーザーに確認を求めて,処理の継続をキャンセルさせることができる.
■タイマーで監視する
一定時間ごとに,プロパティを監視する.
さて,ではSysInfoを使ってプログラムを作ってみよう.といっても,このOCXは「システム情報」という点でまとめられているだけであって,何か特定の処理をするための一連の機能が入っているというものではない.アプリケーションによっては必要になるかもしれないといったものなので,一通りの機能を使うようなサンプルプログラムは作りにくいのである.
たとえば,DevModeChangedであれば,プリンタ設定が変更された可能性があるので,画面に表示していたページ幅を計算しなおすといった処理が必要になるかもしれない.時刻が変更されたなら,表示されているカレンダーや時計の表示をしなおさなくてはならないかもしれない.PowerSuspend時にはネットワークの先にあるどこかのシステムに自分がしばらく音信不通になることを通知するかもしれないし,PowerResume時やDeviceArrival時に処理を再開できることを通知するかもしれない.このように,千差万別なのだ.
しかたがないので,ここではごく単純にふつうなら分からないイベントが起きたことをユーザーに通知しておしまいにしよう.とはいえ,いちいちメッセージが出てもうっとうしいし,コンソールウィンドウを出しておくのもナニだ.だいたい,そんなことではまったく実用性がない.いや,実用性はなくてもいいが,面白くないではないか.
そこで,ここではあらかじめ用意しておいたWaveファイルが鳴って,何が起きたのかをユーザーに通知するようにしてみよう.プログラムの手間としては,メッセージボックスとたいして変わらないが,あらかじめデータを作っておくのが,結構面倒くさい.また,自分の声では気分が悪くなるのが普通だろう.
ここはひとつ,女性の声だ.そういえば,TalkingHubというモノがあり,声優が「ネットワークの負荷が上昇中です」などのようにしゃべるHubすら売っているという.役に立つのか不明だが,なんとなく欲しいではないか(編注:アクトンテクノロジィ社製TalkingHub“宮村優子スペシャルバージョン”通称ミヤムルータ.編集子もちょっと欲しいです…).というわけで,この企画を考えていた折りに,たまたまPCDN事務局にいらした知人の吉野さんにお願いして,吹き込んでいただいた.
このあたりのスケジュールの関係から,今回の付録CD-ROMには収まっていないが,来月号あるいはPCDNからダウンロードできるようにしておく予定だ.
もうお分かりのように,このプログラム自体はまったくたいしたことがない.Waveファイルを鳴らす部分については,本誌1997年2月号で紹介したWin32
APIのsndPlaySoundを使えば一発である.ただし,続けて複数のWaveファイルを鳴らすときには,ASYNCではいけないので注意が必要である.
ここでは,次のようなデザインのフォームにした(図4).
図4:デザイン時![]() |
sndPlaySoundを使うための宣言は,リスト2-aのように記述した.
Waveファイルは,イベントの名前と同じにした.したがって,一般的にはリスト2-bのようなコーディングとなる.ただし,Query系のキャンセル可能なものについては,リスト2-cのような書き方ができる.
また,バッテリ残量については,Timerを使って監視し,キャプションに表示している.さらに10パーセントごとに変化をチェックしてしゃべるCheckBatteryLifePercentを呼び出している(リスト2-d).
この関数では,以前の値から10%境界を過ぎるごとにWaveファイルを鳴らす.ただし,これは3つのWaveファイルを組み合わせてしゃべるようになっている.
WAV_BATRESTは「バッテリ残量」,次に「10」から「100」まで10おきの数字,最後に「パーセント」という言葉が入っている.
また,バッテリフル時のお言葉,バッテリ情報の詳細についてもしゃべるようになっている(リスト2-e).
このように,一通りいろんなことをしゃべるようになっただけでも,なかなか楽しいプログラムになった.ただし,しゃべってくれるためのトリガは,もっぱらシステム設定が変更されたときになるから,ノートパソコンなどのようにレジュームやPCMCIAカードの抜き差しをしないと,あまり楽しくないのも事実である.
このプログラムのよい点は,Waveファイルさえ変えれば,いろいろなおしゃべりをさせることができることだ.つまりサスペンドするときに「サスペンドします」のようなつまらない言葉ではなく,「See
you Later!」と言わせるなど,アイデア次第なのである.
リスト2-a:sndPlaySoundの宣言Option Explicit Public Declare Function sndPlaySound _ Lib "winmm.dll" Alias "sndPlaySoundA" _ (ByVal lpszSoundName As String, _ ByVal uFlags As Long) As Long Public Const SND_SYNC = &H0 Public Const SND_ASYNC = &H1 Public Const SND_NODEFAULT = &H2 Public Const SND_MEMORY = &H4 Public Const SND_LOOP = &H8 Public Const SND_NOSTOP = &H10 |
リスト2-d:バッテリー残量の監視
Private Sub timStat_Timer()
Me.Caption = sysiMessage.BatteryLifePercent & "%"
CheckBatteryLifePercent sysiMessage.BatteryLifePercent
End Sub
|
リスト2-b:wavファイルの指定
Private Sub sysiMessage_TimeChanged()
Call sndPlaySound("TimeChanged.wav", SND_ASYNC)
End Sub
|
リスト2-e:バッテリーフル/バッテリー情報の詳細報告
Private Sub CheckBatteryLifePercent(ByVal NewVal As Integer)
Dim iNewVal As Integer
iNewVal = CInt(NewVal / 10)
If iOldVal <> iNewVal Then
If sysiMessage.BatteryStatus < 128 Then
Call sndPlaySound(WAV_BATREST, SND_SYNC)
Call sndPlaySound(CStr(iNewVal * 10) & ".wav", SND_SYNC)
Call sndPlaySound(WAV_PERCENT, SND_SYNC)
If NewVal = 100 Then
Call sndPlaySound("BatteryFullTime.wav", SND_SYNC)
End If
End If
iOldVal = iNewVal
Call sndPlaySound("BS" & CStr(sysiMessage.BatteryStatus) & _
".wav", SND_ASYNC)
End If
End Sub
|
リスト2-c:キャンセル可能な場合
Private Sub sysiMessage_PowerQuerySuspend(Cancel As Boolean)
Call sndPlaySound("PowerQuerySuspend.wav", SND_ASYNC)
If MsgBox _
("Suspendしますか", vbOKCancel + vbExclamation, App.Title) _
= vbCancel Then
Cancel = True
End If
End Sub
|
| ●VBアプリをタスクトレイに入れる |
| Win32 APIの活用アイデア最後に |
システム起動時から常駐して監視をするようなタイプのアプリケーションは,通常サイズのウィンドウになっていてはうっとうしい.かといって,アイコン化されていてもうっとうしい.最近では,こういったものはタスクトレイに入れてしまうのがスマートとされている.
しかし,Visual Basicにはタスクトレイに入れる機能がない.そこで,Win32
APIの登場である.ここで使うのは,Shell_NotifyIcon関数である.この関数の引数はたった2つであり,細かい情報は第2引数となるPNOTIFYICONDATA型構造体に設定しておく.参考までに関数と構造体の概要を記事末に掲載しておく.
Visual Basicでの宣言ならびに定義は,リスト3のようになる.
リスト3:Shell_NotifyIcon関数の宣言と定義
Option Explicit
Type NOTIFYICONDATA
cbSize As Long
hwnd As Long
uID As Long
uFlags As Long
uCallbackMessage As Long
hIcon As Long
szTip As String * 64
End Type
Declare Function Shell_NotifyIcon _
Lib "shell32.dll" Alias "Shell_NotifyIconA" _
(ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long
Public Const NIM_ADD = &H0
Public Const NIM_MODIFY = &H1
Public Const NIM_DELETE = &H2
Public Const NIF_ICON = &H2
Public Const NIF_MESSAGE = &H1
Public Const NIF_TIP = &H4
Public Const WM_MOUSEMOVE = &H200
Public Const WM_LBUTTONDOWN = &H201
Public Const WM_LBUTTONUP = &H202
Public Const WM_LBUTTONDBLCLK = &H203
Public Const WM_RBUTTONDOWN = &H204
Public Const WM_RBUTTONUP = &H205
Public Const WM_RBUTTONDBLCLK = &H206
Public Const WM_MBUTTONDOWN = &H207
Public Const WM_MBUTTONUP = &H208
Public Const WM_MBUTTONDBLCLK = &H209
|
■本当はタスクトレイへは常駐しない
NOTIFYICONという単語からも分かるように,この関数はタスクトレイにアイコンとして登録されたアプリケーションに,タスクトレイから通知するためのものである.つまり,アプリケーション自体がタスクトレイに入るわけではないのだ.したがって,タスクトレイに常駐しているように見せるためには,ウィンドウ本体は通常はInVisible状態にしておき,タスクトレイのアイコンがダブルクリックされたり,右クリック時のメニューから呼び出されたりしたときに,必要なウィンドウを表示させてやればよい.
■常駐の開始
では,実際にこの機能を使ってみることにしよう.モジュールレベルには,次のようにNOTIFYICONDATA型変数を宣言する.
Private nidSysInfo As NOTIFYICONDATA
Form_Load時に,この構造体の初期値を設定する(リスト4).
リスト4:初期値の設定内容nidSysInfo.cbSize = Len(nidSysInfo) '構造体のサイズ nidSysInfo.hwnd = Me.hwnd '通知されるウィンドウハンドル nidSysInfo.uID = 1 'ID nidSysInfo.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE 'フラグ nidSysInfo.uCallbackMessage = WM_MBUTTONDOWN 'Calllback Message nidSysInfo.hIcon = Me.Icon 'タスクトレイに表示されるアイコン nidSysInfo.szTip = "SysInfo" & vbNullChar 'ToolTipに表示される文字列 |
ここで,ちょっと面白いのが,メッセージの受け渡しである.タスクトレイ上のアイコンになんらかのアクションがあったとき,hwndに指定されたウィンドウに対してuCallbackMessageで指定されたメッセージが送信される.通常は,WM_USERなどを使うことを考えてしまうところだ.その場合には当然ながら,Visual
Basic標準のイベントでこれを取得することはできないので,AddressOfを使ったサブクラス化が必要になる.
しかし,ここでは通常の2ボタンマウスではまず使うことのない真ん中のボタンが押されたときのメッセージWM_MBUTTONDOWNを指定している.つまり,Form_MouseDownイベントが発生するのである.つまり,サブクラス化の必要がなくなるのである.
これらの設定ができたら,NIM_ADDでタスクトレイにアイコンを常駐させることができる.
lRetVal = Shell_NotifyIcon _
(NIM_ADD, nidSysInfo)
これで,タスクトレイに常習した.なお,このときのFormのVisibleプロパティはあらかじめFalseにしておく.
■タスクトレイからの通知
タスクトレイのアイコンになんらかのアクションがあったときには,Form_MouseDownイベントが発生する.このとき,真ん中のボタンであることを判断し,本当のメッセージはなんであったかをX
\ Screen.TwipsPerPixelXで得ることができる.後は,適切に対応した処理を書けばよい.たとえばポップアップウィンドウを開きたいならば,あらかじめ作成したメニューをPopupMenuメソッドで呼び出せばよい(リスト5).
リスト5:Form_MouseDownイベント
Private Sub Form_MouseDown _
(Button As Integer, Shift As Integer, _
X As Single, Y As Single)
If (Button And vbMiddleButton) = vbMiddleButton Then
Select Case X \ Screen.TwipsPerPixelX
Case WM_LBUTTONDOWN
Beep
'mnuStatus_Click
Case WM_RBUTTONDOWN
Me.PopupMenu mnuPopUp, vbPopupMenuRightButton
Case WM_LBUTTONDBLCLK
Me.Visible = True
End Select
End If
End Sub
|
■ツールチップの更新
最初に構造体の初期値を設定したとき,ToolTipへの文字列も設定した.しかし,時系列によって変化するような内容にしたいときには,この文字列を変更する必要がある. このときには,szTipを再設定し,NIM_MODIFYを指定してShell_NotifyIcon関数を呼び出してやればよい.
Private Sub Timer1_Timer() nidSysInfo.szTip = _ Time$ & vbNullChar
lRetVal = Shell_NotifyIcon _ (NIM_MODIFY, nidSysInfo) End Sub
■タスクトレイからの削除
終了時には,タスクトレイからアイコンを削除する必要がある.このときには,NIM_DELETEを指定してShell_NotifyIcon関数を呼び出してやればよい.
Private Sub Form_Unload _ (Cancel As Integer) lRetVal = Shell_NotifyIcon _ (NIM_DELETE, nidSysInfo) End Sub
このように,タスクトレイの扱いは意外にもカンタンなのである(リスト6,図5〜7参照).
リスト6:タスクトレイソースリスト
Private nidSysInfo As NOTIFYICONDATA
Private lRetVal As Long
Private Sub cmdHide_Click()
Me.Visible = False
End Sub
Private Sub Form_Load()
nidSysInfo.cbSize = Len(nidSysInfo)
nidSysInfo.hwnd = Me.hwnd
nidSysInfo.uID = 1
nidSysInfo.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
nidSysInfo.uCallbackMessage = WM_MBUTTONDOWN 'Calllback Message
nidSysInfo.hIcon = Me.Icon
nidSysInfo.szTip = "SysInfo" & vbNullChar
lRetVal = Shell_NotifyIcon(NIM_ADD, nidSysInfo)
End Sub
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
If (Button And vbMiddleButton) = vbMiddleButton Then
Select Case X \ Screen.TwipsPerPixelX
Case WM_LBUTTONDOWN
Beep
'mnuStatus_Click
Case WM_RBUTTONDOWN
Me.PopupMenu mnuPopUp, vbPopupMenuRightButton
Case WM_LBUTTONDBLCLK
Me.Visible = True
End Select
End If
End Sub
Private Sub Form_Unload(Cancel As Integer)
lRetVal = Shell_NotifyIcon(NIM_DELETE, nidSysInfo)
End Sub
Private Sub mnuExit_Click()
Unload Me
End Sub
Private Sub mnuStatus_Click()
MsgBox Time
End Sub
Private Sub Timer1_Timer()
nidSysInfo.szTip = Time$ & vbNullChar
lRetVal = Shell_NotifyIcon(NIM_MODIFY, nidSysInfo)
End Sub
|
図5:タスクトレイへの常駐.ToolTipが出る![]() |
図6:右クリックすれば,ポップアップメニューが表示される![]() |
図7:ダブルクリックすれば,ウィンドウが表示される![]() | |
| ●おしゃべりシステムインフォメーション |
| それらしいプログラム |
Visual Basic 5.0を活用するための隠れたテクニックを紹介してきたが,ここまで使ってきた機能を一通り使ったプログラムを作ってみた.
もう,すでにお分かりであろう.SysInfoで得た情報のうち,チェックされたものだけについてしゃべるというものだ.そして,このアプリケーションはタスクトレイに常駐するのである.
大きな改造はない.今まで作ってきたものをかき集めてひとつにすればよいのである.ただし,次のようにSysInfoの各イベントごとに,しゃべってよいかのチェックをしておく必要がある.
If Waves.Item("DeviceArrival").Speak _
Then
Call sndPlaySound _
("DeviceArrival.wav", SND_ASYNC)
End If
フォームのデザイン,実行時のポップアップメニューとToolTip,設定ダイアログの画面(図8・9),全リストをそれぞれ示す(clsWaves,
modSysSpeak, frmSysSpeak.リスト7参照).
実際にうごかしてみると,レジューム時にはDeviceArrivalやDeviceRemoveCompleteなどは何度も発生したり,機種によって電池残量のステップが違ったり,不思議なタイミングで不思議なイベントが起きたり,起こらなかったり…
といまいち動作がおかしいような気もする.おそらく,APMまわりのBIOSの問題なのだろう.実際にもっと役立つようなプログラムを作るときには,さらにイベントの引数について詳細にチェックしたり,例外処理を書いていかないといけないかもしれない.細かい詰めはもう少し必要そうだ.
ともあれ,ActiveXコントロールでの拡張,Win32 APIでの拡張,言語仕様の有効利用といった3点を満足したプログラムができた.そして,何よりもしゃべるというアイデアもなかなか面白い.はた目で見ている人は必ず興味を持って寄ってくる.これぞ,まさに200%活用と言えるだろう.
| リスト8:sysSpeak完成品全リスト ■clsWave
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "clsWaves"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Public WaveName As String
Public Speak As Boolean
■frmSpeak
VERSION 5.00
Object = _
"{6FBA474E-43AC-11CE-9A0E-00AA0062BB4C}#1.0#0" _
; "sysinfo.ocx"
Begin VB.Form frmSysSpeak
'(略)
End
Attribute VB_Name = "frmSysSpeak"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Waves As New Collection
Private Const WAV_BATREST = "BATRest.wav"
Private Const WAV_PERCENT = "Percent.wav"
Private iOldVal As Integer
Private nidSysInfo As NOTIFYICONDATA
Private lRetVal As Long
Private Sub CheckBatteryLifePercent(ByVal NewVal As Integer)
Dim iNewVal As Integer
Dim i As Integer
Dim vArry As Variant
iNewVal = CInt(NewVal / 10)
If iOldVal <>iNewVal Then
If sysiMessage.BatteryStatus < 128 Then
If Waves.Item("BatteryRestPercent").Speak Then
Call sndPlaySound(WAV_BATREST, SND_SYNC)
Call sndPlaySound(CStr(iNewVal * 10) & ".wav", SND_SYNC)
Call sndPlaySound(WAV_PERCENT, SND_SYNC)
End If
If NewVal = 100 Then
If Waves.Item("BatteryFullTime").Speak Then
Call sndPlaySound("BatteryFullTime.wav", SND_SYNC)
End If
End If
End If
iOldVal = iNewVal
' Battery Status
If Waves.Item("BatteryStatus").Speak Then
If sysiMessage.BatteryStatus = 255 Then
Call sndPlaySound("BS" & CStr(255) & ".wav", SND_ASYNC)
Else
vArry = Array(1, 2, 4, 8, 128)
For i = 0 To UBound(vArry)
If (sysiMessage.BatteryStatus And vArry(i)) = _
vArry(i) Then
Call sndPlaySound("BS" & CStr(vArry(i)) & _
".wav", SND_ASYNC)
End If
Next i
End If
End If
End If
End Sub
Private Sub mnuBattStat_Click()
iOldVal = -1
CheckBatteryLifePercent Val(Me.Caption)
End Sub
Private Sub mnuExit_Click()
Unload Me
End Sub
Private Sub mnuRestore_Click()
Me.Visible = True
End Sub
Private Sub sysiMessage_ConfigChangeCancelled()
If Waves.Item("ConfigChangeCancelled").Speak Then
Call sndPlaySound("ConfigChangeCancelled.wav", SND_ASYNC)
End If
End Sub
Private Sub sysiMessage_ConfigChanged _
(ByVal OldConfigNum As Long, ByVal NewConfigNum As Long)
If Waves.Item("ConfigChanged").Speak Then
Call sndPlaySound("ConfigChanged.wav", SND_SYNC)
End If
End Sub
Private Sub sysiMessage_DeviceArrival _
(ByVal DeviceType As Long, ByVal DeviceID As Long, _
ByVal DeviceName As String, ByVal DeviceData As Long)
If Waves.Item("DeviceArrival").Speak Then
Call sndPlaySound("DeviceArrival.wav", SND_ASYNC)
End If
End Sub
Private Sub sysiMessage_DeviceRemoveComplete _
(ByVal DeviceType As Long, ByVal DeviceID As Long, _
ByVal DeviceName As String, ByVal DeviceData As Long)
If Waves.Item("DeviceRemoveComplete").Speak Then
Call sndPlaySound("DeviceRemoveComplete.wav", _
SND_ASYNC + SND_NOSTOP)
End If
End Sub
Private Sub sysiMessage_DevModeChanged()
If Waves.Item("DevModeChanged").Speak Then
Call sndPlaySound("DevModeChanged.wav", SND_SYNC)
End If
End Sub
Private Sub sysiMessage_DisplayChanged()
If Waves.Item("DisplayChanged").Speak Then
Call sndPlaySound("DisplayChanged.wav", SND_ASYNC)
End If
End Sub
Private Sub sysiMessage_PowerQuerySuspend(Cancel As Boolean)
If Waves.Item("PowerQuerySuspend").Speak Then
Call sndPlaySound("PowerQuerySuspend.wav", SND_ASYNC)
If MsgBox("Suspend", vbOKCancel + vbExclamation, App.Title) = _
vbCancel Then
Cancel = True
End If
End If
End Sub
Private Sub sysiMessage_PowerResume()
If Waves.Item("PowerResume").Speak Then
Call sndPlaySound("PowerResume.wav", SND_SYNC)
End If
End Sub
Private Sub sysiMessage_PowerStatusChanged()
Static iAcStatus As Integer
If Waves.Item("PowerStatusChanged").Speak Then
If iAcStatus <> sysiMessage.ACStatus Then
Select Case sysiMessage.ACStatus
Case 0
Call sndPlaySound("PowerStatusChanged2.wav", SND_SYNC)
Call sndPlaySound("PowerStatusChanged1.wav", SND_ASYNC)
Case 1
Call sndPlaySound("PowerStatusChanged3.wav", SND_ASYNC)
Case Else
End Select
iAcStatus = sysiMessage.ACStatus
End If
End If
End Sub
Private Sub sysiMessage_PowerSuspend()
If Waves.Item("PowerSuspend").Speak Then
Call sndPlaySound("PowerSuspend.wav", SND_SYNC)
End If
End Sub
|
Private Sub sysiMessage_QueryChangeConfig(Cancel As Boolean)
If Waves.Item("QueryChangeConfig").Speak Then
Call sndPlaySound("QueryChangeConfig.wav", SND_SYNC)
If MsgBox("Change Config", vbExclamation + vbOKCancel, App.Title) _
= vbCancel Then
Cancel = True
End If
End If
End Sub
Private Sub sysiMessage_SettingChanged(ByVal Item As Integer)
If Waves.Item("SettingChanged").Speak Then
Call sndPlaySound("SettingChanged.wav", SND_ASYNC)
End If
End Sub
Private Sub sysiMessage_SysColorsChanged()
If Waves.Item("SysColorsChanged").Speak Then
Call sndPlaySound("SysColorsChanged.wav", SND_ASYNC)
End If
End Sub
Private Sub sysiMessage_TimeChanged()
If Waves.Item("TimeChanged").Speak Then
Call sndPlaySound("TimeChanged.wav", SND_ASYNC)
End If
End Sub
Private Sub timStat_Timer()
Me.Caption = sysiMessage.BatteryLifePercent & "%"
nidSysInfo.szTip = Me.Caption & vbNullChar
lRetVal = Shell_NotifyIcon(NIM_MODIFY, nidSysInfo)
CheckBatteryLifePercent sysiMessage.BatteryLifePercent
End Sub
Private Sub cmdCancel_Click()
Dim i As Integer
' Resore from Collection to ListBox
For i = 0 To lstEvents.ListCount - 1
lstEvents.Selected(i) = Waves.Item(lstEvents.List(i)).Speak
Next i
' DispList
Me.Visible = False
End Sub
Private Sub cmdOK_Click()
Dim i As Integer
' Set from ListBox to Collection
For i = 0 To lstEvents.ListCount - 1
Waves.Item(lstEvents.List(i)).Speak = lstEvents.Selected(i)
Next i
' DispList
Me.Visible = False
End Sub
Private Sub Form_Load()
Dim oWv As clsWaves
Dim i As Integer
mnuPopUp.Visible = False
nidSysInfo.cbSize = Len(nidSysInfo)
nidSysInfo.hwnd = Me.hwnd
nidSysInfo.uID = 1
nidSysInfo.uFlags = NIF_ICON Or NIF_TIP Or NIF_MESSAGE
nidSysInfo.uCallbackMessage = WM_MBUTTONDOWN 'Calllback Message
nidSysInfo.hIcon = Me.Icon
nidSysInfo.szTip = "SysInfo" & vbNullChar
lRetVal = Shell_NotifyIcon(NIM_ADD, nidSysInfo)
Me.Hide
For i = 0 To lstEvents.ListCount - 1
Set oWv = New clsWaves
oWv.WaveName = lstEvents.List(i)
oWv.Speak = True
Waves.Add oWv, oWv.WaveName
lstEvents.Selected(i) = oWv.Speak
Set oWv = Nothing
Next i
timStat_Timer
' DispList
End Sub
Private Sub Form_MouseDown _
(Button As Integer, Shift As Integer, X As Single, Y As Single)
If (Button And vbMiddleButton) = vbMiddleButton Then
Select Case X \ Screen.TwipsPerPixelX
Case WM_LBUTTONDOWN
Me.Visible = True
Case WM_RBUTTONDOWN
Me.PopupMenu mnuPopUp, vbPopupMenuRightButton
End Select
End If
End Sub
Private Sub Form_Unload(Cancel As Integer)
lRetVal = Shell_NotifyIcon(NIM_DELETE, nidSysInfo)
End Sub
Private Sub List1_Click()
Dim sWaveName As String
sWaveName = Left(List1.Text, InStr(List1.Text, vbTab) - 1)
MsgBox Waves.Item(sWaveName).WaveName & ": " & _
CStr(Waves.Item(sWaveName).Speak)
End Sub
Private Sub DispList()
Dim oWv As New clsWaves
List1.Clear
For Each oWv In Waves
List1.AddItem oWv.WaveName & vbTab & oWv.Speak
Next
End Sub
■modSysSpeak
Attribute VB_Name = "modSysSpeak"
Option Explicit
Public Declare Function sndPlaySound _
Lib "winmm.dll" Alias "sndPlaySoundA" _
(ByVal lpszSoundName As String, ByVal uFlags As Long) As Long
Public Const SND_SYNC = &H0
Public Const SND_ASYNC = &H1
Public Const SND_NODEFAULT = &H2
Public Const SND_MEMORY = &H4
Public Const SND_LOOP = &H8
Public Const SND_NOSTOP = &H10
Type NOTIFYICONDATA
cbSize As Long
hwnd As Long
uID As Long
uFlags As Long
uCallbackMessage As Long
hIcon As Long
szTip As String * 64
End Type
Declare Function Shell_NotifyIcon _
Lib "shell32.dll" Alias "Shell_NotifyIconA" _
(ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long
Public Const NIM_ADD = &H0
Public Const NIM_MODIFY = &H1
Public Const NIM_DELETE = &H2
Public Const NIF_ICON = &H2
Public Const NIF_MESSAGE = &H1
Public Const NIF_TIP = &H4
Public Const WM_MOUSEMOVE = &H200
Public Const WM_LBUTTONDOWN = &H201
Public Const WM_LBUTTONUP = &H202
Public Const WM_LBUTTONDBLCLK = &H203
Public Const WM_RBUTTONDOWN = &H204
Public Const WM_RBUTTONUP = &H205
Public Const WM_RBUTTONDBLCLK = &H206
Public Const WM_MBUTTONDOWN = &H207
Public Const WM_MBUTTONUP = &H208
Public Const WM_MBUTTONDBLCLK = &H209
|
参考:Shell_NotifyIcon関数と構造体の概要
Shell_NotifyIcon
タスクバーステータスエリアにアイコンを追加修正、削除されるようにシステムにメッセージを送る
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
DWORD dwMessage, // message identifier
PNOTIFYICONDATA pnid // pointer to structure
);
●Parameters(パラメータ)
どのようなメッセージを送るかを示す.次のパラメータのいずれか
NIM_ADD アイコンを追加する
NIM_DELETE アイコンを削除する
NIM_MODIFY アイコンを修正する
●pnid
NOTIFYICONDATA構造体へのポインタ.dwMessageの値による.)
●Return Values(戻り値)
成功時は0以外、失敗時には0を返す.
●NOTIFYICONDATA
システムがタスクバーステータスエリアメッセージを処理するために必要な情報
typedef struct _NOTIFYICONDATA { // nid
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;
●Members(メンバ)
cbSize NOTIFYICONDATA型データのサイズ
hWnd タスクバーステータスエリアのアイコンに関連付けられたメッセージが送られるウィンドウのハンドル
uID タスクバーアイコンのアプリケーションが定義した番号
uFlags 他のメンバに関連したフラグ.以下の組合わせになる
NIF_ICON hIconメンバが有効
NIF_MESSAGE uCallbackMessageメンバが有効
NIF_TIP szTipメンバが有効
●uCallbackMessage
アプリケーション定義.アイコンを含む矩形境界内でのマウスイベントを、hWndで指定されたウインドウに通知するためのメッセージ
hIcon 追加,修正,削除されるアイコンのハンドル
szTip アイコンのツールチップテキスト