プログラムでおかえしできるかな

定年を過ぎて何かの役に立てないかなと始めた元SEのブログです

pywinautoでRPA(自動化)◇導入編【Python】

このエントリーをはてなブックマークに追加

pywinauto は Python で RPA を実現するパッケージです。

RPA でソフトの自動化をしてみませんか。

pywinauto を使い始める時に役に立つ情報を紹介します。

目次

◆RPAとpywinauto

◇RPAとは

RPAとは、ロボティック・プロセス・オートメーション(robotic process automation)の略で、ユーザーがアプリケーションのグラフィカルユーザーインターフェイスGUI)でそのタスクを実行する代わりに、ソフトウェアでタスクを GUI 上で直接繰り返すことで自動化する技術です。

◇pywinautoとは

pywinauto は、Microsoft Windows GUI を自動化するための python モジュールのセットです。
単純な使い方は、マウスとキーボードの操作をアプリに伝えることです。
それによってアプリの操作を自動化できます。

Python RPA関連ライブラリ

Python で使える RPA 関連ライブラリの一部を一覧にします。

ライブラリ 説明
pywinauto 今回選んだ RPA ツール。ブラウザの中身の自動化はできない
sikuli RPA ツール。MIT で開発。現在は RaiMan という方が sikulix として開発
画像マッチングで処理を行う。動作中の画像が必要
Automagica RPA ツール。内部で selenium を使用してブラウザの自動化も可
AGPL3 では使えなくなったとあるのでフリーではないと思われる
pyautogui マウスやキーボードを操作するGUI自動化。マウス操作には座標が必要

◇pywinautoを選んだ理由

個人的には、RPA を実現するために、座標を指定しなければならないもの、画像を事前に用意しなければならないものは、使いにくいと思っています。
それは、ウィンドウの大きさ、表示の仕方などをカスタマイズされた環境で動かすための対応が大変だと思うからです。

そういう意味で pywinauto を使うことにしました。

◆pywinautoの基礎

pywinauto で使われる用語の説明をします。

◇バックエンド

pywinauto がアプリと接続する手段

pywinauto は Windows でサポートされている二つのアクセスビリティ技術を使用しています。
これをバックエンドと呼んでいます。
要は、どういう技術でアプリに接続するかということです。

  • UI Automationベースbackend="uia"
    対象は、WinForms、WPF、ストア アプリ、Qt5、ブラウザーなど
    64ビットアプリケーション

  • Win32 APIベースbackend="win32"
    対象は、MFC、VB6、VCL、シンプルな WinForms コントロール、およびほとんどの古いレガシー
    32ビットアプリケーション

pywinauto は、アプリを制御する時に、どちらか一方で制御するため、対象のアプリにはどちらがいいか判断する必要があります。別途判断方法も示されています。1
最近の Windows 環境であれば、ほとんどの場合、「UI Automation ベース」で問題ないと思います。

◇エントリーポイント

pywinauto のアプリ制御オブジェクト

アプリケーションを制御するために使うオブジェクトを pywinauto ではエントリーポイントと呼んでいます。
2種類あります。どちらも使用にはインポートが必要です。

  • Applicationオブジェクト:プロセスをまたがない制御を行う

    • インポート:from pywinauto.application import Application
    • コンストラクタ:Application(backend="win32")
  • Desktopオブジェクト:プロセスをまたぐ制御を行える
    ※こちらはほとんど説明がありません。win10 の calc が対象らしい

    • インポート:from pywinauto import Desktop
    • コンストラクタ:Desktop(backend="win32")

コンストラクタにはバックエンドを引数で指定します。
省略した場合は win32 になります。

※これらのオブジェクトの使い分け方法は、まだ良く理解できていません。m(__)m
 とりあえず、対象となるのが一つのプロセスであれば Application オブジェクトを使用して問題ないようです。
 ただし、Desktop オブジェクトの場合、アプリを捜さなくて良い(connect が不要)ので一手間減らせます。

◇Applicationオブジェクトでは接続が必要

Application オブジェクトを使って対象のアプリを制御する場合、オブジェクトにプロセスを接続する必要があります。

プロセスを接続する方法は、アプリを起動する方法(start)と起動されているアプリを接続する方法(connect)があります。

