counter

Visual Basicに関するメモ

このページは、不定期に更新されています。更新箇所の明示は行われませんのでご注意ください。また、内容はごく一時的なメモに類するものです。一貫性や真偽について、保証できませんので、ご了承ください。


また、こちらにいくつかのサンプルコードを掲載していますので、あわせてご利用ください。


TimerコントロールのTimerイベント発生のタイミング

Timerイベントの発生の間隔は、Intervalプロパティで調整することができると説明されているが、正確には、Timerイベントのプロシージャーから抜けたあと、次のTimerイベントが発生するまでの間隔である。


したがって、Intervalプロパティーを2000(2秒相当)に設定し、Timerプロシージャーの中に1秒を要する処理を記述した場合、(1)Timerイベント発生、(2)1秒間の処理、(3)Timerイベントを抜けて2秒待機、(4)次のTimerイベント発生、というように(1)と(4)のTimerイベント発生の間隔は3秒となる。


Formのアイコンの取得にLoadResPictureを使うと汚くなる(exeで実行時)

Formのアイコンを実行中に変更したい場合、LoadPictureやLoadResPictureなどを使うことができるが、リソースファイルからLoadResPictureで16×16のサイズのアイコンを取得すると表示が汚くなる。一度32×32に引き伸ばしたあとで再度16×16に戻しているように思われる。


きれいに表示させるためには、LoadPictureでファイルから直接取得するか、あるいは、ImageListコントロールから取得する方法がよい。


リッチテキストコントロールの右端折り返しを抑制する

リッチテキストコントロールは、デフォルトの状態では、長い行がコントロールの右端で折り返されて表示される。この折り返しを抑制し、長い行であっても一行で表示する方法については、ヘルプでも、Microsoft社のHPでも適切な説明がなされていない。一応、以下のプロパティーの設定で、この動作が実現できるようである。

MultiLine: True ScrollBars: 1 - rtfHorizontal 又は 3 - rtfBoth RightMargin: 1E+09


要は、RightMarginを十分に大きな数値にすることである。なお、RightMarginは、オブジェクト・ブラウザによるとSingle型の値を受け付けるので、E+38のオーダーまでエラーにはならないが、大きすぎると、値が0の場合と同じように折り返し動作となってしまう。


コレクションのスキャン

コレクション内のアイテムを総当たりするスキャンは、

Dim I as Long Dim colCollection as Collection For I = 1 to colCollection.Count '(colCollection(I)を使って処理) Next I


とするのが安全と思われる。よく、サンプルとして、次のような記述が見られるが、特に、ActiveX DLLのプロジェクト内でこの例を用いると、DLLの読み込みに失敗することがある。

Dim vntItem as Variant Dim colCollection as Collection For Each vntItem in colCollection '(vntItemを使って処理) Next vntItem


日付の「定数値」(日付/時刻リテラル)

Visual Basicでは、日付のリテラル値は、#m/d/yyyy#のフォーマットが標準である。このフォーマットは、「アメリカ」式であるので、また、Visual Basicは、もともとアメリカの製品であるので、おそらく、どの国のバージョンであっても通用するものと思われる(未確認)。日本語バージョンの場合、様々なフォーマットでリテラル値を入力することはできるが、エディタにより、すべてこのフォーマットに自動変換される。例えば、

#97/06/07# #97-June-07# #07/June/1997# #June/07/1997#


は、すべて、

#6/7/1997#


に変換される。なお、Visual Basicのスクリプト版(VB Script)の場合、この自動変換をするエディタはないので、必ず、#m/d/yyyy#のフォーマットで入力しなければならない。


ファイルとディレクトリの属性

属性の取得はGetAttr関数。属性の設定はSetAttrステートメント。SetAttrステートメントに関して、ヘルプにはファイルの属性設定のことしか書いていないが、ディレクトリの属性設定も可能である。ただし、ディレクトリに対してSetAttrで、vbDirectoryの属性を指定しようとするとエラーとなる。あるディレクトリの属性をGetAttrで取得して、そのままSetAttrで他のディレクトリにコピーする場合、要注意。したがって以下のような記述が必要となる。

lngAttribute = GetAttr(strOriginalDirectoryName) SetAttr strNewDirectoryName, lngAttribute and (Not vbDirectory)


また、Windows NTにおいて、圧縮されたディレクトリ又はファイルに対してGetAttrを行うと、圧縮を示すフラッグ(2048)が立った結果が戻る。これに適当に演算を加えてSetAttrを行うと、エラーとなり正常に属性を設定することができない。以下のような措置が必要である。なお、この操作によって圧縮属性が変化することはない。

lngAttribute = GetAttr(strOriginalDirectoryName) If lngAttribute >= 2048 Then lngAttribute = lngAttribute - 2048 .... SetAttr strNewDirectoryName, lngAttribute


インターネット・トランスファ・コントロールのOpenURLメソッド (VB5)

ヘルプ(オブジェクト・ブラウザからアクセス)を読むと、OpenURLメソッドにより、指定したURLのコンテンツを一括取得できそうに思われるが、そうならない場合もある。試行錯誤の結果、どうもTransfer-Encoding: chunkedである場合にうまくいかないようである。このときには、OpenURLメソッドに続けてGetChunkメソッドも実行する必要がある。


