Dies ist eine andere Herangehensweise zur Problematik des andere Programme fernsteuern-Threads von @RodFromGermany, basiert auf den Infos vom Thread von Hourmin und beantwortet meine einst gestellte Frage über das Auslesen anderer .Net-Anwendungen.
Mit der hier beschriebenen Vorgehensweise können externe .Net-Programme (größtenteils) ausgelesen und auch beeinflusst werden.
Anmerkung für diesen Text: CE = control element (Button, TextBox, DGV, …)
Einschränkungen: Dieses Vorgehen funktioniert nicht bei allen propriäteren WinForms-CEs, da diese explizit solche Automatisierungen unterstützen müssen. Bei einigen Anwendungen habe ich schon Konstrukte gesehen, die einfach eine Zusammenarbeit verweigern. Dafür habe ich noch keine Lösung gefunden. Bei WPF-Anwendungen ist die Automatisierungsunterstützung wohl schon nativ bei den CEs eingebaut, aber da bin ich momentan noch raus, dies zu prüfen.
Als Vorbereitung werden über die Verweise die folgenden DLLs in das Projekt eingebunden: UIAutomationClient, UIAutomationTypes
Das Prinzip der Automatisierung hier ist, dass man sich die passende Anwendung und dort wiederum das passende CE raussucht, welches man dann beeinflussen oder auslesen will. Da bei der hiesigen Art der Automatisierung das Denkprinzip so ist, dass alle Objekte Subelemente von anderen Elementen sind, beginnt man mit dem Stamm-/Wurzelelement, was alles andere beinhaltet. Dies ist definitionsgemäß laut Microsoft für diese Art von Automatisierung der Desktop, dargestellt durch
Zum Durchsuchen eines AutomationElements gibt es zwei Funktionen:
Will man alle laufenden Fenster/Programme auflisten, lässt man alle Kinder des Desktops suchen:
Würde man hier stattdessen als Scope
Alternativ kann man auch zur Programmsuche die
Nachdem wir uns nun unsere TargetApp mithilfe von
herausgesucht haben, können wir die App nun wiederum durchsuchen, denn
Nun wäre es meist erstmal sinnvoll, alle CEs aufzusammeln, wenn man noch nicht genau weiß, welches CE man jetzt manipulieren will:
Diese kann man nun durchforsten und sich das passende CE raussuchen und auslesen. Da kann man sich dann für spätere Automatisierung oder Manipulation die AutomationID des Ziel-CEs merken und später gezielt nach dieser suchen lassen.
Man kann natürlich auch zahlreiche Einschränkungen bei der Suche machen, indem man als 2. Parameter der Find-Funktionen eine PropertyCondition angibt, bei der man dann viele Möglichkeiten hat. Will man zum Beispiel das 1. CE haben, welches die AutomationID 15 hat, schreibt man:
(Die AutomationID ist vom Typ String!)
Will man alle CEs haben, die alte Menühauptpunkte sind, wie im Editor, schreibt man
Alternativ kann man sich auch einfach alle CEs geben lassen und diese selber mit entsprechenden IF-Statements filtern.
Sobald man sich ein CE rausgesucht hat, will man damit natürlich auch was damit machen. Die einfachsten Sachen sind z.B. einen Button drücken oder einen TextBox-Text auslesen. Dazu bedient man sich der sogenannten Patterns (Muster). Ein Button unterstützt z.B. das
Das InvokePattern nimmt man her, um einen Button zu klicken:
Hat man eine TextBox und braucht dessen Text, nimmt man das TextPattern:
Das für den Anfang. Kleine Vereinfachungen für die Patterns, für die FindAll-Funktion und das zurück-zum-Elternteil-Navigieren kommen nach der Freischaltung.
Mit der hier beschriebenen Vorgehensweise können externe .Net-Programme (größtenteils) ausgelesen und auch beeinflusst werden.
Anmerkung für diesen Text: CE = control element (Button, TextBox, DGV, …)
Einschränkungen: Dieses Vorgehen funktioniert nicht bei allen propriäteren WinForms-CEs, da diese explizit solche Automatisierungen unterstützen müssen. Bei einigen Anwendungen habe ich schon Konstrukte gesehen, die einfach eine Zusammenarbeit verweigern. Dafür habe ich noch keine Lösung gefunden. Bei WPF-Anwendungen ist die Automatisierungsunterstützung wohl schon nativ bei den CEs eingebaut, aber da bin ich momentan noch raus, dies zu prüfen.
Als Vorbereitung werden über die Verweise die folgenden DLLs in das Projekt eingebunden: UIAutomationClient, UIAutomationTypes
Das Prinzip der Automatisierung hier ist, dass man sich die passende Anwendung und dort wiederum das passende CE raussucht, welches man dann beeinflussen oder auslesen will. Da bei der hiesigen Art der Automatisierung das Denkprinzip so ist, dass alle Objekte Subelemente von anderen Elementen sind, beginnt man mit dem Stamm-/Wurzelelement, was alles andere beinhaltet. Dies ist definitionsgemäß laut Microsoft für diese Art von Automatisierung der Desktop, dargestellt durch
AutomationElement.RootElement
. (Wenn man (Sub-)Elemente durch »Fenster« ersetzt, wird wieder ein Windows-Schuh draus.)Zum Durchsuchen eines AutomationElements gibt es zwei Funktionen:
FindFirst
und FindAll
. FindFirst
wird immer dann verwendet, wenn man weiß, wonach man suchen muss. Man erhält als Resultat das passende AutomationElement
(oder Nothing
). FindAll
wird hingegen genutzt, wenn man sich erstmal alles Passende auflisten lassen will, logischerweise erhält man eine AutomationElementCollection
. Desweiteren muss man einen Scope, also einen Bereich angeben, den man durchsuchen will. Da der Desktop ganz oben in der Nahrungskette steht, kann man nur dessen Kinder, Nachkommen und sich selbst suchen. Alles andere endet in einer Exception.Will man alle laufenden Fenster/Programme auflisten, lässt man alle Kinder des Desktops suchen:
Würde man hier stattdessen als Scope
TreeScope.Descendants
angeben, würde man alle Fenster und all deren CEs erhalten. Dauert etwas und ist meist überflüssig. Man will ja ein Programm in die Zange nehmen, nicht alle. Der Descendants-Scope wird nachher relevant, wenn wir unser Programm haben und da alle CEs auflisten wollen.FindFirst
können wir natürlich auch verwenden. Dazu müssen wir die ein oder andere Bedingung angeben, die zutreffen soll. Dazu gibt es neben dem Scope-Parameter den 2. Parameter. Eine Möglichkeit unter vielen ist, dass man den Titelleistentext des Zielelements angibt: Alternativ kann man auch zur Programmsuche die
AutomationElement.ClassNameProperty
nehmen. Während allerdings bei Notepad dies noch »Notepad« ist, ist es bei Word 2016 bei mir schon »OpusApp«. Also leider nicht sonderlich instinktiv. Ein Zugriff auf den Dateinamen ist verständlicherweise nicht (direkt) möglich, da es hier um Fensterobjekte geht, nicht um Programmobjekte. Über die ProcessID etc. kommt man aber über Umwege auch an Dateipfad und Co.Nachdem wir uns nun unsere TargetApp mithilfe von
TargetApp
ist ja auch wieder vom Typ AutomationElement
. Nutzen wir hingegen die FindAll
-Funktion, können wir die AutomationElementCollection
auch nach unserer ZielApp durchforsten: Nun wäre es meist erstmal sinnvoll, alle CEs aufzusammeln, wenn man noch nicht genau weiß, welches CE man jetzt manipulieren will:
Diese kann man nun durchforsten und sich das passende CE raussuchen und auslesen. Da kann man sich dann für spätere Automatisierung oder Manipulation die AutomationID des Ziel-CEs merken und später gezielt nach dieser suchen lassen.
Man kann natürlich auch zahlreiche Einschränkungen bei der Suche machen, indem man als 2. Parameter der Find-Funktionen eine PropertyCondition angibt, bei der man dann viele Möglichkeiten hat. Will man zum Beispiel das 1. CE haben, welches die AutomationID 15 hat, schreibt man:
(Die AutomationID ist vom Typ String!)
Will man alle CEs haben, die alte Menühauptpunkte sind, wie im Editor, schreibt man
Alternativ kann man sich auch einfach alle CEs geben lassen und diese selber mit entsprechenden IF-Statements filtern.
Sobald man sich ein CE rausgesucht hat, will man damit natürlich auch was damit machen. Die einfachsten Sachen sind z.B. einen Button drücken oder einen TextBox-Text auslesen. Dazu bedient man sich der sogenannten Patterns (Muster). Ein Button unterstützt z.B. das
InvokePattern
, eine TextBox das TextPattern
. Um herauszufinden, welche Muster ein CE unterstützt, nutzt man die Funktion GetSupportedPatterns
:Das InvokePattern nimmt man her, um einen Button zu klicken:
Hat man eine TextBox und braucht dessen Text, nimmt man das TextPattern:
Das für den Anfang. Kleine Vereinfachungen für die Patterns, für die FindAll-Funktion und das zurück-zum-Elternteil-Navigieren kommen nach der Freischaltung.
Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.
Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.