● 文字列検索ダイアログ ●

VBでは使用不可能と思っていたんだけど、…できちゃった。自分でもびっくり。

検索ダイアログボックスを表示させるのは簡単なだけど、表示させるだけではイベント("次を検索"、"キャンセル"等が押される)を受け取れない。イベントを拾えるようにするには RegisterWindowMessage 関数でメッセージを登録する必要がある。検索ダイアログボックスの場合は、"FINDMSGSTRING"を指定すればよろしい。"COMMDLG.H" ファイルを見ると、

    #define FINDMSGSTRINGA "commdlg_FindReplace"

とあるのでこれを使用する。変数名の一番右にある'A'の文字は取り去ってしまって大丈夫。さて、これを登録することで、ウインドウプロシージャよりメッセージを受け取れるようになる。また、lParam には FINDREPLACE構造体へのポインタが送られてくる。 FINDREPLACE 構造体はVBでは次の通り。

Private Type FINDREPLACE
      lStructSize As Long
      hwndOwner As Long
      hInstance As Long
      flags As Long
      lpstrFindWhat As Long
      lpstrReplaceWith As Long
      wFindWhatLen As Integer
      wReplaceWithLen As Integer
      lCustData As Long
      lpfnHook As Long
      lpTemplateName As Long
End Type

flags から大文字・小文字を区別する、下へ検索 などの情報を取得できる。また lpstrFindWhat からは、検索する文字列を取得できる。このメンバは検索に指定された文字列へのポインタであり、それはLong型である。これから文字列を取得するには、それなりの処理をしなければならない。これはソースを確認されたい。

それでは、フォーム(オブジェクト名:frmMain)にテキストボックス(オブジェクト名:何でもよろしい)、コマンドボタン(オブジェクト名:Command1)を配置しませう。ここでの例は、大文字・小文字を区別するを指定できるようにしている。じゃ、いきまっせ。

@まず、フォームモジュールに一気に次のコードを書きましょう。いきなり関数が出てくるけど、気にしない。

Private Sub Form_Load()

    'ウインドウメッセージを登録する
    Call RegWindowMessage

    'サブクラス化を開始する
    Call SubClass(frmMain.hwnd)

End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

    'サブクラス化を解除する
    Call UnSubClass(frmMain.hwnd)

End Sub

Private Sub Command1_Click()

    Call FindMyWord(frmMain.hwnd)

End Sub

A標準モジュールを用意して、次のコード達を書いてね。

'頼もしいAPI関数達
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Private Declare Function RegisterWindowMessage Lib "user32" Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long

Private Declare Function FindText Lib "comdlg32.dll" Alias "FindTextA" (pFindreplace As FINDREPLACE) As Long

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)

Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Long) As Long

Private Type FINDREPLACE
      lStructSize As Long
      hwndOwner As Long
      hInstance As Long
      flags As Long
      lpstrFindWhat As Long
      lpstrReplaceWith As Long
      wFindWhatLen As Integer
      wReplaceWithLen As Integer
      lCustData As Long
      lpfnHook As Long
      lpTemplateName As Long
End Type

'定数
Private Const EM_GETSEL = &HB0
Private Const WM_GETTEXT = &HD
Private Const EM_SETSEL = &HB1
Private Const EM_SCROLLCARET = &HB7

Private Const FR_DIALOGTERM = &H40
Private Const FR_DOWN = &H1
Private Const FR_ENABLEHOOK = &H100
Private Const FR_ENABLETEMPLATE = &H200
Private Const FR_ENABLETEMPLATEHANDLE = &H2000
Private Const FR_FINDNEXT = &H8
Private Const FR_HIDEMATCHCASE = &H8000
Private Const FR_HIDEUPDOWN = &H4000
Private Const FR_HIDEWHOLEWORD = &H10000
Private Const FR_MATCHCASE = &H4
Private Const FR_NOMATCHCASE = &H800
Private Const FR_NOUPDOWN = &H400
Private Const FR_NOWHOLEWORD = &H1000
Private Const FR_REPLACE = &H10
Private Const FR_REPLACEALL = &H20
Private Const FR_SHOWHELP = &H80
Private Const FR_WHOLEWORD = &H2

Private Const GWL_WNDPROC = (-4)  'ウインドウプロシージャ
Private Const FINDMSGSTRING = "commdlg_FindReplace"

Private  udtFINDREPLACE As FINDREPLACE

'新規メッセージ識別子
Private WM_FDDLGEVENT As Long

'検索文字
Private fndString As String

'デフォルトのウインドウプロシージャのハンドル
Private OldWindowhWnd As String