下記のコードでは、strURLで指定した場所のコンテンツがbytReceivedData()に格納される。コンテンツはhtml/textであっても、その他のバイナリ形式であってもよい。strReceivedDataとstrBufferというString型の変数を用いているが、バイト列とバイト列を連結するためのものである。

Dim strURL as string Dim bytReceivedData() As Byte Dim strReceivedData As String Dim strBuffer As String '**** First Call **** strReceivedData = Inet1.OpenURL(strURL, icByteArray) '**** Purge Chunk **** Do strBuffer = Inet1.GetChunk(8192, icByteArray) If LenB(strBuffer) = 0 Then Exit Do strReceivedData = strReceivedData & strBuffer Loop bytReceivedData = strReceivedData


しかしながら、本当によい方法は、OpenURLメソッドを用いずに、Executeメソッドを用いることと思われる。その後、データの引き出しは、StateChangedイベントにおいて、State=icResponseCompletedとなったところで、GetChunkメソッドを用いて行う。


APIとの文字列のやりとり(ANSI vs UNICODE変換)

ByValにせよ、ByRefにせよ、文字列をString型で渡す場合には、Visual BasicによりUNICODEからANSIへの変換が自動的に行われる。おそらく、変換した結果を元の同じ場所に格納した上で(UNICODEからANSIへの変換では、バイト数は必ず等しいか小さくなる。)、APIに制御が移るようである。したがって、UNICODEのままで渡したい場合には、別途工夫が必要。


APIから文字列を受け取る方法は、

の4種類が考えられるが、いずれの場合にも、Nullで終了する(Null Terminated)ANSI文字列しか正常に受け取ることができず、しかも、UNICODEへの変換が行われる。したがって、API自身がUNICODEを返す場合には、別途工夫が必要。


特に、最後のOLE Automationによる書き換えについては、32bitアプリケーションのOLE Automationの場合、BSTRはUNICODE文字列が保持されていることが大前提(Visual BasicのString型と同じ)なので、取り扱いに注意を要する。


APIへの文字列の引き渡し

LPSTR、LPCSTR、LPTSTR、LPCTSTR、BSTR(いずれも、文字列先頭へのポインタ)で、ANSI文字列を引き渡す

Declare内の仮引数記述ByVal LP As String
実引数記述strVar

Visual Basicでは、DLL呼び出しで文字列を渡す場合、UNICODEをANSIに自動的に変換してくれるので、この方法を用いることができる。


LPTSTR、LPCTSTR、BSTRで、UNIOCDE文字列を引き渡す

Declare内の仮引数記述ByVal LP As Long
実引数記述StrPtr(strVar)

UNICODEからANSIへの自動変換を回避するために、前もってアドレスを算出しておき、アドレスを直接渡す。BSTR自身は、文字列先頭へのポインターであるため、この方法を使うことができる。


文字列先頭へのアドレスから、文字列を引き出す

APIから文字列を得たい場合で、文字列先頭へのポインタしか受け取れないことがある。とくに、構造体でAPIとやり取りする場合、構造体が文字列先頭へのポインタ保持していると、文字列そのものを取り出すためには、工夫が必要となる。以下に、文字列先頭のアドレスが判明している場合に、文字列を取り出すコードを示す。


Null文字で終了しているANSI文字列の場合

Private Declare Function SysAllocStringByteLen _ Lib "oleaut32" _ (ByVal psz As Long, _ ByVal length As Long) _ As String Private Declare Function lstrlenA _ Lib "kernel32" _ (ByVal lpString As Long) _ As Long Public Function GetANSIStringAt(ByVal sz As Long) As String Dim lngLengthByte As Long If sz = 0 Then GetANSIStringAt = vbNullString Else lngLengthByte = lstrlenA(sz) If lngLengthByte = 0 Then GetANSIStringAt = "" Else GetANSIStringAt = SysAllocStringByteLen(sz, lngLengthByte) End If End If End Function


SysAllocStringByteLenは、OLE AutomationのAPIであり、pszでポイントされている位置に存在するNull文字で終了するANSI文字列を、BSTRに、lengthバイトだけANSI文字列のままコピーし、さらに最後にNull文字を追加し、戻す。Visual Basicでは、APIからString型が戻ると自動的にANSIからUNICODEに変換してくれるので、Visual Basic側でANSIからUNICODEに変換する必要はない。


ただし、SysAllocStringByteLenでは、ANSI文字列のバイト数が分かっていなければならないので、lstrlenAを用いる。lstrlenAは、lpStringでポイントされている位置に存在するNull文字で終了するANSI文字列のバイト数を返す。このバイト数には、最後のNull文字は含まれない。


SDKによると、lstrlenの関数個別の説明では、ANSI文字列の場合バイト数を返すと説明されており、一方、文字列取扱関数の概説(String Manipulation in Windows)では、lstrlenは、ANSIでもUNICODEでも文字数を返すとなっており、矛盾が見られるが、動作を確認してみると、前者のlstrlenの関数個別の説明が正しい。


