Andere Programme fernsteuern

    • C#

    Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

      Andere Programme fernsteuern

      Unter Fernsteuerung eines Programms verstehe ich, dieses Programm, die „GUI“, von einem anderen Programm aus so zu bedienen, als würde der Benutzer sie mit Maus und Tastatur selbst bedienen.
      Ich verstehe nicht darunter, eine Seite in einem Webbrowser zu bedienen.

      "Fenster" ist im folgenden ein Oberbegriff für alle möglichen Controls, das, was in C/C++ ein "Fensterhandle" (Handle) hat.
      Was wollen wir alles bei der Fernsteuerung tun?
      - Fenstertexte lesen
      - Fenstertexte schreiben
      - Button klicken
      - CheckBoxen checken und unchecken
      - Child-Dialoge identifizieren
      Damit ist im Prinzip alles erfasst, alles weitere lässt sich darauf zurückführen.

      Zunächst benötigen wir das Fenster-Handle des zu steuernden GUI selbst, dies bekommen wir als Property MainWindowHandle der kommunizierenden Process-Instanz:

      C#-Quellcode

      1. private Process _Process;
      2. private IntPtr _MainWnd;

      C#-Quellcode

      1. this._Process = Process.Start("FernsteuerungTestGui.exe"); // hier mal ohne Parameter
      2. // oder
      3. Process[] prAll = Process.GetProcessesByName("FernsteuerungTestGui"); // hier ohne ".exe"
      4. this._Process = prAll[0];
      5. // und dann
      6. this._MainWnd = pr.MainWindowHandle;

      Fangen wir an mit dem Identifizieren aller Controls (Child-Fenster) auf der anzusteuernden GUI.
      Der Witz dabei ist der, dass beim Durchklickern die Reihenfolge dieser Controls immer (bei jedem Neustart) dieselbe ist. Man muss sich also nur ein Mal die Arbeit machen, alle Fenster zuzuordnen, dann genügt es, aus der Liste der Controls per Index das richtige / die richtigen herauszupicken.
      Dazu verwenden wir die API-Funktion EnumChildWindows().

      C#-Quellcode

      1. private delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
      2. [DllImport("user32")]
      3. [return: MarshalAs(UnmanagedType.Bool)]
      4. private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr listHandle);
      Diese erwartet als Parameter zunächst das Fensterhandle der GUI.
      Der 2. Parameter ist ein Delegate auf eine Call-Back-Funktion, in der wir die uns vom System übergebene Information für jedes einzelne Fenster behandeln können.
      Der 3. Parameter ist der Pointer auf das Handle einer Liste, in der die Call-Back-Funktion die gesuchte Information ablegt.
      Resultat ist eine Liste mit den Window-Handles aller Controls. Hier werden native Methoden und managed IEnumerables gehörig verwoben, so sieht der Code auch aus.
      Aufgerufen wird diese Funktion wie folgt, ich hab das ganze mal als eine List<IntPtr> implementiert:

      C#-Quellcode

      1. public static List<IntPtr> GetChildWindows(IntPtr parent)
      2. {
      3. List<IntPtr> result = new List<IntPtr>();
      4. GCHandle listHandle = GCHandle.Alloc(result);
      5. try
      6. {
      7. EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
      8. EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
      9. }
      10. finally // ordentlich aufräumen, falls was passiert, denn das sind unmanaged Ressourcen
      11. {
      12. if (listHandle.IsAllocated)
      13. {
      14. listHandle.Free();
      15. }
      16. }
      17. return result;
      18. }
      19. private static bool EnumWindow(IntPtr handle, IntPtr pointer)
      20. {
      21. GCHandle gch = GCHandle.FromIntPtr(pointer);
      22. List<IntPtr> list = gch.Target as List<IntPtr>;
      23. if (list == null)
      24. {
      25. throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
      26. }
      27. list.Add(handle);
      28. // You can modify this to check to see if you want to cancel the operation, then return a null here
      29. return true;
      30. }
      Der Ablauf ist folgender:
      Wir rufen auf:

      C#-Quellcode

      1. List<IntPtr> childs = GetChildWindows(mainWnd);
      Die Prozedur EnumChildWindows() klickert alle Child-Fenster einer GUI durch und für jedes gefundene Fenster wird die Callback-Funktion EnumWindow genau ein al aufgerufen und legt das übergebene Fensterhandle in der List<IntPtr> ab. Da der Aufruf aus dem unmanaged Bereich kommt, wird der Pointer auf diese List mit "durchgetunnelt".
      Falls was passiert oder auch nicht, werden die unmanaged Überreste im finally-Block aufgeräumt.

      Das Identifizieren aller Controls erfolgt nun in 2 Schritten, einmal der "Design-Time", einmal der "Runtime".
      Bei der "Design-Time" ermitteln wir die Indizes der Controls, die wir benötigen, bei "Runtime" erfolgt nur noch der entsprechende Array-Index-Zugriff.
      Bei der Design-Time können z.B. der Window-Text aller gefundener Controls durch einen eigenen Text ersetzt werden (z.B. durch eine laufende Nummer), so können die Controls ganz easy identifiziert werden.

      Dazu verwenden wir die Prozedur SendMessage in ihrer .NET-Ausprägung mit dem letzten Parameter (LPARAM) als string (später kommt noch eine Ausprägung mit LPARAM als int):

      C#-Quellcode

      1. [DllImport("user32.dll", CharSet = CharSet.Unicode)]
      2. public static extern IntPtr SendMessage(IntPtr hwnd, int wMsg, IntPtr wParam, string lParam);

      Zur Demonstration habe ich ein kleines C++-Programm geschrieben, dessen Controls wollen wir nun enumerieren.
      Zuerst wird die "GUI" gestartet, dann enumerieren wir die Controls und weisen ihnen einen anderen signifikanten Text zu:

      C#-Quellcode

      1. private void btnStart_Click(object sender, EventArgs e)
      2. {
      3. this._Process = new Process();
      4. this._Process.Exited += this.TestGui_Exited;
      5. this._Process.StartInfo.FileName = "FernsteuerungTestGui.exe";
      6. this._Process.Start();
      7. this._Process.WaitForInputIdle();
      8. this._MainWnd = this._Process.MainWindowHandle;
      9. }

      C#-Quellcode

      1. private void btnEnumWnd_Click(object sender, EventArgs e)
      2. {
      3. List<IntPtr> childs = NativeMethods.GetChildWindows(this._MainWnd);
      4. for (int i = 0; i < childs.Count; i++)
      5. {
      6. NativeMethods.SendMessage(childs[i], NativeMethods.WM_SETTEXT, IntPtr.Zero, i.ToString());
      7. }
      8. }
      Und das passiert, die Reihenfolge ist bei jedem Start der GUI dieselbe:

      vorher: ==> nachher:

      Fortsetzung folgt ...
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Bevor wir weiter machen wollen wir das ganze "Gehandle" ordentlich strukturieren und aufräumen. Das bietet gleichzeitig die Möglichkeit, den Code auch in anderen Projekten ganz einfach nachzunutzen.
      Wir machen uns eine separate Klasse, in der alle API-Deklarationen und alle statischen Prozeduren zusammengefasst werden. Microsoft schlägt für eine solche Klasse den Namen NativeMethods vor:

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Runtime.InteropServices;
      4. namespace Fernsteuerung
      5. {
      6. static class NativeMethods
      7. {
      8. // ...
      9. }
      10. }
      Wenn wir dann von dem eigenen Programm drauf zugreifen, greifen wir mit dem Klassennamen als Präfix zu, da wird uns von der IDE auch gleich alles angeboten, was da zur Verfügung steht:

      OK, weiter im Text.
      Es kommt vor, dass uns die Liste aller Child-Fenster gar nicht interessiert, wir wollen z.B. nur auf Buttons klicken oder wir wollen nur Labels ausfüllen.
      Nichts leichter als das. Dafür gigt es die API-Funktion FindWindowEx().
      Sie hat ähnliche Eigenschaften wie die API-Funktion EnumChildWindows(), bei richtigem Gebrauch kann sie diese Funktion sogar vollständig ersetzen. Allerdings ist auch hier die Summe aller Schwierigkeiten konstant.

      C#-Quellcode

      1. [DllImport("user32.dll", CharSet = CharSet.Unicode)]
      2. public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
      Diese erwartet als Parameter wie oben das Fensterhandle der GUI.
      Der 2. Parameter ist das Handle des Child-Fensters, von dem aus die Suche beginnen soll. Suchen wir das erste Fenster, geben wir IntPtr.Zero vor.
      Der 3. Parameter ist der Name der Fensterklasse, unter dem dieses Child-Fensters im Betriebssystem registriert ist und
      der 4. Parameter ist sein Text.
      Der Aufruf dieser Funktion erfolgt entweder iterativ, das heißt, wir klickern alle Fenster durch, bis wir das richtige Child-Handle gefunden haben
      oder
      wir haben alle erforderlichen Informationen und können mit einem einzigen Aufruf das richtige Child-Handle auslesen.
      Fangen wir mit der zweiten Möglichkeit an.
      Beispiel: Wir suchen das Handle zu einem Button mit dem Text "Button1".
      Wir benötigen nun den Name der Fensterklasse, unter dem der (jeder) Button im Betriebssystem registriert ist. Dafür gibt es das Tool Spy++.
      Das Studio bietet uns einen Menüpunkt zum Start von Spy++ und von Spy++ (x64) (Extended / Professional / Ultimate Version, Express weiß ich nicht):
      Studio => Extras => Spy++. Wir müssen zustimmen, dass Spy als Administrator ausgeführt wird.
      In der ToolBar finden wir 2 Buttons (da ist ein kleines Fernrohr drauf) mit dem Text "Fenster suchen":

      da klicken wir auf den rechten Button (nur Fernrohr ohne Dialog):

      Wir klicken auf das Suchtool (Fenster mit Zielscheibe) und gehen damit (mit gedrückter Maustaste) auf dem Bildschirm spatzieren und sehen uns an, was passiert.
      Ich beschränke mich hier auf die "FernsteuerungTestGui" und schreibe neben den Text den Klassennamen:

      "0" => "Button"
      "1" => "Button"
      "2" => "Button"
      "3" => "Static"
      "4" => "Edit"
      Nun können wir uns das Handle auf den Button mit dem Text "Button1" holen und zum Test den Text ändern:

      C#-Quellcode

      1. IntPtr btn = NativeMethods.FindWindowEx(this._MainWnd, IntPtr.Zero, "Button", "Button1");
      2. NativeMethods.SendMessage(btn, NativeMethods.WM_SETTEXT, IntPtr.Zero, "Button2");
      Funktioniert, auch ohne dass ich das Resultat poste.
      Nun kann es passiern, dass in der zu steuernden GUI mehrere gleichartige Controls sind, die den selben Fenstertext haben (z.B. Labels ohne Text).
      Da ich in meinem Testprogramm nur ein Label habe, nehmen wir die Button.
      Wir rufen FindWindowEx() iterativ auf und lassen uns alle Button finden, deren Inhalt wir mit "bla" markieren:

      C#-Quellcode

      1. IntPtr next = IntPtr.Zero;
      2. int i = 0;
      3. // Endlosschleife über alle Controls gleicher Window-Klasse
      4. while (true)
      5. {
      6. IntPtr btn2 = NativeMethods.FindWindowEx(this._MainWnd, next, "Button", null);
      7. if (btn2 == IntPtr.Zero)
      8. {
      9. // Abbruchkriterium
      10. break;
      11. }
      12. NativeMethods.SendMessage(btn2, NativeMethods.WM_SETTEXT, IntPtr.Zero, "bla");
      13. i++;
      14. next = btn2;
      15. }


      Ganz wichtig ist noch dieser Hinweis:
      FindWindowEx() unterscheidet ganz sauber zwischen einem Leerstring ("") und einem Null-String (Pointer mit dem Inhalt NULL bzw. NULL-Pointer) mit folgender Bewandtnis:
      Suchen wir alle Label, die keinen Text enthalten, müssen wir den Leerstring "" suchen.

      C#-Quellcode

      1. IntPtr lbl1 = NativeMethods.FindWindowEx(this._MainWnd, next, "Static", "");

      Suchen wir alle Label, egal, welchen Text sie beinhalten, müssen wir null als Argument verwenden:

      C#-Quellcode

      1. IntPtr lbl2 = NativeMethods.FindWindowEx(this._MainWnd, next, "Static", null);

      Nun sind wir in der Lage, mit FindWindowEx() die Funktionalität von EnumChildWindows() nachzubilden:
      Statt eines Klassennamens geben wir null als Parameter mit und teilen so der Funktion mit, dass wir auch diesen Parameter ignoriert haben möchten.

      C#-Quellcode

      1. IntPtr next = IntPtr.Zero;
      2. List<IntPtr> allWnd = new List<IntPtr>();
      3. // Endlosschleife über alle Controls gleicher Window-Klasse
      4. while (true)
      5. {
      6. IntPtr wnd = NativeMethods.FindWindowEx(this._MainWnd, next, null, null);
      7. if (wnd == IntPtr.Zero)
      8. {
      9. // Abbruchkriterium
      10. break;
      11. }
      12. allWnd.Add(wnd);
      13. next = wnd;
      14. }
      Das Ergebnis ist identisch mit dem von EnumChildWindows().

      Nun haben wir Zugriff auf jedes Child-Window einer GUI, nun wollen wir mit der GUI arbeiten.

      Fortsetzung folgt...
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()

      Nun wollen wir die Controls ansteuern, fangen wir an mit dem Drücken eines Buttons. Das ganze besteht aus zwei Teilen:
      1. Finden des Buttons,
      2. Drücken des Buttons.
      Üblicherweise sind Buttons in einem Fenster einzigartig beschriftet, es gibt keine 2 Button mit demselben Text.
      Falls doch, siehe oben, dann müssen alle Button identifiziert und die Handles (IntPtr) zugeordnet werden.
      Mit

      C#-Quellcode

      1. IntPtr ptr = NativeMethods.FindWindowEx(this._MainWnd, IntPtr.Zero, className, wndText);
      bekommen wir den Pointer auf den Button, wobei className der Klassenname "button" und wndText der Text des Buttons ist.
      Für das Drücken des Buttons machen wir uns eine separate Prozedur in der Klasse NativeMethods, der Parameter hwnd ist das Handle auf den Button:

      C#-Quellcode

      1. public const int BM_CLICK = 0xF5;
      2. public static void ClickButton(IntPtr hwnd)
      3. {
      4. NativeMethods.SendMessage(hwnd, NativeMethods.BM_CLICK, IntPtr.Zero, null);
      5. }
      Drücken wir nun den Button "Button1" im Testprogramm.
      Eine Messageox "Hallo Welt" geht auf, doch unsere TestForm ist blockiert.
      Was ist passiert?
      Die API-Funktion SendMessage() hat uns einen Streich gespielt: Sie kehrt erst zurück, wenn die gesendete Message verarbeitet wurde (etwa so, als würden wir einen modalen Dialog aufrufen).
      Also müssen wir eine andere Funktion nehmen: SendMessage(), die Parameter sind dieselben, jedoch kehrt die Funktion sofort zurück.
      Unsere ProzedurClickButton() sieht nun so aus:

      C#-Quellcode

      1. /// <summary>
      2. /// Prozedur, die an ein Fenster ein Button_Click sendet
      3. /// </summary>
      4. /// <param name="hwnd">Handle des Buttons</param>
      5. public static void ClickButton(IntPtr hwnd)
      6. {
      7. //NativeMethods.SendMessage(hwnd, NativeMethods.BM_CLICK, IntPtr.Zero, null);
      8. NativeMethods.PostMessage(hwnd, NativeMethods.BM_CLICK, IntPtr.Zero, null);
      9. }
      OK.

      Nächstes Problem:
      Betätigen einer CheckBox (ein RadioButton funktioniert identisch):
      Im Testprogramm befindet sich eine CheckBox, deren Check-Zustand beim Umschalten im Label angezeigt wird.
      Wir finden das CheckBox-Fenster mit der Funktion FindWindowEx() und geben als Parameter den Klassennamen "Button" und den Window-Text "Check1" mit.

      C#-Quellcode

      1. IntPtr check = NativeMethods.FindWindowEx(this._MainWnd, IntPtr.Zero, "Button", "Check1");

      Zum Setzen des Check-Zustandes verwenden wir die Prozedur SendMessage in ihrer .NET-Ausprägung mit dem letzten Parameter (LPARAM) als int.
      Ich implementiere keine Überladung der Parameter, sondern gebe der Funktion einen eigenen Namen (das ist Geschmackssache):

      C#-Quellcode

      1. [DllImport("user32.dll", EntryPoint = "SendMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
      2. public static extern int SendMessageInt(IntPtr hwnd, int uMsg, int wParam, int lParam);
      Wir müssen der CheckBox noch sagen was wir wollen, nämlich den Chek-Zustand setzen, dazu nehmen wir die Konstante

      C#-Quellcode

      1. public const int BM_SETCHECK = 0xF1;
      Nun müssen wir noch wissen, welchen Wert wir setzen müssen, um den gewünschten Effekt zu erreichen.
      BM_SETCHECK invertiert den Check-Zustand der CheckBox, also müssen wir für das Checken eine 0, für das Un-Checken eine 1 senden.
      Damit ist das Setzen bzw. Entfernen des Häkchens getan, aber noch nicht das Klicken. Das übernehmen wir mit der bereits besprochenen Funktion ClickButton().
      Das ganze sieht dann so aus, das CheckChange-Ereignis unter .NET lässt den Haken der C++-GUI parallel ändern:

      C#-Quellcode

      1. private void cbCheck_CheckedChanged(object sender, EventArgs e)
      2. {
      3. IntPtr check = NativeMethods.FindWindowEx(this._MainWnd, IntPtr.Zero, this.GetClassName(ClassID.eButton), "Check1");
      4. int value = this.cbCheck.Checked ? 0 : 1; // Achtung: Anders herum, BM_SETCHECK invertiert, löst aber nicht aus.
      5. NativeMethods.SendMessageInt(check, NativeMethods.BM_SETCHECK, value, 0);
      6. this.ClickButton(check);
      7. }
      Das Resultat des Änderns des Check-Zustandes wird im Label des Testprogramms angezeigt.

      Fortsetzung folgt.

      Damit Ihr schon mal ein wenig spielen könnt, hänge ich das anzusteuernde Testprogramm an, die kompletten Projekte poste ich, wenn wir mit der Besprechung durch sind.
      Dateien
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Heute wollen wir uns mit einer MessageBox befassen. Analog können wir auch angezeigte Dialoge behandeln.
      Wenn wir eine MessageBox angezeigt bekommen, müssen wir sie auch gezielt bedienen können. Deswegen lasse ich im Testprogramm YesNoCancel anzeigen, und je nach gedrücktem Button wird zur Kontrolle im Label "Ja", "Nein" bzw. "Abbrechen" angezeigt.
      Das einzige Problem besteht darin, das Fensterhande der MessageBox zu finden:
      Wir suchen ein Fenster, von dem wir genau zwei Dinge wissen:
      Seinen Fenster-Titel und den Umstand, dass es ein Child-Fenster von unserem Hauptprogramm ist.
      Das machen wir uns nun zu Nutze.

      Wir verwenden die API-Funktion GetWindow():

      C#-Quellcode

      1. [DllImport("user32.dll")]
      2. public static extern IntPtr GetWindow(IntPtr hwnd, IntPtr nID);
      Diese Funktion iteriert über alle Hauptfenster und gibt uns das Handle des nächsten Fensters zurück. Sie funktioniert so ähnlich wie FindWindowEx, nur beziehen sich die Fenster auf das System und nicht auf ein Hauptfenster.

      Wir verwenden die API-Funktion GetWindowText():

      C#-Quellcode

      1. [DllImport("user32.dll")]
      2. public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder returnedString, int nMaxCount);
      Diese Funktion gibt uns den Text des Fensters zurück. Dabei ist es egal, ob das Fenster ein Hauptfenster, eine TextBox oder ein Label ist.

      Und wir verwenden die API-Funktion GetParent():

      C#-Quellcode

      1. [DllImport("user32.dll")]
      2. public static extern IntPtr GetParent(IntPtr hwnd);
      Diese Funktion gibt uns das Handle des ParentWindows des Fensters zurück.

      Mit den Konstanten

      C#-Quellcode

      1. public const int GW_HWNDFIRST = 0;
      2. public const int GW_HWNDNEXT = 2;
      ergibt sich die folgende Prozedur:
      Spoiler anzeigen

      C#-Quellcode

      1. /// <summary>
      2. /// Ermitteln des Handles eines Child-Dialogs oder einer MessageBox mit dem übergebenen Fenstertitel
      3. /// </summary>
      4. /// <param name="title">Titel des zu suchenden Fensters</param>
      5. /// <returns>
      6. /// Vorhanden: Fensterhandle
      7. /// nicht vorhanden: IntPtr.Zero
      8. /// </returns>
      9. private IntPtr GetDlgWindow(string title)
      10. {
      11. IntPtr hWndNext = NativeMethods.GetWindow(NativeMethods.FindWindow(IntPtr.Zero, IntPtr.Zero), (IntPtr)NativeMethods.GW_HWNDFIRST);
      12. StringBuilder space = new StringBuilder(0x200);
      13. while (hWndNext != IntPtr.Zero)
      14. {
      15. if (((NativeMethods.GetWindowText(hWndNext, space, space.Capacity) > 0)
      16. && space.ToString().StartsWith(title)
      17. && (NativeMethods.GetParent(hWndNext) == this._MainWnd)))
      18. {
      19. return hWndNext;
      20. }
      21. hWndNext = NativeMethods.GetWindow(hWndNext, (IntPtr)NativeMethods.GW_HWNDNEXT);
      22. }
      23. return IntPtr.Zero;
      24. }
      Sie funktioniert folgendermaßen:
      Übergeben wird der Titel der MessageBox. Die Prozedur holt sich das Handle des ersten Fensters (GW_HWNDFIRST). Wenn dieses Fenster existiert, holen wir seinen Text, testen, ob dieser mit dem Suchwort beginnt und ob das Fenster ein Child unseres Hauptfensters ist.
      Ist dies der Fall, wird das Handle zurückgegeben. Wenn nicht, wird mit dam nächsten Fenster probiert (GW_HWNDNEXT).
      Wird kein Fenster gefunden, bekommen wir IntPtr.Zero zurück.

      Wie oben schon behandelt iterieren wir durch die Button der MessageBox und drücken den gewünschten solchen.

      Das Finden der Button-Handles wird mit beiden oben beschriebenen Methoden gemacht, das Resultat ist identisch:
      Spoiler anzeigen

      C#-Quellcode

      1. /// <summary>
      2. /// Button-Click-Routine für die Button "Ja", "Nein", "Abbrechen"
      3. /// </summary>
      4. private void button_Click(object sender, EventArgs e)
      5. {
      6. int index = 0;
      7. if (sender == this.btnJa)
      8. {
      9. index = 0;
      10. }
      11. else if (sender == this.btnNein)
      12. {
      13. index = 1;
      14. }
      15. else if (sender == this.btnAbbrechen)
      16. {
      17. index = 2;
      18. }
      19. // Handle der MessageBox holen
      20. IntPtr box = this.GetDlgWindow("FernsteuerungTestGui");
      21. if (box == IntPtr.Zero)
      22. {
      23. MessageBox.Show("Fenster nicht gefunden");
      24. return;
      25. }
      26. if (this.radioButton1.Checked)
      27. {
      28. // Methode GetChildWindows
      29. List<IntPtr> childs = NativeMethods.GetChildWindows(box);
      30. // Button auslösen
      31. this.ClickButton(childs[index]);
      32. }
      33. else
      34. {
      35. // Methode FindWindowEx
      36. IntPtr next = IntPtr.Zero;
      37. for (int i = 0; i <= index; i++)
      38. {
      39. IntPtr btn = NativeMethods.FindWindowEx(box, next, this.GetClassName(ClassID.eButton), null);
      40. next = btn;
      41. }
      42. // Button auslösen
      43. this.ClickButton(next);
      44. }
      45. }

      Fortsetzung folgt.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ () aus folgendem Grund: Spoiler eingefügt