Tech-Ecke / Delphi Inhalt / Maus- und Tastendruck auf/in fremder Anwendung simulieren

 

     Maus- und Tastendruck auf/in fremder Anwendung simulieren

 

Wenn man sich in diversen Delphi-Foren auf die Suche nach "Tastedruck in fremder Anwendung simulieren" macht, so findet man eigentlich nur haufenweise Fragmente die einen als Anfänger mehr verwirren, als weiterhelfen. Dabei ist das Prinzip, wie man auf eine Fremde Anwendung zugreift zunächst mal nicht sonderlich schwer zu verstehen.

Alles dreht sich um den Handle

Wie die Oberfläche von Windows selbst, entstehen die allermeisten Anwendungen in einer objektorientierten Programmierumgebung. Diese bestehen aus einer Ansammlung von mehr oder weniger logisch ;-) verknüpften Objekten, wie Buttons, Editfeld, Listbox... usw. Beim Start der Anwendung wird jedem dieser Objekte, vom Betriebsystem eine eindeutige ID, in Form des Handle zugewiesen. Der Handle selbst ist ein Wert, der demnach ein bestimmtes Objekt im System eindeutig identifiziert. Über diesen kann man nun innerhalb des Systems direkt auf das entsprechende Objekt zugreifen, ohne dessen genaue Lage zu kennen. Genau hier setzt die hier beschriebene Methode an. Einzige Hürde ist nun den Handle herauszubekommen. Dank einer genialen Anwendung namens "WinSpy"  von Thomas Stutz ist dies jedoch selbst für einen Anfänger kein großes Problem...

    WinSpy 2008

 

Handle einer Anwendung auslesen

Möchte man in einer fremden Anwendung, ein Ereignis auf ein Objekt auslösen, so benötigt man, wie bereits erwähnt dessen Handle. An diesen kommt man z.B. über den Objekt-Name ran. Jedoch kann man nicht direkt ein x-beliebiges Objekt direkt auslesen, sondern man muss sich über dessen Fenster und eventuell noch über weitere Objekte, bis an das gewünschte Objekt heranpirschen. Zur besseren Verständnis mal ein theoretisches Beispiel: Wir erzeugen in Delphi eine Form und platzieren dort einen Button drauf. Möchte man nun extern in der Laufenden Anwendung auf diesen Button zugreifen, so führt der Weg zum Button über die Form, also das Anwendungsfenster. Liegt dieser Button jedoch in einer GroupBox, so führt der Weg von der Form über die GroupBox zum Button usw.  Man benötigt also zunächst den Objektnamen der Form, dann den der GorupBox und dann den des Buttons. Ok, ist ja alles schön und gut, aber woher bekomme ich denn jetzt die Objektnamen einer Anwendung, die ich nicht geschrieben habe... Genau hier kommt WinSpy zum Einsatz, denn mit WinSpy lässt sich der Objektname inklusive Handle, über ein 'Pipettenwerkzeug' kinderleicht abgreifen. Aber damit nicht genug, per Knopfdruck liefert das Programm auch gleich ein komplettes Delphi-Source-Gerüst, auf dem man weiter aufbauen kann.
Ich möchte hier mal anhand des Windows-Notepad zeigen, wie man Maus- und Tastendrücke in einer fremden Anwendung simuliert.

Zunächst geht es darum, den Objektnamen bzw. Handle der Anwendung zu finden, bevor man sich an die einzelnen Objekte in der Anwendung zu schaffen macht. OK, nachdem man Notepad (Start/Programme/Zubehör/Editor) und WinSpy gestartet hat, klickt man in WinSpy auf die Pipette (Fenster auswählen), hält die Maustaste gedrückt und zieht das erscheinende Fadenkreuz auf den oberen Fensterrahmen vom Notepad. Bitte nicht in das Eingabefeld!

Nun zeigt WinSpy, neben anderen Informationen auch den Objektname und den Handle für Notepad an. Jetzt kann man sich über "Tools/Code Generator..." einen Delphi-Code ausgeben lassen, welcher als Grundgerüst für die nächsten Schritte dienen soll. Der Code sollte wie folgt aussehen (ohne die grau hinterlegten Bemerkungen):

   var
   wndMain: HWND;
begin
   wndMain := FindWindow('Notepad','Unbenannt - Editor');       <-  1.)  umbenennen in wndMain := FindWindow('Notepad',nil);
   if wndMain <> 0 then
   begin
      ShowMessage('Window Handle: ' + IntToStr(wndMain));
      FlashControl(wndMain); // internal function        <- 2.)   löschen
  end;
end.       <- 3.)   umbenennen in end;
 