GetANSIStringAtは、szでポイントされている位置に存在するNull文字で終了するANSI文字列をString型(UNICODE)として取り出すVisual Basicの関数である。最初に、lstrlenAを呼び出しANSI文字列のバイト数を調べ、文字列の長さがゼロでなければ、SysAllocStringByteLenを呼び出す。SysAllocStringByteLenのAPI自身は、ANSIからUNICODEの変換を行わないが、結果がVisual Basicの渡される際に変換が行われる。


Null文字で終了しているUNICODE文字列の場合

Private Declare Function lstrlenW _ Lib "kernel32" _ (ByVal lpString As Long) _ As Long Private Declare Sub RtlMoveMemory _ Lib "kernel32" _ (ByVal dest As Long, _ ByVal src As Long, _ ByVal count As Long) Public Function GetUNICODEStringAt(ByVal sz As Long) As String Dim lngLengthChar As Long If sz = 0 Then GetUNICODEStringAt = vbNullString Else lngLengthChar = lstrlenW(sz) If lngLengthChar = 0 Then GetUNICODEStringAt = "" Else GetUNICODEStringAt = String$(lngLengthChar, vbNullChar) RtlMoveMemory StrPtr(GetUNICODEStringAt), sz, lngLengthChar * 2 End If End If End Function


lstrlenWは、lpStringでポイントされている位置に存在するNull文字で終了するUNICODE文字列の文字数を返す。この文字数には、最後のNull文字は含まれない。UNICODEの場合、1文字が必ず2バイトである。


RtlMoveMemoryは、srcでポイントされるアドレスから、destでポイントされるアドレスへ、メモリの内容をコピーする。コピーするデータの長さがcountバイトである。コピー元の領域(srcからsrc+count-1)とコピー先の領域(destからdest+count-1)に重複があっても、正しくコピー元の内容がコピー先に移る。


GetUNICODEStringAtは、szでポイントされている位置に存在するNull文字で終了するUNICODE文字列をString型(UNICODE)として取り出すVisual Basicの関数である。最初に、lstrlenWを呼び出しUNICODE文字列のバイト数を調る。文字列の長さがゼロでなければ、GetUNICODEStringAtの戻り値として文字数分の領域を確保した上で、RtlMoveMemoryにより、szから確保した領域へ内容のコピーを行う。


Null文字で終了するUNICODE文字列をBSTRに格納するだけであれば、OLE AutomationのSysAllocString系のAPIが使えるが、このBSTRがVisual BasicにString型として戻る際に、余計なANSI-UNICODEの変換が行われるので、内容が破壊されてしまう。このため、SysAllocString系のAPIが使えないので、ここで紹介したような方法を採用せざるを得ない。


ここでは、StrPtrという、Visual Basicの隠し関数を使用しているが、これを嫌う場合は、次のようなコードとなる。

Private Declare Function lstrlenW Lib "kernel32" _ (ByVal lpString As Long) _ As Long Private Declare Sub RtlMoveMemory _ Lib "kernel32" _ (dest As Any, _ ByVal src As Long, _ ByVal count As Long) Public Function GetUNICODEStringAt(ByVal sz As Long) As String Dim lngLengthChar As Long Dim bytBuffer() As Byte If sz = 0 Then GetUNICODEStringAt = vbNullString Else lngLengthChar = lstrlenW(sz) If lngLengthChar = 0 Then GetUNICODEStringAt = "" Else Redim bytBuffer(1 to lngLengthChar * 2) RtlMoveMemory bytBuffer(1), sz, lngLengthChar * 2 GetUNICODEStringAt = bytBuffer End If End If End Function


リストビューコントロールで、アイテムへのダブルクリックを拾う (VB5)

ただし、リストビューコントロールのMultiSelectがFalseの場合

リストビューコントロールのClickに関するイベントプロシージャは、

の三種類である。アイテム上でダブルクリックした場合には、1.でクリックされたアイテムがSelected=Trueとなり、2.、3.のイベントプロシージャ続けて実行される。アイテムと無関係な位置がダブルクリックされたときには、すべてのアイテムがSelected=Falseとなり、2.と3.のイベントプロシージャが実行される。(VB6の場合、すべてのアイテムがSelected=Falseとなることはない。アイテムと無関係な位置がダブルクリックされた場合でも、すでに選択されたアイテムが選択されつづける。)


したがって、DblClickにおいて、リストビューのListItemsの個々のItemをチェックし、Selected=Trueのものがあれば、このItemに対して処理を行う。Selected=Trueのものがなければ、処理を行わない。(VB6の場合、リストビューコントロールのSelectedItemプロパティで、選択されているListItemへの参照を得ることができる。)


frxファイル

frxファイルはfrmファイルに付随して生成されるが、frmファイルに関連するバイナリデータを格納している。例えば、フォームモジュールに貼り付けられたイメージリストコントロールの画像は、frxファイルに格納されている。


したがって、frmファイルを「部品」として取り扱う場合、同名のfrxファイルが生成されていれば、必ず一体として取り扱わなければならない。普通、frxファイルはfrmファイルと同じディレクトリに生成される。