※仕様に依れば、アプリを起動した場合、接続は不要なはずなのですが、提供されているサンプルプログラムでは接続も行っています。

  • 起動:start(cmd_line) メソッド

    • 主な引数
      • cmd_line:アプリを起動するコマンド
      • timeout=Noneタイムアウト時間(秒)
  • 接続:connect(**kwargs)メソッド

    • キーワード引数
      • process:プロセス番号
      • hamdle:ハンドル
      • path:起動しているアプリのパス
      • timeoutタイムアウト時間(秒)
      • find_elements() 2 で使えるキーワード引数も使えます(以下は一部)
        ※「find_elementsメソッドのキーワード引数」参照⤵
        • title:タイトル名
        • title_re:タイトル名を正規表現で指定
        • class_name:ウィンドウのクラス名

※Desktop オブジェクトを使用する時は不要です。

アプリの起動について アプリの起動もプログラム内で実施し、起動完了を待つことも可能です。
起動が早ければ問題なく使用できます。
起動が遅い場合、タイムアウトにどのくらいの時間を設定すればよいのかなど不確定要素が多いです。
この記事では、手動でアプリを起動した状態から動作させた内容を紹介しています。

◇ダイアログとコントロール

Application オブジェクト、または Desktop オブジェクトを作成したら(以下、app オブジェクト)、制御するウィンドウを指定して制御します。
pywinauto ではウィンドウをダイアログと呼んでいます。
ダイアログに配置されているボタンやテキストエディットボックスはコントロールと呼んでいます。

pywinauto ではダイアログとコントロールを使用してアプリを制御します。

  • ダイアログ
    いくつかの GUI 要素/コントロールを含むウィンドウ
    メインフォームもダイアログ
    WindowSpecification 型

  • コントロール
    階層の任意のレベルにある GUI 要素
    ウィンドウ、ボタン、編集ボックス、グリッド、グリッド セル、バーなど

これらは次のように使われます。

app.ダイアログ名.コントロール名.メソッド
または
app["ダイアログ名"]["コントロール名"].メソッド

これは基本形であって、ダイアログの下に更にダイアログがあることも、コントロールの下に更にコントロールがあることもあります。
それらは「.」または「[]」でつないでアクセスできます。

この仕組みによってオブジェクト指向的な記述を実現しています。他のパッケージにはない特徴です。

◇ダイアログの指定方法

ダイアログはダイアログの名前で指定します。
指定する名前はダイアログ(ウィンドウ)のタイトルに基づいて決まります。

