Blog,

Steuerung eines Programms mit grafischer Benutzeroberfläche


Automatisierung von Programmen mit grafischer Benutzeroberfläche (GUI) mit Python und pywinauto

Das Problem

Wer kennt das nicht? Man hat ein Gerät und dazu ein Windowsprogramm mit grafischer Benutzeroberfläche (GUI) mit dem sich das Gerät auch einfach bedienen lässt. Nun möchte man dies automatisieren, um z.B. eine Messung durchzuführen und das Ergebnis weiter zu verarbeiten. Es gibt aber keine Programmierschnittstelle (SDK). Was tun?

Eine Lösung

Mit der Python Bibliothek pywinauto können die meisten Programme mit GUI unter Windows gezielt angesteuert werden. Ich möchte dies am Beispiel des Windows 10 Rechner Programms vorführen (Es gibt hier auch ein Videobeipiel um Notepad zu automatiseren). Ich gehe hier davon aus, dass Python 3.x installiert ist. Dann installiert man das Modul pywinauto wie üblich mit pip.

pip install pywinauto

Was benötigt man noch? Man muss die Steuerelemente des Nutzerprogramms identifizieren. Das kann man zwar auch grundsätzlich mit pywinauto machen (Siehe auskommentierte Zeile im Beispielprogramm). Ich war aber froh als ich mit Inspect.exe mir die Elemente genauer anschauen konnte. Nur so konnte ich das eigentliche Ausgabeelement überhaupt finden. Das Programm Inspect.exe kommt als Bestandteil des Windows SDK mit. Man kann auch die Version auf dieser Seite verwenden. Das SDK ist aber der sicherere Weg.

Wir wollen nun im Folgenden die schwierige Aufgabe 5+7 in der Rechner App von Windows 10 berechnen und das Ergebnis auslesen. Das sieht in der App so aus:

e1e1bdc5646169740a05bbe9b5da1461.png

Man kann das Programm entweder manuell oder aus Python starten.

Das folgende Python Programm startet die App und erfüllt die Aufgabe. Wichtig ist, dass hier die Selektion des zu steuernden Fensters über den Titel des Fensters erfolgt. Hat man z.B. Englisch eingestellt, müsste man statt Rechner den englischen Titel eingeben.

import time
from pywinauto.application import Application

# Das Backend "uia" ist hier gewählt, da die App relativ neu ist

# Wenn man das Programm manuell gestartet hat 
# bitte die folgende Zeile einkommentieren
# app_start = Application(backend="uia")

# Die folgende Zeile startet das Proramm
# Bei manuellem start bitte auskommentieren
app_start = Application(backend="uia").start("calc.exe")

# Vor allem wenn man das Programm aus Python heraus startet,
# muss man darauf achten, dass der Start und der
# Verbindungsaufbau einige Zeit dauert.
success = False
while not success:
    try:
        app = app_start.connect(title="Rechner")
        success = True
    except Exception as err:
        time.sleep(0.1)

# die folgende Zeile würde einem eine Fensterliste erzeugen
# die Ausgabe mit child_window(...) verwendet man zur Selektion des Fensters
# app.Rechner.print_control_identifiers(depth=3)

# die folgenden Zeilen generieren Objekte die den Benutzer-
# elementen in der GUI entsprechen.
# Über diese Objekte erfolgt die Interaktion

but_c = app.Rechner.child_window(auto_id="clearButton", class_name="Button").wrapper_object()
but_5 = app.Rechner.child_window(auto_id="num5Button", class_name="Button").wrapper_object()
but_plus = app.Rechner.child_window(auto_id="plusButton", class_name="Button").wrapper_object()
but_7 = app.Rechner.child_window(auto_id="num7Button", class_name="Button").wrapper_object()
but_equal = app.Rechner.child_window(auto_id="equalButton", class_name="Button").wrapper_object()

# Wir löschen den Speicher und simulieren das Drücken von 5, +, 7 und =
but_c.click()
but_5.click()
but_plus.click()
but_7.click()
but_equal.click()

# Das Auslesen des Ergebnisses war etwas tricky.
# Das Tochterlement des Steuerelements "CalculatorResults"
# war nicht direkt in der children Liste.
# Daher wird der Generator verwendet
result = app.Rechner.child_window(auto_id="CalculatorResults").wrapper_object()
result_txt = [child for child in result.iter_children()][0]

# Nun können wir aus dem Steuerelement das Ergebnis als String auslesen
print(result_txt.texts()[0])

Die Selekton eines Steuerelementes ist nicht immer so einfach. Es gibt zwei Automatiserungs-Backends. Standardmässig ist win32 eingestellt. Die Identifizierung der Steuerelemente kann dort aber schwieriger sein. Beim Backend uia kann man meist das Element AutomationId verwenden. Es gibt zur Ausfilterung des richtigen Elementes jedoch mehr Möglichkeiten siehe hier

Der folgende Screenshot zeigt das Inspect.exe Fenster vor dem Fenster der Rechner App, um das Steuerlement mit dem C zum Löschen zu identifizieren.  

0a98e5c8e7cd76138d86de97034ea00d.png

Ich hoffe, dass Euch der obige Codeauschnitt einen einfachen Einstieg in die Automatisierung mit pywinauto unter Python ermöglicht.