Excel VBAでUIAutomationを用いてRPAソフトを作成_ver.1.0

# 概要
* VBAでUIAutomationを用いて、UI要素を操作するためのソースコードです。以下の動画で使われているものです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/Zd1ukhIJnTs?si=OJTePHS07zj7mRsz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>　

また、以下の動画も合わせて確認すると、わかりやすいと思います。

<iframe width="560" height="315" src="https://www.youtube.com/embed/J27Sw5v3f0M?si=t3CU_k7bYtueivrh" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# 動作前の設定
* 必ずVBEの参照設定でUIAutomationClientの参照を有効化してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618291/e7f2f0d1-a9b3-87b6-c912-acfaa46cdf92.png)

# 実行環境
以下の環境で動作確認をしました。
* Windows10(64bit)でのExcel 2016
* Windows11でのExcel 2021

# 注意点
* プログラムの実行については、すべて自己責任で行ってください。実行により発生した、いかなる直接的または間接的被害について、作者はその責任を負いません。
* テスト環境で動作確認をしてください。
* ブラウザで使う場合は通信先のサーバーへの負荷、サイトポリシーに留意してください。
* 静的なサイトで主に実施しているため、未知の状況によってエラーが発生する可能性があります。

# コードの簡単な解説
プログラムの大まかな流れは以下のとおりです。
1. ウインドウ名からウインドウハンドルを取得
2. 基準となる要素を取得
3. 実行対象の要素を取得
4. 要素に対して何らかの操作を実行

# 画面の要素の調査方法
Excel VBAでUIAutomationを用いて画面の要素を調査するための記事を掲載します。RPAを実行するうえで必要ですので、ご確認ください。

https://qiita.com/Mrs_P/items/0081e64323be62d59bb5

# 実行方法（パターン①）
パターン①では、操作要素に“要素名”が付与されている場合のコードの書き方について、記載します。

1. まずは、画面要素の調査を行います。上記のサイトに掲載されたコードを利用して、実際の画面と比較しながら対象の要素を確定します。
1. 上記の調査結果を元に、操作をするためのmanipulate関数を作成します。関数の引数は以下のとおりです。
第1引数：ウィンドウの一部分の名前
第2引数：要素の一部分の名前
第3引数：要素の種類
第4引数：基準要素の重複がある場合、その要素の順位
第5引数：「元」と記載
1. パターン①では、第6,7引数の入力は不要です。また、第8引数は、操作したい要素に応じて値を入力します。
1. 実行すると、操作要素が実行され、デバッグウィンドウにログが出力されます。

# 実行方法（パターン②）
パターン②は、操作要素に“要素名”がない場合のコードの書き方について、記載します。

1. パターン①と同様に、まず、画面要素の調査を行います。基準要素を何にするか決定したうえで、要素を探します。
1. この調査結果を元に、操作したい要素を実行するためのManipulateUIElement関数を作成します。関数の引数は以下のとおりです。
第1引数：ウィンドウの一部分の名前
第2引数：基準要素の名前
第3引数：基準要素の種類
第4引数：基準要素の重複がある場合、その要素の順位
第5引数：実行要素が基準要素より前の要素なら「前」、後ろなら「後」
第6引数：操作要素の種類
第7引数：基準要素から移動したときに、操作要素の重複がある場合の順位
第8引数：操作したい要素に応じた値
1. 実行すると、操作要素が実行され、ログが出力されます。

# 第3、第6引数の要素の種類の名前
要素の種類の名前は以下の中から指定します。
1. ボタン
2. チェックボックス
3. コンボボックス, ドロップダウンリスト
4. テキストボックス
5. ハイパーリンク
6. ラジオボタン
7. テキストエリア
8. スプリットボタン
9. 画像


# 第8引数が必要な場合について
以下の要素を操作する場合は、第8引数を必要とする場合があります。
1. コンボボックス, ドロップダウンリスト
2. テキストボックス


上記1.は、下に動かす場合はプラスの数値、上に動かす場合はマイナスの数値を入力します。
上記2.は、設定したい値を入力します。