In dem Ausgabefenster des Codes befindet sich links oben der Button "Ausführen", betätigt man diesen, so erscheint für einen kurzen Augenblick ein flackerndes Rechteck um das ausgewählte Objekt. Dies geschieht mit dem Befehl FlashControl(). Man kann den Code auch hier schon editieren und testen, jedoch ist es ja Ziel den Code im eigenen Delphi Programm zu verwenden. Um den Code in Delphi zu übernehmen sollte man die grau hinterlegten Bemerkungen umsetzen.

Erläuterung der Bemerkungen: 1.)  Hier wird der Fenstername angegeben 'Unbenannt - Editor', dieser wird durch nil ersetzt, da der Fenstertext abhängig von der geladenen Datei ist. Würde im Editor also eine Datei geladen sein, so hätte der Rahmen eine Andere Benennung und die Suche nach dem Handle würde fehlschlagen.
  2.)  Dies ist eine interne Funktion von WinSpy und hat im Delphi-Code nichts zu suchen.
  3.) Da man geschickter Weise das begin und end; im Delphi-Code überkopiert, das end. aber das Ende des kompletten Sources und nicht das Ende einer Procedur anzeigt... bla bla

Bei einem TButtonClick-Event sollte das in Delphi dann so aussehen:

  procedure TForm1.Button1Click(Sender: TObject);
var
wndMain: HWND;
   begin
      wndMain := FindWindow('Notepad',nil);
      if wndMain <> 0 then ShowMessage('Window Handle: ' + IntToStr(wndMain));
end;
 

Wird dieser Code nun ausgeführt (Notepad natürlich gestartet) , so erscheint, wenn alles geklappt hat folgendes Fenster.


Jetzt kommt der Tastendruck

Ist man hier angelangt, so kann man nun Tastendrucksimulationen an das Notepad-Fenster senden. Zunächst wollen wir mal die Taste F5 simulieren, was im Notepad auch über das Menü, "Bearbeiten/Uhrzeit/Datum" erreicht wird. Hierzu ersetzen wir:

 ShowMessage('Window Handle: ' + IntToStr(wndMain));       gegen ->       PostMessage(wndMain, WM_KEYDOWN, VK_F5, 0);

 

Window Child

So, mit dem Fenster als solches kann man ja nicht viel anfangen, deshalb senden wir jetzt einmal eine Tastendrucksimulationen in das Editfeld von Notepad. Dazu benötigen man, wie Anfangs erklärt, neben dem Hande des Hauptfensters auch den Handle des Editfeldes. Das Editfeld, sowie alle Komponenten die darunter hängen (auch weitere Fenster) werden als "Window Child" bezeichnet. Hierzu bedienen wir uns wieder dem WinSpy, klicken aber diesmal direkt auf das Editfeld, dann wieder "Tools/Generator...", wodurch uns  folgender Code ausgegeben wird:

 

var
wndMain, wndChild: HWND;
   begin
      wndMain := FindWindow('Notepad','Unbenannt - Editor');       <-  1.)  umbenennen in wndMain := FindWindow('Notepad',nil);
      if wndMain <> 0 then
      begin
            wndChild := FindWindowEx(wndMain, 0, 'Edit', nil);
            if wndChild <> 0 then
               begin
                  ShowMessage('Window Handle: ' + IntToStr(wndChild));
                  FlashControl(wndChild); // internal function        <- 2.)   löschen
               end;
      end;
end.       <- 3.)   umbenennen in end;

 

 

Auch hier ist wieder das Gleiche abzuändern wie im ersten Beispiel, damit man es in seinem Delphi-Code verwenden kann. Das Ganze sieht dann in Delphi, hinter einem TButtonClick-Event so aus:

 

procedure TForm1.Button1Click(Sender: TObject);
var
wndMain, wndChild: HWND;
   begin
      wndMain := FindWindow('Notepad',nil);
      if wndMain <> 0 then wndChild := FindWindowEx(wndMain, 0, 'Edit', nil);
      if wndChild <> 0 then ShowMessage('Window Handle: ' + IntToStr(wndChild));  
end;

 

Um nun den Buchstaben "W" in das Editfeld zu schreiben, ersetzt man:

ShowMessage('Window Handle: ' + IntToStr(wndChild));       gegen ->         PostMessage(wndChild, WM_CHAR, Ord('W'), 0);

 

Senden eines kompletten Strings

Möchte man durch mehrere Tastendrucksimulation einen ganzen String senden, so könnte das wie folgt aussehen:

  procedure TForm1.Button1Click(Sender: TObject);
