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から文字列を受け取る方法は、

  1. ByValでAPIに引き渡した文字列が、その場で書き換えられて戻る場合、
  2. *LPTSTRに対してByRefで引き渡し、Automation以外の方法で文字列が書き換えられて戻る場合、
  3. APIの戻り値が文字列へのポインタである場合、
  4. ByRefで引き渡したBSTRを用いて、OLE Automationにより文字列が書き換えられて戻る場合、
の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. ItemClick:クリックされたアイテムが引数となる、
  2. Click:クリックされた位置は不明、
  3. DblClick:クリックされた位置は不明、
の三種類である。アイテム上でダブルクリックした場合には、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ファイルと同じディレクトリに生成される。


表紙ページに戻る