# 要素を操作するためのコードの例_ver.1.0用
動画で説明したプロシージャの一部例を掲載します。
```vb:Pattern01test, Pattern02test
Sub Pattern01test()
    Call ManipulateUIElement("Challenge", _
                             "Enclosed", "ラジオボタン", "1st", "元", _
                             "", "", _
                             "")
    
End Sub


Sub Pattern02test()
    Call ManipulateUIElement("Challenge", _
                             "State", "テキストエリア", "1st", "後", _
                             "コンボボックス", "1st", _
                             "3")
                             
End Sub
 
```

# 関数として利用するコード_ver.1.0
汎用的に使うコードを以下に記載します。RPAとして動作する根幹部分のコードです。

```vb:汎用関数
Option Explicit

' GetNextWindow関数の宣言
Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" _
  (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
' GetNextWindow関数は、指定されたウィンドウの次のウィンドウのハンドルを取得する関数です。

' IsWindowVisible関数の宣言
Declare PtrSafe Function IsWindowVisible Lib "user32" _
  (ByVal hwnd As LongPtr) As LongPtr
' IsWindowVisible関数は、指定されたウィンドウが可視状態かどうかを判定する関数です。

' GetWindowText関数の宣言
Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
  (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As LongPtr
' GetWindowText関数は、指定されたウィンドウのテキストを取得する関数です。

' FindWindow関数の宣言
Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
  (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
' FindWindow関数は、指定されたクラス名やウィンドウ名からウィンドウのハンドルを取得する関数です。

Dim hwnd As LongPtr ' ウィンドウのハンドルを保持する変数

Const GW_HWNDLAST = 1 ' 最後のウィンドウ
Const GW_HWNDNEXT = 2 ' 次のウィンドウ

Const ElemStartIndex As Long = 0 '要素配列の開始番号（要素配列の終了番号までの値を設定）
Const ElemArrayOperations As Long = 10 '要素配列からの足し引き数（探索が配列数を超えたらerr.number=5のエラーとなる。）


' ウィンドウハンドルを取得するための関数
Function getWindowHandle(ByVal PartialWindowName As String) As LongPtr
    Dim strCaption As String * 500 ' ウィンドウのテキストを保持する変数
    hwnd = FindWindow(vbNullString, vbNullString) ' 最初のウィンドウのハンドルを取得
    Dim cnt As Long ' カウンタ変数
    cnt = 0
    Dim cap As String ' ウィンドウのテキストを保持する変数
    Do
        If IsWindowVisible(hwnd) Then ' ウィンドウが可視状態かどうかを判定
            GetWindowText hwnd, strCaption, Len(strCaption) ' ウィンドウのテキストを取得
            cap = Left(strCaption, InStr(strCaption, vbNullChar) - 1) ' ヌル文字までの部分を抽出
            If InStr(cap, PartialWindowName) <> 0 Then ' ウィンドウのテキストに指定の文字列が含まれているかを判定
                getWindowHandle = hwnd ' ウィンドウのハンドルを返す
                Exit Function ' 関数を終了
            End If
        End If
        hwnd = GetNextWindow(hwnd, GW_HWNDNEXT) ' 次のウィンドウのハンドルを取得
        DoEvents ' イベントを処理
        If hwnd = GetNextWindow(hwnd, GW_HWNDLAST) And cnt = 0 Then ' 最後のウィンドウで初回の場合
            cnt = 1
        ElseIf hwnd = GetNextWindow(hwnd, GW_HWNDLAST) And cnt = 1 Then ' 最後のウィンドウで2回目の場合
            Debug.Print "ウィンドウ取得に失敗" ' デバッグウィンドウにメッセージを出力
            Application.Wait [Now()] + (1 / 86400) ' 要素取得失敗用の待機
            hwnd = FindWindow(vbNullString, vbNullString) ' 最初のウィンドウのハンドルを取得し直す
            cnt = 0 ' カウンタをリセット
        End If
    Loop
End Function

' 与えられた情報を元にUI要素を特定し、指定されたアクションを実行するコード
Function ManipulateUIElement(ByVal PartialWindowName As String, _
                              ByVal ReferenceElementName As String, ByVal ReferenceElementType As String, ByVal ReferenceElementOrder As String, ByVal PositionFromReference As String, _
                              Optional ByVal ActionElementType As String, Optional ByVal ActionElementOrder As String, _
                              Optional ByRef ProcessingValue As String)
    '---引数について---------------------------------------------------------------
    ' 第一引数：PartialWindowName:ウィンドウ名（一部）
    ' 第二引数：ReferenceElementName:基準要素名
    ' 第三引数：ReferenceElementType:基準要素のコントロールタイプ
    ' 第四引数：ReferenceElementOrder:基準条件のx番目の要素（基準要素の重複性対応）
    ' 第五引数：PositionFromReference:基準要素からの位置関係 (前後元)
    ' 第六引数：ActionElementType:操作要素コントロールタイプ
    ' 第七引数：ActionElementOrder:操作要素のx番目の要素（操作要素の重複性対応）
    ' 第八引数：ProcessingValue:処理値
    '------------------------------------------------------------------------------

    ' 初めに基準要素名をデバッグ
    Debug.Print vbCrLf & "-----基準要素名：" & ReferenceElementName & "-----"

    'ManipulateUIElementの引数を数値化等するために変換
    Call ReplaceArgument(ReferenceElementType, ReferenceElementOrder, PositionFromReference, _
                         ActionElementType, ActionElementOrder)
    
    ' ウィンドウ名（一部）からウィンドウハンドルの取得
    hwnd = getWindowHandle(PartialWindowName)
    'ErrorFlag = True エラーが発生した場合、ErrorFlag = False エラーが発生しなかった場合
    Dim ErrorFlag As Boolean
    ' 基準要素の配列番号に関する変数
    Dim ReferenceElementIndex As Long
    ' ウィンドウハンドルの要素（配列）を格納する変数
    Dim aryElm As UIAutomationClient.IUIAutomationElementArray
    Dim ActionElement As UIAutomationClient.IUIAutomationElement
    Do
        'エラーフラグ初期化
        ErrorFlag = False
        
        '基準要素を取得
        ReferenceElementIndex = GetReferenceElement(hwnd, aryElm, ReferenceElementName, ReferenceElementType, ReferenceElementOrder, ErrorFlag)
        
        '操作要素を取得
        If ErrorFlag = False Then
            Set ActionElement = GetActionElement(ReferenceElementIndex, aryElm, _
                                                 PositionFromReference, ActionElementType, ActionElementOrder, ErrorFlag)
        Else
        End If
        
        '要素の実行
        If ErrorFlag = False Then
            Call ExecuteElement(ActionElement, ActionElementType, ProcessingValue, ErrorFlag)
        Else
        End If
        
        If ErrorFlag = True Then
            Application.Wait [Now()] + (0.5 / 86400) ' 要素取得失敗用の待機
        Else
        End If
        
        ' メモリ解放
        Set aryElm = Nothing
        Set ActionElement = Nothing
        DoEvents
    Loop While ErrorFlag = True

End Function

'引数の値を置き換える
Function ReplaceArgument(ByRef ReferenceElementType As String, ByRef ReferenceElementOrder As String, Optional ByRef PositionFromReference As String, _
                          Optional ByRef ActionElementType As String, Optional ByRef ActionElementOrder As String)
    '操作要素に関する引数は両方空白または両方値ありのみ許容
    If ActionElementType <> "" And ActionElementOrder <> "" Then
    ElseIf ActionElementType = "" And ActionElementOrder = "" Then
    Else
        Stop '操作要素の値に問題あり
    End If

    ' 基準要素のコントロールタイプを数値に置換する
    ReferenceElementType = ControlTypeToNumeric(ReferenceElementType)
    ' 操作要素のコントロールタイプを数値に置換する
    If ActionElementType <> "" Then
        ActionElementType = ControlTypeToNumeric(ActionElementType)
    ' 操作要素のコントロールタイプがNULL値の際に参照要素のコントロールタイプを挿入する
    Else
        ActionElementType = ReferenceElementType
    End If
    
    ' 基準要素の「x番目」（の要素）を数値に変換する
    ReferenceElementOrder = numExtract(ReferenceElementOrder)
    ' 操作要素の「x番目」（の要素）を数値に変換する
    ActionElementOrder = numExtract(ActionElementOrder)
    
    ' 操作要素と基準要素が同じ場合の値を設定
    If PositionFromReference = "元" Then
        PositionFromReference = "1"
        ActionElementType = ReferenceElementType
    ' 操作要素が基準要素より前に出現する場合の値を設定
    ElseIf PositionFromReference = "前" Then
        PositionFromReference = "2"
    ' 操作要素が基準要素より後に出現する場合の値を設定
    ElseIf PositionFromReference = "後" Then
        PositionFromReference = "3"
    Else
        Stop ' 設定外の値の場合
    End If
End Function

' ElementTypeの値によってコントロールの種類を返す関数
Function ControlTypeToNumeric(ByVal ElementType As String) As String

    ' コントロールの種類を定義
    Select Case ElementType
        Case "ボタン"
            ' ボタンの種類を表すID（UIA_ButtonControlTypeId）
            ControlTypeToNumeric = 50000
        Case "チェックボックス"
            ' チェックボックスの種類を表すID（UIA_CheckBoxControlTypeId）
            ControlTypeToNumeric = 50002
        Case "コンボボックス", "ドロップダウンリスト"
            ' コンボボックス（ドロップダウンリスト）の種類を表すID（UIA_ComboBoxControlTypeId）
            ControlTypeToNumeric = 50003
        Case "テキストボックス"
            ' テキストボックスの種類を表すID（UIA_EditControlTypeId）
            ControlTypeToNumeric = 50004
        Case "ハイパーリンク"
            ' ハイパーリンクの種類を表すID（UIA_HyperlinkControlTypeId）
            ControlTypeToNumeric = 50005
        Case "ラジオボタン"
            ' ラジオボタンの種類を表すID（UIA_RadioButtonControlTypeId）
            ControlTypeToNumeric = 50013
        Case "テキストエリア"
            ' テキストエリアやスタティックテキスト（表示専用のテキスト）の種類を表すID（UIA_TextControlTypeId）
            ControlTypeToNumeric = 50020
        Case "スプリットボタン"
            ' スプリットボタンの種類を表すID（UIA_SplitButtonControlTypeId）
            ControlTypeToNumeric = 50031
        Case "画像"
            ' 画像の種類を表すID（UIA_ImageControlTypeId）
            ControlTypeToNumeric = 50006
        Case Else
            ' 未知のコントロールタイプの場合
            Stop
    End Select
End Function

'　数字のみ取り出す関数
Function numExtract(StringValue As String) As String
    '変数の準備
    Dim i As Integer
    Dim numText As String
    '半角英数字へ変換
    StringValue = StrConv(StringValue, vbNarrow)
    'Len で文字数が分かる ＝ 1 to 文字列（最終文字）まで For Nextで影響させる。
    For i = 1 To Len(StringValue)
        'Midで文字列を左から順にnumTextに格納
        numText = Mid(StringValue, i, 1)
        'もしnumText[0-9]に該当する場合 変数numExtractに格納していく
        If numText Like "[0-9]" Then: numExtract = numExtract & numText
    Next i
End Function

' 基準要素を取得する関数
Function GetReferenceElement(ByVal hwnd As LongPtr, ByRef aryElm As UIAutomationClient.IUIAutomationElementArray, ByVal ReferenceElementName As String, _
                              ByVal ReferenceElementType As String, ByVal ReferenceElementOrder As String, _
                              ByRef ErrorFlag As Boolean) As Long
    ' 引数について、aryElm、ErrorFlagは値を切り替えるためにbyrefとする

    ' エラーハンドリング
    On Error GoTo HandleElementError
    
    ' UIAutomation オブジェクトの作成
    Dim uiAuto As UIAutomationClient.CUIAutomation
    Set uiAuto = New UIAutomationClient.CUIAutomation

    ' UIAutomation 条件の作成
    Dim uiCnd As IUIAutomationCondition
    Set uiCnd = uiAuto.CreateTrueCondition

    Dim ElementX As UIAutomationClient.IUIAutomationElement
    
    ' ウィンドウハンドルから要素を取得
    Set ElementX = uiAuto.ElementFromHandle(ByVal hwnd)
    
    ' 要素の配列を取得　※読み込みに時間がかかる場合あり
    Set aryElm = ElementX.FindAll(TreeScope_Subtree, uiCnd)
    
    Dim i As Long
    Dim DuplicateElementCount As Long
    ' 配列内で指定された要素が何回重複しているかを数える変数
    DuplicateElementCount = 0
    
    For i = ElemStartIndex To aryElm.Length - 1
        ' 要素の名前が基準名を含み、コントロールタイプが指定されたものである場合
        If InStr(aryElm.GetElement(i).CurrentName, ReferenceElementName) <> 0 _
           And aryElm.GetElement(i).CurrentControlType = ReferenceElementType Then
            ' 名前が何番目にくるかをカウント
            DuplicateElementCount = DuplicateElementCount + 1
            If DuplicateElementCount = ReferenceElementOrder Then
                ' 基準の要素が見つかった場合はインデックスを返す
                GetReferenceElement = i
                Debug.Print "1.基準要素を取得"
                Exit Function
            End If
        End If
    Next i
    
    Debug.Print "基準要素が見つかりません"
    ErrorFlag = True
    Exit Function

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "基準要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "基準要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Function

' 操作要素を取得する関数
Function GetActionElement(ByVal ReferenceElementIndex As Long, ByVal aryElm As UIAutomationClient.IUIAutomationElementArray, _
                            ByVal PositionFromReference As String, ByVal ActionElementType As String, ByVal ActionElementOrder As String, ByRef ErrorFlag As Boolean) _
                            As UIAutomationClient.IUIAutomationElement
    ' エラーハンドリング
    On Error GoTo HandleElementError
    ' 正負（1 or -1）いずれかをとる変数
    Dim EitherPlusMinus As Integer

    ' 操作要素と基準要素が同じ場合
    If PositionFromReference = 1 Then
        Set GetActionElement = aryElm.GetElement(ReferenceElementIndex)
        Exit Function
    ' 操作要素が基準要素より前に出現する場合は、負の方向へ配列番号を動かす
    ElseIf PositionFromReference = 2 Then
        EitherPlusMinus = -1
    ' 操作要素が基準要素より前に出現する場合は、正の方向へ配列番号を動かす
    ElseIf PositionFromReference = 3 Then
        EitherPlusMinus = 1
    Else
    End If
    
    Dim DuplicateElementCount As Long
    ' 配列内で指定された要素が何回重複しているかを数える変数
    DuplicateElementCount = 0

    Dim k As Long

    ' kの初期値：「基準要素の配列番号」に 1を加算または減算した値
    ' kのループ範囲：「kの初期値」に、要素配列からの足し引き数（定数：ElemArrayOperations）を演算した値まで
    ' kの増減：1または-1（EitherPlusMinus ）ずつ
    For k = ReferenceElementIndex + EitherPlusMinus To ReferenceElementIndex + (ElemArrayOperations * EitherPlusMinus) Step EitherPlusMinus
        ' 配列 aryElm の k 番目の要素の コントロールタイプ が 操作要素のコントロールタイプ と一致するか    
        If aryElm.GetElement(k).CurrentControlType = ActionElementType Then
            ' 「操作要素の重複カウンター」を 1 増加させます。
            DuplicateElementCount = DuplicateElementCount + 1

            ' 「操作要素の重複カウンター」が 「操作要素のx番目（ユーザー指定の引数）」に等しい場合に処理を実行します。
            If DuplicateElementCount = ActionElementOrder Then
                ' GetActionElement に要素を設定して、関数を抜ける。
                Set GetActionElement = aryElm.GetElement(k)
                Debug.Print "2.操作要素を取得"
                Exit Function
            Else
            End If
        Else
        End If
    Next k
        Debug.Print "操作要素を見つけられません"
        ErrorFlag = True
    Exit Function

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Function

    ' ActionElementTypeに応じて異なるアクションを実行するための関数
Function ExecuteElement(ByVal ActionElement As UIAutomationClient.IUIAutomationElement, _
                        ByVal ActionElementType As String, _
                        ByRef ProcessingValue As String, _
                        ByRef ErrorFlag As Boolean)

    ' ActionElementTypeに応じて異なるアクションを実行する
    Select Case ActionElementType
        Case "50000"  ' ボタンの種類を表します。 uiInvokeアクションを実行します
            Call InvokeAction(ActionElement, ErrorFlag)

        Case "50002"  ' チェックボックスの種類を表します。 uiToggleアクションを実行します
            Call ToggleAction(ActionElement, ErrorFlag)

        Case "50003"  ' コンボボックス（ドロップダウンリスト）の種類を表します。 uiExpandCollapseアクションを実行します
            Call ExpandCollapseAction(ActionElement, ProcessingValue, ErrorFlag)

        Case "50004"  ' テキストボックスの種類を表します。 uiValueアクションを実行します
            Call UIValueAction(ActionElement, ProcessingValue, ErrorFlag)

        Case "50005"  ' ハイパーリンク（通常はウェブページへのリンク）の種類を表します。 uiInvokeアクションを実行します
            Call InvokeAction(ActionElement, ErrorFlag)

        Case "50013"  ' ラジオボタンの種類を表します。 uiSelectionItemアクションを実行します
            Call SelectionItemAction(ActionElement, ErrorFlag)

        Case "50020"  ' テキストエリアやスタティックテキスト（表示専用のテキスト）の種類を表します。 テキストアクションを実行します
            Call TextAction(ActionElement, ProcessingValue, ErrorFlag)

        Case "50031"  ' ボタンとドロップダウンリストを組み合わせたコントロール（スプリットボタン）の種類を表します。 uiInvokeアクションを実行します
            Call InvokeAction(ActionElement, ErrorFlag)

        Case "50006"  ' 画像の種類を表します。例えば、アイコンや写真など。
            '画像の表示を確認
        Case Else     ' 未知の数値の場合
    End Select

End Function



' UI要素に対して何かを起動する関数
Sub InvokeAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByRef ErrorFlag As Boolean)
    Dim uiInvoke As IUIAutomationInvokePattern
    ' エラーハンドリング
    On Error GoTo HandleElementError
    
    Set uiInvoke = uiElm.GetCurrentPattern(UIA_InvokePatternId)
  
    uiInvoke.Invoke
    Debug.Print "3.Invokeを実行"
    ' 待機
    Application.Wait [Now()] + (0.2 / 86400)
    Exit Sub

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub

' UI要素へ値を設定する関数
Sub UIValueAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByVal ProcessingValue As String, ByRef ErrorFlag As Boolean)
    '引数：ProcessingValue: 設定する値の文字列

    Dim uiValue As IUIAutomationValuePattern
    ' エラーハンドリング
    On Error GoTo HandleElementError
    
    ' UI AutomationのValueパターンを取得する
    Set uiValue = uiElm.GetCurrentPattern(UIA_ValuePatternId)
    
    ' 取得したValueパターンに値を設定する
    uiValue.SetValue ProcessingValue
    Debug.Print "3.SetValueを実行"
    ' 待機
    Application.Wait [Now()] + (0.1 / 86400)
    Exit Sub

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub

' UI要素名を取得する関数
Sub TextAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByRef ProcessingValue As String, ByRef ErrorFlag As Boolean)
    On Error GoTo HandleElementError
    
    ProcessingValue = uiElm.CurrentName
    Debug.Print "3.対象の要素名を取得"
    Exit Sub

HandleElementError:
    ' その他の何らかのエラーが呼び出されたため
    Debug.Print Err.Number & ":" & Err.Description
    ErrorFlag = True
    Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub


' ラジオボタンを押すための関数
Sub SelectionItemAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByRef ErrorFlag As Boolean)
    '引数：ProcessingValue: 設定する値の文字列
    Dim uiSelectionItem As IUIAutomationSelectionItemPattern
    
    On Error GoTo HandleElementError
    
    Set uiSelectionItem = uiElm.GetCurrentPattern(UIA_SelectionItemPatternId)

    uiSelectionItem.Select
    Debug.Print "3.Selectを実行"

    ' 待機
    Application.Wait [Now()] + (0.1 / 86400)
    Exit Sub

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub

' ドロップダウンリストの値を選択する関数
Sub ExpandCollapseAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByVal ProcessingValue As String, ByRef ErrorFlag As Boolean)
    '引数：ExpansionCount: 正の数で下方向、負の数で上方向へ動かす
    On Error GoTo HandleElementError
    Dim uiExpandCollapse As IUIAutomationExpandCollapsePattern
    Dim i As Long
    ' 数字のみ半角で抜き出す
    ProcessingValue = numExtract(ProcessingValue)

    Dim ExpansionCount As Integer
    ' ProcessingValueを数値化（ProcessingValueに日本語が入力されていると0となる。）
    ExpansionCount = Val(ProcessingValue)
    
    Set uiExpandCollapse = uiElm.GetCurrentPattern(UIA_ExpandCollapsePatternId)

    ' 要素を展開する
    uiExpandCollapse.Expand

    If ExpansionCount < 0 Then
        ' 上方向へ動かす
        ExpansionCount = ExpansionCount * (-1)
        For i = 1 To ExpansionCount
            Call SendCustomKeys("{UP}")
            Application.Wait [Now()] + (0.1 / 86400)
        Next i
    Else
        ' 下方向へ動かす
        For i = 1 To ExpansionCount
            Call SendCustomKeys("{DOWN}")
            Application.Wait [Now()] + (0.1 / 86400)
        Next i
    End If

    ' 選択を確定する
    Call SendCustomKeys("{ENTER}")
    Debug.Print "3.Expandを実行"

    Application.Wait [Now()] + (0.1 / 86400)

    Exit Sub

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub

' SendKeysをカスタマイズした関数
Public Sub SendCustomKeys(ByVal Keys As String, Optional ByVal Wait As Boolean = False)
    Dim w As Object
    ' WScript.Shellを使用してSendKeysを呼び出す
    
    If w Is Nothing Then
        Set w = CreateObject("WScript.Shell")
    End If
    
    ' SendKeys関数を呼び出す
    Call w.SendKeys(Keys, Wait)
End Sub



' チェックボックスを選択する関数
Sub ToggleAction(ByVal uiElm As UIAutomationClient.IUIAutomationElement, ByRef ErrorFlag As Boolean)

    Dim uiToggle As IUIAutomationTogglePattern
    
    On Error GoTo HandleElementError
    
    Set uiToggle = uiElm.GetCurrentPattern(UIA_TogglePatternId)
    
    uiToggle.Toggle
    Debug.Print "3.Toggleを実行"
    ' 待機
    Application.Wait [Now()] + (0.1 / 86400)
    Exit Sub

HandleElementError:
    If Err.Number = -2147220991 Then
        ' 存在しなくなった要素に対してメソッドが呼び出されたため（エラーコード -2147220991）
        Debug.Print "操作要素が存在しなくなりました"
        ErrorFlag = True
    ElseIf Err.Number = -2147220992 Then
        ' 無効にされた要素に対して、 有効な要素を必要とするメソッド（ Select や Expand など）が呼び出されたため（エラーコード -2147220992）
        Debug.Print "操作要素が無効にされました"
        ErrorFlag = True
    Else
        ' その他の何らかのエラーが呼び出されたため
        Debug.Print Err.Number & ":" & Err.Description
        ErrorFlag = True
        Stop '予期せぬエラー確認用。このStopをコメントアウトすると、処理を止めずに要素の取得をやり直す。
    End If
    ' エラーをクリアし、エラーハンドリングを元に戻す
    Err.Clear
    On Error GoTo 0
End Sub

```

# 把握している動き
* ウィンドウ名を間違えたときにウィンドウを取得するためにループします。
* 基準要素が見つからないときに基準要素を取得するためにループします。
* 操作要素が見つからないときに操作要素を取得するためにループします。
* 重複数およびelementindexを大きい値にしすぎて、要素の配列番号が要素の範囲外を指定したとき実行が止まります。
* 要素について特定のエラーを検知したとき、"基準要素が存在しなくなりました"、"基準要素が無効にされました"という文言がデバッグされます。（画面が読み込み中などの理由で現れます。）
* それぞれのコントロールタイプに対して、特定の１つの操作が指定されています。指定された操作以外の操作は動作せず、特定の操作が実行されるか、または操作が実行できないかいずれかの挙動になります。
* その他のVBA上のエラーが起きたときはエラー番号と内容をデバッグして止まります。
* 画面の要素数が多いほど読み込み時間が長くなります。


# コードの修正履歴
ex ) yyyy/mm/dd : AAAAAを修正
2023/12/31：「ManipulateUIElement」関数の初めに、基準要素名をデバッグするようにし、処理対象の要素名をログに残すようにしました。