var
   wndMain, wndChild: HWND;
   MeinText: String;
   i: Integer;
   begin
      wndMain := FindWindow('notepad', nil);
      if Handle <>0 then wndMain := FindWindowEx(wndMain, 0, 'Edit', nil);
      if wndChild <> 0 then
            begin

               MeinText := 'Hallo Welt';
               for
i := 1 to Length(MeinText) do
                SendMessage(wndMain, WM_CHAR, Ord(MeinText[i]), 0);
            end;
   end;
 

Mausklicksimulation

Bislang haben wir ja nur einen Tastendruck simuliert und nun wird es Zeit für einen Mausklick. Zugleich kommt dann auch die nächste Stufe, das "Window Child" dran, wofür man eine eigene Function benötigt.
Zunächst lassen wir uns den FileOpenDialog von Notepad anzeigen, krallen uns dort mit der WinSpy-Pipette den "Abbrechen"-Button und lassen uns den Code anzeigen. Nun bekommt man neben dem eigentlichen Code zur Abfrage des Handle, auch noch eine Function präsentiert. Diese Function (FindWindowEx2) wird benötigt, um das Child "Abbrechen"-Button aufzuspüren und muss demnach auch im Delphi-Code der eigenen Anwendung mitaufgenommen werden. Hierbei sind keine Änderungen nötig.
Die ganze Geschichte sieht in Delphi dann so aus:

Die Funktion (irgendwo vor dem eigentlichen Code im eigenen Programm platzieren):

  function FindWindowEx2(hParent: HWND; ChildClassName: string; ChildNr: Word): HWND;
var
   i: Word;
   hChild: HWND;
begin
   hChild := 0;
   Result := 0;
   Dec(ChildNr);
   for i := 0 to ChildNr do
      begin
         hChild := FindWindowEx(hParent, hChild, PChar(ChildClassName), nil);
         if hChild = 0 then
         Break;
         Result := hChild;
      end;
end;
 

Der eigentliche Code:

  procedure TForm1.Button1Click(Sender: TObject);
var
   wndMain, wndChild: HWND;
begin

   wndMain := FindWindow('#32770','Öffnen');
   if wndMain <> 0 then wndChild := FindWindowEx2(wndMain,'Button', 3);
   if wndChild <> 0 then ShowMessage('Window Handle: ' + IntToStr(wndChild));
end;
 

Möchte man nun auf den Abbrechen-Button, welcher sich hier als Button3 entpuppt einen Mausklick simulieren, so ersetzt man:

ShowMessage('Window Handle: ' + IntToStr(wndChild));       gegen ->         SendMessage(wndChild, BM_CLICK,0,0)

 

Zugabe

Im Grunde beschreiben diese Beispiele mal den generellen Weg, wie man an einen Handle ran kommt und wie man über diesen einen Tastendruck bzw. Mausklick lossendet. Der Handle wird übrigens für jede Anwendung immer gleich ermittelt, sofern dies möglich ist. Die Simulation eines Ereignisses, kann jedoch von Anwendung zu Anwendung unterschiedlich aussehen und genau hier beginnt die ganze Sache mit unter nervig zu werden. Zwei der häufigsten Befehle wurden in den Beispielen bereits verwendet:

                                                      PostMessage und SendMessage

