サイトアイコン わんすけに聞いてみる

UIAutomationでUI操作してみる

■ UI自動化について考える

 

前回なんとなくUI検査ツール使ってみたらAutomationIDってのを発見して、これはもしかしてUI自動化できるのか?という気配を感じた。

Inspectツールの上のプルダウンから、「MSAA」、「UI Automation」が選択できる。

どうもすこし調べた感じだとMSAAの方が昔っからある規格らしい。

AutomationID」は、「UI Automation」の方を選択した時に取得できる。

と、いう訳でUI Automationというライブラリについていろいろ調べて実験した。

 

1..NETフレームワークから使えるようだ。

.NETフレームワークから、System.Windows.Automationというライブラリから使えるようだ。

.NETフレームワーク開発といえば、Visual Studioさん。

まず、参照設定を追加してUI Automationを使えるようにする。

この2つが参照設定で追加されてればいいみたいね。

 

2.とにかくコード書いて試しまくった。

コンソールアプリケーションでプロジェクト作ってMicrosoft Docsとにらめっこ。

動くまであきらめずサンプルコード試しまくった。

なんとなく動く感じになってきたコードがこれ。

Imports System.Windows.Automation

Module Module1

    Sub Main(args As String())
        Dim argVal As String

        If args.Length < 3 Then
            Console.WriteLine("コマンドラインオプションが不足しています。")
            Exit Sub
        ElseIf args.Length = 4 Then
            argVal = args(3)
        End If

        ' コマンドライン引数を取得
        Dim func As String = args(0)
        Dim hwnd As String = args(1)
        Dim TargetVals As String() = Split(args(2), ":")

        Try
            ' vbs単体起動用に「GetHWND:」で始まるキーワードが指定されていた場合には独自にhandle取得する。
            If (hwnd.StartsWith("GetHWND:")) Then
                hwnd = GetWindowHandle(Mid(hwnd, 9))
            End If
            ' AutomationElementを取得する。
            Dim elm As AutomationElement = AutomationElement.FromHandle(hwnd)
            Dim TargetElm As AutomationElement
            Dim AutoProp As AutomationProperty
            Dim PropCond As PropertyCondition

            Select Case TargetVals.Length
                Case 1
                    ' 初版との互換対応、単一指定の場合はAutomationIDで要素検索
                    AutoProp = AutomationElement.AutomationIdProperty
                    PropCond = New PropertyCondition(AutoProp, TargetVals(0), PropertyConditionFlags.IgnoreCase)
                    TargetElm = elm.FindFirst(TreeScope.Descendants, PropCond)
                Case 2
                    ' 2条件指定の場合は、検索プロパティの指定ができる
                    Select Case TargetVals(0)
                        Case "Name"
                            AutoProp = AutomationElement.NameProperty
                        Case "AutomationID"
                            AutoProp = AutomationElement.AutomationIdProperty
                        Case Else
                            Console.WriteLine("操作対象の指定方法が不正です。")
                            Exit Sub
                    End Select

                    PropCond = New PropertyCondition(AutoProp, TargetVals(1), PropertyConditionFlags.IgnoreCase)
                    TargetElm = elm.FindFirst(TreeScope.Descendants, PropCond)
                Case 3
                    ' 3条件指定の場合は、Previous または Next 、FirstChild、LastChildによる相対位置指定
                    Select Case TargetVals(0)
                        Case "Name"
                            AutoProp = AutomationElement.NameProperty
                        Case "AutomationID"
                            AutoProp = AutomationElement.AutomationIdProperty
                        Case Else
                            Console.WriteLine("操作対象の指定方法が不正です。")
                            Exit Sub
                    End Select

                    PropCond = New PropertyCondition(AutoProp, TargetVals(2), PropertyConditionFlags.IgnoreCase)
                    TargetElm = elm.FindFirst(TreeScope.Descendants, PropCond)

                    Select Case TargetVals(1)
                        Case "Previous"
                            TargetElm = TreeWalker.ControlViewWalker.GetPreviousSibling(TargetElm)
                        Case "Next"
                            TargetElm = TreeWalker.ControlViewWalker.GetNextSibling(TargetElm)
                        Case "FirstChild"
                            TargetElm = TreeWalker.RawViewWalker.GetFirstChild(TargetElm)
                        Case "LastChild"
                            TargetElm = TreeWalker.RawViewWalker.GetLastChild(TargetElm)
                        Case Else
                            Console.WriteLine("操作対象の指定方法が不正です。")
                            Exit Sub
                    End Select
            End Select

            If IsNothing(TargetElm) Then
                Console.WriteLine("操作対象を取得できませんでした。")
                Exit Sub
            End If

            ' 第一引数によって処理を変える。
            Select Case StrConv(func, VbStrConv.Lowercase)
                Case "click"
                    Dim ptnInvk As InvokePattern = TargetElm.GetCurrentPattern(InvokePattern.Pattern)
                    ptnInvk.Invoke()
                Case "focus"
                    TargetElm.SetFocus()
                Case "getvalue"
                    Dim ptnVal As ValuePattern = TargetElm.GetCurrentPattern(ValuePattern.Pattern)
                    Console.WriteLine(ptnVal.Current.Value)
                Case "setvalue"
                    Dim ptnVal As ValuePattern = TargetElm.GetCurrentPattern(ValuePattern.Pattern)
                    ptnVal.SetValue(argVal)
                Case "getselection"
                    Dim SlctPtn As SelectionPattern = TargetElm.GetCurrentPattern(SelectionPattern.Pattern)
                    Dim SelectLabel As String = ""
                    For Each SlctItm As AutomationElement In SlctPtn.Current.GetSelection()
                        If (Len(SelectLabel) > 0) Then SelectLabel = SelectLabel & ","
                        SelectLabel = SelectLabel & SlctItm.GetCurrentPropertyValue(AutomationElement.NameProperty).ToString()
                    Next
                    Console.WriteLine(SelectLabel)
                Case "getselectionitemisselected"
                    Dim ptnVal As SelectionItemPattern = TargetElm.GetCurrentPattern(SelectionItemPattern.Pattern)
                    Console.WriteLine(ptnVal.Current.IsSelected)
                Case "setselectionitemvalue"
                    If (TargetElm.GetCurrentPropertyValue(AutomationElement.IsSelectionPatternAvailableProperty)) Then
                        Dim itemElm As AutomationElement = TargetElm.FindFirst(TreeScope.Subtree, New PropertyCondition(AutomationElement.NameProperty, argVal))
                        Dim ptnVal As SelectionItemPattern = itemElm.GetCurrentPattern(SelectionItemPattern.Pattern)
                        ptnVal.Select()
                    ElseIf (TargetElm.GetCurrentPropertyValue(AutomationElement.IsSelectionItemPatternAvailableProperty)) Then
                        Dim ptnVal As SelectionItemPattern = TargetElm.GetCurrentPattern(SelectionItemPattern.Pattern)
                        ptnVal.Select()
                    End If
                Case "gettoggle"
                    Dim TglPtn As TogglePattern = TargetElm.GetCurrentPattern(TogglePattern.Pattern)
                    Console.WriteLine(TglPtn.Current.ToggleState = ToggleState.On)
                Case "settoggle"
                    Dim TglPtn As TogglePattern = TargetElm.GetCurrentPattern(TogglePattern.Pattern)
                    Select Case StrConv(argVal, VbStrConv.Lowercase)
                        Case "0", "false"
                            If (TglPtn.Current.ToggleState = ToggleState.On) Then
                                TglPtn.Toggle()
                            End If
                        Case "1", "true"
                            If (TglPtn.Current.ToggleState <> ToggleState.On) Then
                                TglPtn.Toggle()
                            End If
                    End Select
                Case "getname"
                    Console.WriteLine(TargetElm.GetCurrentPropertyValue(AutomationElement.NameProperty).ToString())
                Case Else
                    Console.WriteLine("[" & func & "]に相当する処理はありませんでした。")
            End Select
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Private Function GetWindowHandle(part_key As String) As String
        Dim hwnd As String = part_key
        For Each p As Process In System.Diagnostics.Process.GetProcesses()
            If (p.ProcessName Like "*" & part_key & "*") Or
               (p.MainWindowTitle Like "*" & part_key & "*") Then
                hwnd = p.MainWindowHandle
                Exit For
            End If
        Next
        Return hwnd
    End Function

End Module

 

3.使い方

コマンドプロンプトから3~4個のオプションを指定して起動する。

(:で区切ってオプション指定することでNext・Previousにも対応)

電卓の計算結果が「Text」ってなっててGetValueだと値とれなかったからGetNameを追加した。

でも、このまま結果取得すると「表示は●●です」って出てしまう。。。

 

でも、なんとなくこれをWinActorのスクリプト実行ステージに仕込めばイベントモードのWIN32では操作が座標指定になってしまう要素も操作できるんじゃないかという期待を込めて。

コンパイル済みファイルはこちら

モバイルバージョンを終了