指定した文字列がタイトルに含まれるウィンドウのハンドルを取得するVBAコード

Win32API を呼び出してウィンドウの操作をする場合、操作したいウィンドウのハンドルを取得する必要がある。

たいていはウィンドウのタイトル文字列を頼りにハンドル（hWnd）を取得するのだが、完全なタイトル文字列（title_str）がわかっている場合は、API の `FindWindow`関数で 

```vb
    hWnd = FindWindow(vbNullString, title_str)
```
と、簡単にハンドルを取得できる。が、タイトル文字列の一部を頼りにウィンドウハンドルを取得する場合は、ちょっと面倒くさくなる。

流れとしては、

1. 基準となるウィンドウ（最前面の）ハンドルを取得する。`FindWindow`
1. 基準ウィンドウからスタートして、現在開いているすべての可視ウィンドウをループし、ウィンドウのタイトルを調べる。`GetWindowText`
1. タイトルの中に指定した文字が含まれている可視ウィンドウ`IsWindowVisible` があれば、ループを終了しウィンドウハンドルを取得する。`GetWindow`

```vb:WindowModule.bas
Option Explicit

'// API呼び出し（Declare）宣言

#If VBA7 Then

    '// Office2010以降で64/32bit共通の宣言

    '// 指定されたクラス名（lpClassName）とウィンドウ名（lpWindowName）を持つウィンドウのハンドルを返す
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
    
    '// 指定されたウィンドウ（hWnd）と指定された関係（wCmd）にあるウィンドウのハンドルを返す
    Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As LongPtr, ByVal wCmd As Long) As LongPtr

    '// ウィンドウ（hWnd）のタイトルをバッファ（lpString）に格納する
    Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long

    '// ウィンドウの可視状態を取得する
    Public Declare PtrSafe Function IsWindowVisible Lib "user32" (ByVal hWnd As LongPtr) As Long
   
#Else

    '// Office2007以前での宣言

    '// 指定されたクラス名（lpClassName）とウィンドウ名（lpWindowName）を持つウィンドウのハンドルを返す
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

    '// 指定されたウィンドウ（hWnd）と指定された関係（wCmd）にあるウィンドウのハンドルを返す
    Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
    
    '// ウィンドウ（hWnd）のタイトル（lpString）を取得する
    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

    '// ウィンドウの可視状態を取得する
    Public Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long

#End If
    
Private Const GW_HWNDNEXT   As Long = 2
Private Const ERROR_SUCCESS As Long = 0
```
```vb
'// タイトルに cap_str が含まれるウィンドウのハンドルを返す

Function GetHwnd(ByVal cap_str As String) As LongPtr

    Dim hWnd        As LongPtr
    Dim wCaption    As String * 255

    '// 基準となるウィンドウハンドルを取得
    '// FindWindow は、どちらの引数にも vbNullString を指定すると
    '// 最前面のウィンドウのハンドルを返す
    hWnd = FindWindow(vbNullString, vbNullString)
    
    Do Until hWnd = ERROR_SUCCESS
    
        '// 可視ウィンドウのみ
        If CBool(IsWindowVisible(hWnd)) Then        
            '// ウィンドウのキャプション（タイトル文字列）を取得
            '// GetWindowText は、ウィンドウタイトルをバッファ（wCaption）に格納する
            GetWindowText hWnd, wCaption, Len(wCaption)            
            If InStr(wCaption, cap_str) > 0 Then Exit Do
        End If
                        
        '// 次のウィンドウのウィンドウハンドルを取得
        '// GetWindow は、第二引数に GW_HWNDNEXT（=2）をして指定すると
        '// 次のウィンドウのハンドルを返す
        hWnd = GetWindow(hWnd, GW_HWNDNEXT)
    Loop
    
    GetHwnd = hWnd

End Function
```
逆に、ウィドウハンドル（hWnd）がわかっている場合にウィンドウタイトルを取得するのは比較的簡単である。
ウィンドウタイトルをバッファ（wCaption）に格納する `GetWindowText`関数の動き（引数）を理解しよう。

```vb
'// 指定したハンドルのウィンドウタイトルを返す

' GetWindowText は、バッファ（wCaption）にウィンドウタイトルを格納する
' 255文字分のバッファを取っておいたのでバッファの後半（タイトル文字列以降）は
' Null文字になるため、Left関数で Null文字より前の文字列を取り出す。

Function GetWindowTitle(ByVal hWnd As LongPtr) As String
    Dim wCaption As String * 255
    GetWindowText hWnd, wCaption, Len(wCaption)    
    GetWindowTitle = Left(wCaption, InStr(wCaption, vbNullChar) - 1)
End Function
```
---
＜参考サイト＞

- [F-Basic Programming Tips](https://www.tokovalue.jp/)
- [WindowsAPIをOffice64bit版または32bit版のVBAで使うには](https://hatena19.com/use-win-api-with-office-64bit-or-32bit-vba/)