■ UI自動化について考える
- ウィンドウハンドルについて
- アプリケーションのUIを分析する(Inspectツール)
- UIAutomationでUI操作してみる ←今ここ
- UIAutomationをWinActorに組み込む
前回なんとなくUI検査ツール使ってみたらAutomationIDってのを発見して、これはもしかしてUI自動化できるのか?という気配を感じた。
Inspectツールの上のプルダウンから、「MSAA」、「UI Automation」が選択できる。
どうもすこし調べた感じだとMSAAの方が昔っからある規格らしい。
「AutomationID」は、「UI Automation」の方を選択した時に取得できる。
と、いう訳でUI Automationというライブラリについていろいろ調べて実験した。
1..NETフレームワークから使えるようだ。
.NETフレームワークから、System.Windows.Automationというライブラリから使えるようだ。
.NETフレームワーク開発といえば、Visual Studioさん。
まず、参照設定を追加してUI Automationを使えるようにする。
- UIAutomationClient
- UIAutomationTypes
この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個のオプションを指定して起動する。
- 第一引数
- Click - クリックイベントを発火する(Invoke)
- Focus - 要素にフォーカスを移動する
- GetValue,SetValue - テキスト編集欄の取得設定(TextBox,直接編集可能なCombobox)
- GetSelection - Combobox, ListViewの選択項目が取得
- GetSelectionItemIsSelected - RadioButton, Combobox, ListViewの選択項目がIsSelectedかどうかを取得
- SetSelectionItemValue - RadioButton, Combobox, ListViewの選択項目を設定
- GetToggle,SetToggle ⇒ Checkboxの取得設定
- GetName - 要素のNameプロパティを標準出力に表示する
- 第二引数
- ウィンドウハンドルを指定(GetHWND:ウィンドウタイトルorプロセス名でも可)
- 第三引数
- Inspectツールで取得したName or AutomationIDを指定
(:で区切ってオプション指定することでNext・Previousにも対応)
- 第四引数(第一引数:Set~の場合のみ)
- 要素に設定した文字を指定
電卓の計算結果が「Text」ってなっててGetValueだと値とれなかったからGetNameを追加した。
でも、このまま結果取得すると「表示は●●です」って出てしまう。。。
でも、なんとなくこれをWinActorのスクリプト実行ステージに仕込めばイベントモードのWIN32では操作が座標指定になってしまう要素も操作できるんじゃないかという期待を込めて。
コンパイル済みファイルはこちら。