'---------------------------------------------------------------------------
'  関数名 : RegWindowMessage
'  機能   : ウインドウメッセージを登録する
'  引数   : なし
'  返り値 : イベントメッセージ定数
'---------------------------------------------------------------------------
Public Sub RegWindowMessage()
    WM_FDDLGEVENT = RegisterWindowMessage(FINDMSGSTRING)
End Sub

'---------------------------------------------------------------------------
'  関数名 : SubClass
'  機能   : サブクラス化を開始する
'  引数   : (in) hWnd … フォームのウインドウハンドル
'  返り値 : なし
'---------------------------------------------------------------------------
Public Sub SubClass(ByVal hwnd As Long)
    OldWindowhWnd = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
End Sub

'---------------------------------------------------------------------------
'  関数名 : UnSubClass
'  機能   : サブクラス化を解除する
'  引数   : (in) hWnd … フォームのウインドウハンドル
'  返り値 : なし
'---------------------------------------------------------------------------
Public Sub UnSubClass(ByVal hwnd As Long)
    If OldWindowhWnd <> 0 Then
    '元のプロシージャアドレスに設定する
        Call SetWindowLong(hwnd, GWL_WNDPROC, OldWindowhWnd)
        OldWindowhWnd = 0
    End If
End Sub

'---------------------------------------------------------------------------
'  関数名 : FindMyWord
'  機能   : 文字列検索ダイアログボックスを表示する
'  引数   : (in)hWnd … 対象ハンドル
'  返り値 : なし
'---------------------------------------------------------------------------
Public Sub FindMyWord(ByVal hwnd As Long)

    With udtFINDREPLACE
        .lStructSize = Len(udtFINDREPLACE)
        .hInstance = App.hInstance
        .hwndOwner = hWnd

        fndString = String$(256, Chr$(0)) '文字列初期化

        .lpstrFindWhat = StrPtr(fndString) '文字列のアドレスを渡す
        .wFindWhatLen = Len(fndString)
        .flags = FR_FINDNEXT Or FR_MATCHCASE Or FR_HIDEWHOLEWORD Or FR_HIDEUPDOWN
    End With udtFINDREPLACE

    Call FindText(udtFINDREPLACE)

End Sub

'---------------------------------------------------------------------------
'  関数名 : WindowProc
'  機能   : ウインドウプロシージャ
'  引数   : (in) hWnd … サブクラス化したウインドウハンドル
'          (in) uMsg … ウインドウメッセージ
'          (in) wParam … 追加情報1
'          (in) lParam … 追加情報2
'  戻り値 : ウインドウプロシージャのアドレス
'---------------------------------------------------------------------------
Public Function WindowProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

    Select Case uMsg
        Case WM_FDDLGEVENT
            '検索処理
            Call SearchGetString(hwnd, lParam)

            WindowProc = 0&
        Case Else
            WindowProc = CallWindowProc(OldWindowhWnd, hwnd, uMsg, wParam, lParam)
    End Select

End Function

'---------------------------------------------------------------------------
'  関数名 : SearchGetString
'  機能   : 文字列を検索する
'  引数   : (in) hWnd … オーナーウインドウのハンドル
'          (in) udtFRPtr … FINDREPLACE構造体へのポインタ
'  戻り値 : なし
'---------------------------------------------------------------------------
Public Sub SearchGetString(ByVal hwnd As Long, ByVal udtFRPtr As Long)

    Dim udtFR As FINDREPLACE
    Dim gStrPtr As Long    '検索文字列へのポインタ
    Dim gText As String
    Dim SearchText As String    '検索する文字列
    Dim gStrLength As Long    '文字列の長さ

    'FINDREPLACE構造体情報をコピー
    Call CopyMemory(udtFR, ByVal udtFRPtr, Len(udtFR))

    '次を検索が押された
    If (udtFR.flags And FR_FINDNEXT) = FR_FINDNEXT Then

        '文字列のアドレス
        gStrPtr = udtFR.lpstrFindWhat

        '文字列の長さを取得
        gStrLength = lstrlen(gStrPtr)

        'Nullで初期化
        gText = String$(gStrLength, Chr$(0))

        '文字列取得
        Call CopyMemory(ByVal gText, ByVal gStrPtr&, gStrLength)

        SearchText = gText

        'この先に検索処理を書く
        '          ・
        '          ・
        '          ・

        '大文字・小文字の区別をする
        If udtFR.flags And FR_MATCHCASE Then
                '検索処理
        '大文字・小文字の区別をしない
        Else
                '検索処理
        End If

        '          ・
        '          ・
        '          ・
        '検索処理終了
    Else
        'キャンセルが押された
    End If

End Sub

気をつけることは、コマンドボタンを押す度にダイアログが表示されてしまうこと。フラグなど用意して、2つ目以降を表示させないようにするようにしないと駄目ですぜ。


戻る