1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

python-uiautomationでWindowsのGUIコントロールをlxml.etreeにマッピングしてXPathでいい感じに検索する

Last updated at Posted at 2025-08-24

nananek/utree - GitHub

コードもREADMEもGitHub Copilotに書かせているので、割と適当です。とりあえずやりたいことができそうなので、気が向くと開発が続行します。

やりたいこと

image.png

例えばこういうGUIがある。「スキャン画面を開く」ボタンを押すことを自動化したい。さてどうしよう?

言語はとりあえずPythonを使いたいものとする。

先行研究

1. PyAutoGUI

いちいちコードは示さないが、そのボタンのスクショを撮って、画像を保存しておき、今現在のデスクトップのスクショからパターンマッチでボタンを見つけ出して、マウスを移動してクリックする、という古典的なRPA手法がある。

PyAutoGUIならPyScreezeとOpenCVでいい感じにやってくれるとは思うけど、画像のスクショを保存しておく必要がある上、たまたまボタンが隠れたり、ボタンのスケールが変わったりした場合などに対するロバストネスが低すぎる。作業コスパが悪い。なし。

2. uiautomation

そこで、UI AutomationというゴリゴリのWindowsプログラミングフレームワークに乗っかることを検討しなければならない。

uiautomation - Trail4You

さいわい、とても有益なPythonでの先行事例を見つけることができた。特にダンプのところを見ればわかるとおり、UIは完全に木構造であることが見て取れる。

なら、木構造そのままダンプできるXMLっていう素晴らしいマークアップ形式がありますやんね?

やってみたこと

とりあえずHTMLでいい感じに見やすくダンプしてみよう!

とりあえず先人の知恵をもとにダンプを見やすくHTMLにマークアップしてもらいますか。

GeminiやGitHub Copilotなどにいい感じにコードを書いてもらって、こんな感じになりました。

image.png

たとえば、「ファイル形式」のコンボボックスを操作したいときは、 TextControl(Name="ファイル形式") をまず探して、その兄弟の直近の ConboBoxControl を探さないといけないわけです。
これを GetParentControl とか GetNextSiblingControl とかで頑張るのはちょっと嫌です。

UIをXPathで検索したいな…、じゃあUI構造をXMLに起こせばいいじゃん。

そこで、このUIの木構造をXMLで表現してしまえば、XPathで検索することもできるだろうよ、ってことになるわけです。

/WindowControl[@name="ScanSnap Home"]//ComboBoxControl[preceding-sibling::TextControl/@name="ファイル形式"] みたいなXPathが使えるようになると嬉しいわけですね。

XMLで表現するっていったって、実際にやることはlxml.etreeを組み立てるだけです。任意の要素から実際のコントロールにアクセスできるようUUIDを振って、dictで管理しておけばいいわけです。

というわけで、面倒な実装はGitHub Copilotに丸投げして、こんな感じの操作ができるようになりました。

from uitree import UITree

ui_tree = UITree(depth=1)

window = ui_tree.xpath('.//WindowControl[@name="ScanSnap Home"]')
window_tree = UITree(window[0])

# スキャン画面を開く ボタンを押す。
window_tree.xpath(".//ButtonControl[./TextControl/@name='スキャン画面を開く']")[0].control.Click()

# 操作対象ウインドウを切り替え
ui_tree = UITree(depth=1)
window = ui_tree.xpath('.//WindowControl[@name="ScanSnap Home - スキャン"]')
window_tree = UITree(window[0])

# ScanSnap Homeプロファイルを選択
item = window_tree.xpath('.//ListItemControl[@name="Pfu.ScanSnap.Home.UI.Sub.ViewModels.ProfileItemViewModel" and .//TextControl/@name="ScanSnap Home"]')
item[0].control.Click()

# 右クリックメニューを開く
target = item[0].control
target.SetFocus()
target.RightClick()

#window_tree.refresh()
#print(window_tree.dumpxml(), file=open('dump.xml', 'w', encoding='utf-8'))

もっと色々やってみたいところですが、気が向いたら。

っていうか、一晩明けて

GitHub Copilotに丸投げしたのでコードがひっどい。誰か直してほしい。私はUIAに早速飽きたのでモチベなし。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?