指定の仕方も複数あります。
以下はメモ帳の場合の例です。

  • app.メモ帳:「.」でつないて指定
  • app['メモ帳']:辞書のインデックスのように指定
  • app.window(title_re=".*メモ帳")window()メソッドで指定
  • `app.top_window():最上位のダイアログ
    ※Application オブジェクトの場合、ダイアログが一つしか見つからないのでこれで十分

◎ダイアログ名の調査方法サンプル

ダイアログ名を調べるには、Desltop オブジェクトの windows() メソッドを使用します。

from pywinauto import Desktop
app = Desktop(backend="uia")
    
for win in app.windows():
    print(win)

▼結果

uiawrapper.UIAWrapper - '', Pane
uiawrapper.UIAWrapper - '無題 - メモ帳', Dialog
uiawrapper.UIAWrapper - '電卓', Dialog
uiawrapper.UIAWrapper - 'Program Manager', Pane

ハイフンの後がダイアログのタイトルです。
すべて指定(「無題 - メモ帳」)するか、アプリ名の部分(「メモ帳」)を指定して使います。

※バックエンドが uia の場合の出力例です。

◇コントロールの指定方法

通常、コントロールにもタイトルが付いています。
そのタイトルに基づいてコントロールを指定します。
ダイアログの指定と要領は同じです。

  • app.dlg.control
  • app['dlg']['control']
    言語が英語以外はこちらを推奨しています
    指定する文字列の先頭が数字だったり、空白が含まれる場合はこちらを使います

◎コントロール名の調査方法サンプル①

コントロール名を調べるには、print_control_identifiers()メソッドを使います。

ダイアログオブジェクトのメソッドで、ダイアログに属するコントロールの情報を取得できます。
ここで出力される要素の [] で挟まれた文字列は、要素の名前解決に使えます。

  • 【構文】print_control_identifiers(depth=None, filename=None)
    自身と子孫の「識別子」を出力します
  • 引数
    • depth=None:深さ
    • filename=None:出力ファイル名

▼サンプル

from pywinauto import Desktop
app = Desktop(backend="uia")

app.電卓.print_control_identifiers()

▼結果の一部

Control Identifiers:

Dialog - '電卓'    (L918, T259, R1146, B596)
['電卓Dialog', 'Dialog', '電卓']
child_window(title="電卓", control_type="Window")
   |
   | Pane - ''    (L926, T310, R1138, B588)
   | ['実行履歴Pane', 'Pane']
   |    |
   |    | Image - ''    (L937, T320, R1127, B383)
   |    | ['実行履歴Image', 'Image']
   |    | child_window(auto_id="160", control_type="Image")
   |    |

▼結果で電卓のボタンが示されている部分

  |    | Button - '3'    (L1313, T904, R1347, B932)
  |    | ['3', '3Button', 'Button16']
  |    | child_window(title="3", auto_id="133", control_type="Button")
  |    |
  |    | Button - '小数点'    (L1313, T935, R1347, B963)
  |    | ['小数点', '小数点Button', 'Button17']
  |    | child_window(title="小数点", auto_id="84", control_type="Button")

コントロールを指定する名前は、[] で囲まれた部分の要素のいずれかです。

app.電卓.小数点.click() と言う使い方はすぐわかると思います。
app.電卓.Button16.click() も同じです。

名前は、他のコントロールと重複しなければ一部でも動作します。

例えば、app.電卓.小数.click() でも動作します。
また、他のコントロールと重複している場合、末尾に数字を付けて区別することもできます。

◎コントロール名の調査方法サンプル②

コントロール名を調べるもう一つの方法は、descendants()メソッドを使います。
こちらの方が子孫までわかるので便利です。
また、出力が簡潔なので使いやすいかもしれません。

>>> pprint(app.電卓.descendants())
# 結果
[<uiawrapper.UIAWrapper - '', Pane, -2088469484646182407>,
 <uiawrapper.UIAWrapper - '', Image, 7796650432385265154>,
 <uia_controls.StaticWrapper - '実行履歴', Static, 35394252131897040>,
 <uia_controls.StaticWrapper - 'メモリ', Static, 3850211947032841757>,
 <uia_controls.StaticWrapper - '結果', Static, 8657429938950635689>,
 <uia_controls.StaticWrapper - '0', Static, 8269514721375299420>,
 <uia_controls.ButtonWrapper - 'メモリのクリア', Button, -4529645659650611549>,
 <uia_controls.ButtonWrapper - 'バックスペース', Button, -8113700530651832004>,
 <uia_controls.ButtonWrapper - '7', Button, -6943344216624096333>,
 <uia_controls.ButtonWrapper - '4', Button, -2660938956445707959>,
 <uia_controls.ButtonWrapper - '1', Button, 1528378795935228914>,
 <uia_controls.ButtonWrapper - '0', Button, 7299455821206303324>,
 <uia_controls.ButtonWrapper - 'メモリ呼び出し', Button, -6287521407086437232>,
 <uia_controls.ButtonWrapper - '入力のクリア', Button, -9003876290891478422>,
 <uia_controls.ButtonWrapper - '8', Button, -8074387621959237623>,
 <uia_controls.ButtonWrapper - '5', Button, 6520457217722033563>,
 <uia_controls.ButtonWrapper - '2', Button, 4674839885406608361>,
 <uia_controls.ButtonWrapper - 'メモリ保存', Button, -5063970690761171022>,
 <uia_controls.ButtonWrapper - 'クリア', Button, 3131611487579494108>,
 <uia_controls.ButtonWrapper - '9', Button, -4935735793161818816>,
 <uia_controls.ButtonWrapper - '6', Button, -1701319644920625573>,
 <uia_controls.ButtonWrapper - '3', Button, -3882827725024147606>,
 <uia_controls.ButtonWrapper - '小数点', Button, 7612437574428478661>,
 <uia_controls.ButtonWrapper - 'メモリ加算', Button, 2444006862691061333>,
 <uia_controls.ButtonWrapper - '符号切り替え', Button, -1395719328092426716>,
 <uia_controls.ButtonWrapper - '除算', Button, 1458373243187541835>,
 <uia_controls.ButtonWrapper - '乗算', Button, -8173468047042769967>,
 <uia_controls.ButtonWrapper - '減算', Button, -996806363108533141>,
 <uia_controls.ButtonWrapper - '加算', Button, 6952234307603248808>,
 <uia_controls.ButtonWrapper - 'メモリ減算', Button, 74884552013049057>,
 <uia_controls.ButtonWrapper - '平方根', Button, 5184136895055385749>,
 <uia_controls.ButtonWrapper - 'パーセント', Button, -6595684676779691516>,
 <uia_controls.ButtonWrapper - '逆数', Button, 6314855933917097163>,
 <uia_controls.ButtonWrapper - '等号', Button, 5235926999523883132>,
 <uiawrapper.UIAWrapper - '', TitleBar, -2118714046004290876>,
 <uia_controls.MenuWrapper - 'システム', Menu, 2069725647282648299>,
 <uia_controls.MenuItemWrapper - 'システム', MenuItem, -3050192662478732254>,
 <uia_controls.ButtonWrapper - '最小化', Button, -4902504701859356945>,
 <uia_controls.ButtonWrapper - '最大化', Button, 1605136054242641480>,
 <uia_controls.ButtonWrapper - '閉じる', Button, 3219702781371943539>,
 <uia_controls.MenuWrapper - 'アプリケーション', Menu, 4137060108454581790>,
 <uia_controls.MenuItemWrapper - '表示(V)', MenuItem, 1664813704343689934>,
 <uia_controls.MenuItemWrapper - '編集(E)', MenuItem, 8172454460445688359>,
 <uia_controls.MenuItemWrapper - 'ヘルプ(H)', MenuItem, 3279380431472991993>]

※ここに出てくる名称はそのままダイアログの要素として使えるようです。
 例えば、app.電卓.小数点 追加:2023-02-07

◎コントロール名の調査方法サンプル③

コントロール名を調べるもう一つの方法は、children()メソッドを使います。
アプリケーションの構成によっては一度で確認できない場合もある点が使いずらいかもしれません。
こちらも出力は簡潔です。

▼電卓ダイアログの結果

コマンド:pprint.pprint(app.電卓.children())

>>> pprint.pprint(app.電卓.children())
[<uiawrapper.UIAWrapper - '', Pane, 7399452172566593794>,
 <uiawrapper.UIAWrapper - '', TitleBar, -2575571051873893012>,
 <uia_controls.MenuWrapper - 'アプリケーション', Menu, -1446670289958555039>]

※pprint は出力を見やすくするために使用

コントロール名の調査方法サンプル②」の結果にあるように、Pane の下にはコントロールが存在しているのでさらに調べます。

▼電卓.Pane エレメントの結果

コマンド:pprint.pprint(app.電卓.Pane.children())

>>> pprint.pprint(app.電卓.Pane.children())
[<uiawrapper.UIAWrapper - '', Image, -2214242205524061483>,
 <uia_controls.StaticWrapper - '実行履歴', Static, 6675571042244367833>,
 <uia_controls.StaticWrapper - 'メモリ', Static, -7941291026824673679>,
 <uia_controls.StaticWrapper - '結果', Static, 7982315895947818398>,
 <uia_controls.StaticWrapper - '0', Static, 383330798273806262>,
 <uia_controls.ButtonWrapper - 'メモリのクリア', Button, 5311632400003929208>,
 <uia_controls.ButtonWrapper - 'バックスペース', Button, -4210365671060142192>,
 <uia_controls.ButtonWrapper - '7', Button, 7664468602492823808>,
 <uia_controls.ButtonWrapper - '4', Button, -2054698080888606086>,
 <uia_controls.ButtonWrapper - '1', Button, -8764128806022878257>,
 <uia_controls.ButtonWrapper - '0', Button, -966843871764171494>,
 <uia_controls.ButtonWrapper - 'メモリ呼び出し', Button, -7594904877297114247>,
 <uia_controls.ButtonWrapper - '入力のクリア', Button, 2650974324089183114>,
 <uia_controls.ButtonWrapper - '8', Button, -6273771678556297102>,
 <uia_controls.ButtonWrapper - '5', Button, -3613113602641551008>,
 <uia_controls.ButtonWrapper - '2', Button, -2595798943930840133>,
 <uia_controls.ButtonWrapper - 'メモリ保存', Button, -5227680329770853067>,
 <uia_controls.ButtonWrapper - 'クリア', Button, -5664495650813229429>,
 <uia_controls.ButtonWrapper - '9', Button, 7507797449064852787>,
 <uia_controls.ButtonWrapper - '6', Button, -5046634782585055540>,
 <uia_controls.ButtonWrapper - '3', Button, 5296267884889540903>,
 <uia_controls.ButtonWrapper - '小数点', Button, 8328702045475377830>,
 <uia_controls.ButtonWrapper - 'メモリ加算', Button, -7580516532259747667>,
 <uia_controls.ButtonWrapper - '符号切り替え', Button, 8499002993923542769>,
 <uia_controls.ButtonWrapper - '除算', Button, 5174111475026242653>,
 <uia_controls.ButtonWrapper - '乗算', Button, -1423547851886352624>,
 <uia_controls.ButtonWrapper - '減算', Button, -5071767726360054708>,
 <uia_controls.ButtonWrapper - '加算', Button, -4796725876404681598>,
 <uia_controls.ButtonWrapper - 'メモリ減算', Button, 5188499820063609233>,
 <uia_controls.ButtonWrapper - '平方根', Button, -6411292603533983657>,
 <uia_controls.ButtonWrapper - 'パーセント', Button, 7801989527470510363>,
 <uia_controls.ButtonWrapper - '逆数', Button, -8118590231983258215>,
 <uia_controls.ButtonWrapper - '等号', Button, -6815848711675552874>]

※Pane は省略する必要があるようです。(経験的な見解)
 例えば、app.電卓.Pane.小数点.click()app.電卓.小数点.click()app.電卓["小数点"].click() は同じ動作をします。
 しかし、app.電卓.Pane["小数点"].click() はエラーになります。

◇find_elementsメソッドのキーワード引数

  • class_name=None
  • class_name_re=None
  • parent=None
  • process=None
  • title=None
  • title_re=None
  • top_level_only=True
  • visible_only=True
  • enabled_only=False
  • best_match=None
  • handle=None
  • ctrl_index=None
  • found_index=None
  • predicate_func=None
  • active_only=False
  • control_id=None
  • control_type=None
  • auto_id=None
  • framework_id=None
  • backend=None
  • depth=None

◆コントロールで使えるメソッド

コントロールで使えるメソッドを列挙します。

◇すべてのコントロール

  • capture_as_image
  • click
  • click_input
  • close
  • close_click
  • debug_message
  • double_click
  • double_click_input
  • drag_mouse
  • draw_outline
  • get_focus
  • get_show_state
  • maximize
  • menu_select
  • minimize
  • move_mouse
  • move_window
  • notify_menu_select
  • notify_parent
  • press_mouse
  • press_mouse_input
  • release_mouse
  • release_mouse_input
  • restore
  • right_click
  • right_click_input
  • send_message
  • send_message_timeout
  • set_focus
  • set_window_text
  • type_keys
  • Children
  • Class
  • ClientRect
  • ClientRects
  • ContextHelpID
  • ControlID
  • ExStyle
  • Font
  • Fonts
  • FriendlyClassName
  • GetProperties
  • HasExStyle
  • HasStyle
  • IsChild
  • IsDialog
  • IsEnabled
  • IsUnicode
  • IsVisible
  • Menu
  • MenuItem
  • MenuItems
  • Owner
  • Parent
  • PopupWindow
  • ProcessID
  • Rectangle
  • Style
  • Texts
  • TopLevelParent
  • UserData
  • VerifyActionable
  • VerifyEnabled
  • VerifyVisible
  • WindowText

主なメソッドを説明します。

メニューから選択します。

  • 【構文】menu_select(path, exact=False)
  • 主な引数
    • path:メニューのパス
      パスは、「->」で区切られたアイテムのリストによって指定します
      例:"表示 -> 普通の電卓"

◇Button, CheckBox, RadioButton, GroupBox

  • Check
  • GetCheckState
  • SetCheckIndeterminate
  • UnCheck

◇ComboBox

  • DroppedRect
  • ItemCount
  • ItemData
  • ItemTexts
  • Select
  • SelectedIndex

主なメソッドを説明します。

select()

コンボボックスから選択します。

  • 【構文】select(item)
  • 主な引数
    • item:選択する項目の 0 ベースのインデックス、または選択する文字列のいずれか

◇その他

こちらのリンクを参照:Methods available to each different control type — pywinauto 0.6.8 documentation

◆待機

RPA をうまく動作させるには待機を使いこなせることが肝要です。
タイムアウトした時の処理を用意しておくことも大切ですが、まずは、適切な待機を組み込んで処理が中断しないようにします。

pywinauto ではダイアログやコントロールが見つかるまでにデフォルトで5秒のタイムアウトを設けています。
5秒では短いと想定される処理では、メソッドに timeout 引数を加えたり、wait() メソッドで別途待機します。

◇ダイアログの待機

例えば、アプリケーションのメニューからファイル選択ダイアログを開くとします。
ファイル選択ダイアログは、フォルダの情報なども取得して表示するので、ただ画面を作るだけのダイアログより、表示に時間が掛かります。
そのような場合に wait() メソッドを使用してタイムアウト時間を長く設定します。

◎WindowSpecificationオブジェクトの待機メソッド

  • wait(wait_for, timeout=None, retry_interval=None)
    wait_for 状態になるまで待機

    • wait_for:この状態になるまで。空白で連結可能
      • exists:「存在する」:ウィンドウが有効なハンドルである
      • visible:「表示」:ウィンドウが表示されている
      • enabled:「有効」:ウィンドウが有効
      • ready(visible and enabled):「準備完了」:ウィンドウが表示され、有効
      • active:「アクティブ」:ウィンドウがアクティブ
    • timeoutタイムアウト秒(デフォルト5秒)
    • retry_interval:リトライする場合の間隔
  • wait_not(wait_for, timeout=None, retry_interval=None)
    wait_for状態ではなくなるまで待機
    引数は同上

◇グローバルタイミング

pywinauto には、多くの処理で使用するタイムアウト用のグローバルな静的変数が timings.Timeins オブジェクトに用意されています。
また、すべてのグローバルタイミングを一度に設定するメソッドがあります。

◎グローバルタイミング変更メソッド

  • timings.Timings.defaults():デフォルトにする

  • timings.Timings.fast():早くする

    • _timeout で終わる変数:1
      ※個別に指定した設定値が1未満ならそちらが有効
    • _wait で終わる変数:1/2倍
    • _retry で終わる変数:0.001
    • その他:そのまま
  • timings.Timings.slow():遅くする
    ※個別に指定した設定値より小さくはなりません

    • _timeout で終わる変数:10倍
    • _wait で終わる変数:3倍
    • _retry で終わる変数:3倍
    • その他:2倍

◎主なグローバルタイミングとデフォルト値 3

 ※これらは個別にも変えられます(メソッドの引数を使わなくて済む)
VS Codeデバッグではうまく動作したけれど、バッチでは動作しませんでした。
  変数の参照の仕方の問題と推測しますが、未調査です。

  • window_find_timeout (default 5)::WindowSpecificationオブジェクトのwait()で使用
  • app_start_timeout (default 10):appオブジェクトのstart()で使用
  • app_connect_timeout (default 5):appオブジェクトのconnect()で使用
  • exists_timeout (default 0.5):WindowSpecificationオブジェクトのexists()で使用

◆サンプル

こちらにサンプルプログラムを掲載します。

これらのサンプルは Windows7 上で動かしています。
その他の Windows 上で動作しない場合はご容赦ください。

◇電卓

電卓で足し算をするサンプルです。
メニューで表示の変更もしています。
2回目の表示変更はコントロールから制御しています。命令は動作するものですが、タイムアウトエラーになってしまいます。原因は調査中です。

from pywinauto import Desktop
app = Desktop(backend="uia")
dlg = app.電卓
# メニューの制御
dlg.menu_select("表示 -> 普通の電卓")
# 計算
dlg['1'].click()
dlg.加算.click()
dlg['2'].click()
dlg.等号.click()
dlg.menu2.item_by_path("表示->関数電卓").select() # これがエラーになる

◇メモ帳

メモ帳で名前を付けて保存するサンプルです。
文字コードを「UTF-8」に変更して保存します。
 (実は、ここではまってしまいました😢)

from pywinauto.application import Application
import pprint

app = Application(backend="uia")
app.connect(title_re=".*メモ帳")
dlg = app.メモ帳

dlg.menu_select("ファイル->名前を付けて保存")
dlg2 = dlg.名前を付けて保存
# pprint.pprint(dlg2.children())
ctrl = dlg2.文字コード2
ctrl.wait('ready')
# ctrl.expand() # IndexError のワークアラウンドみたいだけど、うまくいかない
# ctrl.ListBox.wait('ready')
try:
    ctrl.select('UTF-8')
except IndexError:
    pass

# 名前を付けて保存ダイアログ⇒Pane⇒ファイル名ComboBox⇒Editコントロール
dlg2.ファイル名2.Edit.set_text("Example-utf8.txt")
dlg2.保存.click()

◎ComboBoxのselect()メソッドのIndexError

上記のサンプルコードで例外処理を入れているように、select() メソッドを実行すると選択自体はできているのにインデックスエラーが出ます。
ワークアラウンドでコンボボックスを開いて実行すれば回避できるとあったのですが、うまくいきませんでした。
コンボボックスの構成を調べてリストボックスを対象に select() すればと思ったのですが、これもうまくいきませんでした。
仕方なく例外処理を入れました。4

◎メモ帳のコントロール名を調べる

▼起動した状態のメモ帳の調査

コマンド:pprint.pprint(app.メモ帳.children())

>>> pprint.pprint(app.メモ帳.children())
[<uia_controls.EditWrapper - '', Edit, 4631978040804330797>,
 <uiawrapper.UIAWrapper - '', TitleBar, -6457851064542665454>,
 <uia_controls.MenuWrapper - 'アプリケーション', Menu, -3120937130976757868>]

この状態では名前を付けて保存ダイアログの情報が見えません。

▼名前を付けて保存ダイアログを表示した状態のメモ帳の調査

コマンド:pprint.pprint(app.メモ帳.children())

>>> pprint.pprint(app.メモ帳.children())
[<uiawrapper.UIAWrapper - '名前を付けて保存', Dialog, -6136431129902167441>,
 <uia_controls.EditWrapper - '', Edit, 4631978040804330797>,
 <uiawrapper.UIAWrapper - '', TitleBar, -6457851064542665454>,
 <uia_controls.MenuWrapper - 'アプリケーション', Menu, -3120937130976757868>]

名前を付けて保存ダイアログを開いた状態であれば、その情報が取得できます。
その状態でさらに子供の情報を取得します。

▼名前を付けて保存ダイアログを表示した状態の保存ダイアログの調査

コマンド:pprint.pprint(app.メモ帳.名前を付けて保存.children())

>>> pprint.pprint(app.メモ帳.名前を付けて保存.children())
[<uiawrapper.UIAWrapper - '', Pane, 3175986485059829293>,
 <uia_controls.ToolbarWrapper - '', Toolbar, 8716193281468337454>,
 <uia_controls.StaticWrapper - '文字コード(E):', Static, -3452074520473113460>,
 <uia_controls.ComboBoxWrapper - '文字コード(E):', ComboBox, -4621298449198877470>,
 <uia_controls.ButtonWrapper - '保存(S)', Button, 6069923550590957940>,
 <uia_controls.ButtonWrapper - 'キャンセル', Button, -1562465943388438291>,
 <uiawrapper.UIAWrapper - '', Thumb, -1837507793343811401>,
 <uiawrapper.UIAWrapper - '', Pane, 8900593343581287614>,
 <uiawrapper.UIAWrapper - '', TitleBar, 8526681594217739331>]

▼名前を付けて保存ダイアログを表示した状態の保存ダイアログの文字コードの選択肢の調査

コマンド:app.メモ帳.名前を付けて保存.文字コード2.expand();app.メモ帳.名前を付けて保存 .文字コード2.print_control_identifiers()

※「文字コード」という名前が2つあるので「2」を付加してコントロールを指定します。
文字コードリストボックスを開いた状態でないと取得できないので、開きます。
 ただし、手動では開きっぱなしにならないのでコマンドで開きます。

結果的にここまでの調査は不要でしたが、調査のために工夫したので掲載します。

>>> app.メモ帳.名前を付けて保存.文字コード2.expand();app.メモ帳.名前を付けて保存
.文字コード2.print_control_identifiers()
<uia_controls.ComboBoxWrapper - '文字コード(E):', ComboBox, -4621298449198877470
>
Control Identifiers:

ComboBox - '文字コード(E):'    (L1396, T655, R1546, B681)
['文字コード(E):', '文字コード(E):ComboBox', 'ComboBox', '文字コード(E):0', '文
字コード(E):1']
child_window(title="文字コード(E):", control_type="ComboBox")
   |
   | ListBox - '文字コード(E):'    (L1396, T681, R1546, B755)
   | ['文字コード(E):2', 'ListBox', '文字コード(E):ListBox']
   | child_window(title="文字コード(E):", control_type="List")
   |    |
   |    | ListItem - 'ANSI'    (L1397, T682, R1545, B700)
   |    | ['ANSIListItem', 'ANSI', 'ListItem', 'ListItem0', 'ListItem1']
   |    | child_window(title="ANSI", control_type="ListItem")
   |    |
   |    | ListItem - 'Unicode'    (L1397, T700, R1545, B718)
   |    | ['UnicodeListItem', 'ListItem2', 'Unicode']
   |    | child_window(title="Unicode", control_type="ListItem")
   |    |
   |    | ListItem - 'Unicode big endian'    (L1397, T718, R1545, B736)
   |    | ['Unicode big endian', 'Unicode big endianListItem', 'ListItem3']
   |    | child_window(title="Unicode big endian", control_type="ListItem")
   |    |
   |    | ListItem - 'UTF-8'    (L1397, T736, R1545, B754)
   |    | ['UTF-8', 'UTF-8ListItem', 'ListItem4']
   |    | child_window(title="UTF-8", control_type="ListItem")
   |
   | Static - '文字コード(E):'    (L3, T3, R130, B23)
   | ['文字コード(E):3', '文字コード(E):Static', 'Static']
   | child_window(title="文字コード(E):", control_type="Text")
   |
   | Button - '閉じる'    (L1530, T656, R1545, B680)
   | ['閉じるButton', '閉じる', 'Button']
   | child_window(title="閉じる", auto_id="DropDown", control_type="Button")

◆さいごに

pywinauto の使い始め方をまとめてみました。
pywinauto は、ダイアログやコントロールを名前で指定できて魅力的なのですが、どこからどういう名前でアクセスできるのかが分かりにくいです。
それを解決できるような記事にしたつもりです。
この記事で pywinauto へのハードルが下がると嬉しいです。

Windows の RPA の走りのようなアプリに UWSC があります。
仕事でテストの自動化をする時に使用しました。
個人的には、使いやすくて良いアプリだと思います。

開発者の方はすでに亡くなられてしまったようです。

優秀なアプリケーションなのに技術の伝承ができず、もったいない限りです。

あわせて読みたい 📖 音声入力で文章作成するアプリの作り方【Python】 🔗
📖 pywinautoでRPA(自動化)◇ブラウザ編【Python】 🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

  • Python 3.8.5
  • pywinauto 0.6.8

◇免責事項

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

◆参考

投稿: 、更新:

  1. バックエンドの判断方法:GUI Objects Inspection / Spy - ToolsGetting Started Guide — pywinauto 0.6.8 documentation
  2. find_elementsメソッド:pywinauto.findwindows — pywinauto 0.6.8 documentation
  3. グローバルタイミング:pywinauto.timings — pywinauto 0.6.8 documentation
  4. 個人的にはパッケージのバクだと思います。何もアクションしていませんが・・・