Die beiden Befehle unterscheiden sich eigentlich nur darin, dass SendMessage den Befehl absendet und wartet, PostMessage hingegen direkt weiterfährt. Über Beide lassen sich Mausklicks oder Tastendrücke simulieren, wobei je nach Anwendung mal der eine und mal der anderen Befehl letztlich zum Erfolg führt. Bei manchen Anwendungen kommt man mitunter weder mit Post-, noch mit SendMessage weiter, dann könnte eventuell SendKeys (Unit) oder PostKeyExHWND (Procedure) einem weiterhelfen. Insbesondere bei der Simulation von Tastenkombinationen (Hotkey's) kommt man mit PostKeyEx32 sehr oft zum Ziel, wenn alles andere versagt.

    SendKeys32         PostKeyEx      PostKeyEx32

Weitere, interessante Befehle in diesem Zusammenhang sind:

  GetWinText(Handle): String; Edit1.Text := GetWinText(wndChild);    // bzw. wndMain
SetWinText(Handle,S: String); SetWinText(wndChild,'irgend ein Text');    // bzw. wndMain
SetForeGroundWindow(Handle): Boolean; SetForeGroundWindow(wndMain);     // bzw. wndChild

Ereignisse

WM_ACTIVATE
WM_APPCOMMAND
WM_CHAR
WM_DEADCHAR
WM_HOTKEY
WM_KEYDOWN
WM_KEYUP
WM_KILLFOCUS
WM_SETFOCUS
WM_SYSDEADCHAR
WM_SYSKEYDOWN
WM_SYSKEYUP
WM_UNICHAR

Tasten-Codes VK_*

VK_NULL      
VK_LBUTTON     
VK_RBUTTON     
VK_CANCEL   
VK_MBUTTON    
VK_BACK         
VK_TAB          
VK_CLEAR        
VK_RETURN       
VK_SHIFT       
VK_CONTROL      
VK_MENU       $12 ALT Taste
VK_PAUSE        
VK_CAPITAL      
VK_ESCAPE        
VK_SPACE        
VK_PRIOR        
VK_NEXT         
VK_END          
VK_HOME         
VK_LEFT         
VK_UP           
VK_RIGHT        
VK_DOWN         
VK_SELECT       
VK_PRINT        
VK_EXECUTE      
VK_SNAPSHOT     
VK_INSERT       
VK_DELETE       
VK_HELP       
VK_0            $30 Taste 0
VK_1            $31 Taste 1
VK_2            $32 Taste 2
VK_3            $33 Taste 3
VK_4            $34 Taste 4
VK_5            $35 Taste 5
VK_6            $36 Taste 6
VK_7            $37 Taste 7
VK_8            $38 Taste 8
VK_9            $39 Taste 9
VK_A            $41 Taste A
VK_B            $42 Taste B
VK_C            $43 Taste C
VK_D            $44 Taste D
VK_E            $45 Taste E
VK_F            $46 Taste F
VK_G            $47 Taste G
VK_H            $48 Taste H
VK_I            $49 Taste I
VK_J            $4A Taste J
VK_K            $4B Taste K
VK_L            $4C Taste L
VK_M            $4D Taste M
VK_N            $4E Taste N
VK_O            $4F Taste O
VK_P            $50 Taste P
VK_Q            $51 Taste Q
VK_R            $52 Taste R
VK_S            $53 Taste S
VK_T            $54 Taste T
VK_U            $55 Taste U
VK_V            $56 Taste V
VK_W            $57 Taste W
VK_X            $58 Taste X
VK_Y            $59 Taste Y
VK_Z            $5A Taste Z
VK_LWIN         
VK_RWIN         
VK_APPS         
VK_NUMPAD0      
VK_NUMPAD1      
VK_NUMPAD2      
VK_NUMPAD3      
VK_NUMPAD4      
VK_NUMPAD5      
VK_NUMPAD6      
VK_NUMPAD7      
VK_NUMPAD8      
VK_NUMPAD9      
VK_MULTIPLY     
VK_ADD          
VK_SEPARATOR    
VK_SUBTRACT     
VK_DECIMAL      
VK_DIVIDE       
VK_F1           $70 F1 Taste
VK_F2           $71 F2 Taste
VK_F3           $72 F3 Taste
VK_F4           $73 F4 Taste
VK_F5           $74 F5 Taste
VK_F6           $75 F6 Taste
VK_F7           $76 F7 Taste
VK_F8           $77 F8 Taste
VK_F9           $78 F9 Taste
VK_F10          $79 F10 Taste
VK_F11          $7A F11 Taste
VK_F12          $7B F12 Taste
VK_F13          $7C F13 Taste
VK_F14          $7D F14 Taste
VK_F15          $7E F15 Taste
VK_F16          $7F F16 Taste
VK_F17          $80 F17 Taste
VK_F18          $81 F18 Taste
VK_F19          $82 F19 Taste
VK_F20          $83 F20 Taste
VK_F21          $84 F21 Taste
VK_F22          $85 F22 Taste
VK_F23          $86 F23 Taste
VK_F24          $87 F24 Taste
VK_NUMLOCK      
VK_SCROLL       
VK_LSHIFT       
VK_LCONTROL     
VK_RCONTORL     
VK_LMENU        
VK_RMENU        
VK_SEMICOLON      $BA Taste ;
VK_EQUAL          $BB Taste =
VK_COMMA          $BC Taste ,
VK_MINUS          $BD Taste -
VK_PERIOD         $BE Taste .
VK_SLASH          $BF Taste \
VK_BACKQUOTE      $C0 Taste `
VK_LEFTBRACKET    $DB Taste <
VK_BACKSHLASH     $DC Taste \
VK_RIGHTBRACKET   $DD Taste >
VK_QUOTE          $DE Taste ´
VK_PROCESSKEY   
VK_ATTN      
VK_CRSEL
VK_EXSEL
VK_EREOF
VK_PLAY
VK_ZOOM
VK_NONAME
VK_PA1
VK_OEM_CLEAR

 

"Die Option Drucken funktioniert erst ab Netscape V4.0 bzw. I-Explorer 5.0 !"

[letzte Aktualisierung 24.01.2009]