10.9 Benutzerinteraktionen automatisieren, Robot und Screenshots *
Eine besondere Eigenschaft von Präsentationsprogrammen ist die Tatsache, dass Benutzerinteraktionen wie von Zauberhand automatisch vom System vorgenommen werden. Ein Programm kann beispielsweise die Interaktion mit der Maus oder der Tastatur aufnehmen und zu einem späteren Zeitpunkt abspielen. Weitere Nutzer sind GUI-Testwerkzeuge wie FEST-Swing (http://fest.easytesting.org/swing/wiki/pmwiki.php), die Benutzerinteraktionen automatisieren.
10.9.1 Der Roboter
Genau für diese Art der Oberflächensteuerung gibt es die Klasse Robot im Paket AWT. Sie verwaltet eine mit Aktionen gefüllte Ereigniswarteschlange. Die Aktionen werden nacheinander abgearbeitet.
class java.awt.Robot |
- void keyPress(int keycode)
Drückt eine Taste. - void keyRelease(int keycode)
Lässt die Tasten wieder los. - void mouseMove(int x, int y)
Bewegt die Maus auf die Koordinate relativ zum aktuellen Fenster. - void mousePress(int buttons)
Aktiviert eine oder mehrere Maustasten. - void mouseRelease(int buttons)
Lässt die Maustaste wieder los. - void delay(int ms)
Wartet Millisekunden. Mehr als 60 Sekunden sind nicht möglich; die Strafe wäre eine IllegalArgumentException.
10.9.2 Automatisch in die Tasten hauen
Bei den Methoden keyPress(), keyRelease(), mousePress() und mouseRelease() sind die Parameter erklärungsbedürftig. Der Parameter keycode ist der erste, der beachtet werden muss.
Beispiel |
Der Roboter legt Tastendrücke in die Ereignisschlange: Robot rob = new Robot(); Wird das Programmstück in einer grafischen Applikation genutzt, muss sichergestellt sein, dass die Tasten auch die passende Komponente erwischen. Liegt der Fokus zum Beispiel auf einem Rollbalken, werden diese Tastendrücke natürlich ohne Wirkung bleiben. Aus diesem Grund ist es angebracht, mit requestFocus() den Fokus zu setzen. |
Das Argument von keyPress() und keyRelease() ist eine Konstante der Klasse KeyEvent. Neben den alphanumerischen Tasten auf der Tastatur finden sich unter anderem Konstanten für Funktionstasten, Meta-Tasten, Cursor-Tasten und weitere Sonderzeichen. Die Konstanten für Großbuchstaben und Ziffern decken sich mit den ASCII-Zeichen und können daher alternativ verwendet werden. Bei fast allen Sonderzeichen entspricht die Konstante dem ASCII-Code, doch ist dies nicht selbstverständlich. Die Kleinbuchstaben (ab 0x61) werden zum Beispiel auf den 10er-Block abgebildet (ab 0x60). Ist der übergebene Keycode undefiniert (durch die Konstante KeyEvent.VK_UNDEFINED vorgegeben), so erzeugt der Roboter eine IllegalArgumentException.
Um die Umschalttaste während der Automatisierung einzuschalten, nutzen wir die Methode keyPress(KeyEvent.VK_SHIFT) und lösen die Tasten mit keyRelease(). Das Beispiel macht deutlich, dass das Freigeben nur für Operationen wie zum Beispiel Umschalttaste, VK_ALT und VK_ALT_GRAPH nötig ist, allerdings nicht für normale Buchstaben.
10.9.3 Automatisierte Mausoperationen
Jetzt fehlen uns noch die Methoden mousePress() und mouseRelease(). Beide erhalten als Argument eine Konstante der Klasse InputEvent: entweder BUTTON1_MASK, BUTTON2_MASK oder BUTTON3_MASK oder eine Kombination aus den dreien wie BUTTON2_MASK | BUTTON3_MASK.
Beispiel |
Aktiviere die (meist linke) Maustaste, warte eine Sekunde, und lass die Maustaste wieder los: rob.mousePress( InputEvent.BUTTON1_MASK ); |
Wenn die Mausaktion eine Komponente treffen soll, dann gilt das Gleiche wie für Tastendrücke. Nur muss hier der Fokus nicht auf der Komponente liegen, sondern die Mauskoordinaten müssen auf die Komponente zeigen. Damit die Bildschirmkoordinate einer Komponente ausgelesen werden kann, bietet Component eine Methode getLocationOnScreen() an, die ein Point-Objekt mit den Startkoordinaten liefert. Diese können mit einem kleinen Offset an mouseMove() weitergereicht werden. Dann befindet sich der Zeiger über der Komponente, und mousePress() kann seine Wirkung nicht mehr verfehlen.
10.9.4 Methoden zur Zeitsteuerung
Einige Methoden der Klasse Robot steuern Verzögerungen.
class java.awt.Robot |
- void setAutoDelay(int ms)
Die Robot-Klasse sendet zum Abspielen Ereignisse an die Oberfläche. Diese Methode setzt die Dauer der Verzögerung fest, die nach dem Ereignis vergehen soll. Mehr als 60 Sekunden sind auch hier nicht gültig. - int getAutoDelay()
Liefert die Zeit, die nach dem Ereignisaufruf vergehen soll. - void waitForIdle()
Wartet, bis alle Ereignisse in der Warteschlange bearbeitet sind. - void setAutoWaitForIdle(boolean isOn)
Ist isOn = true, wird waitForIdle() nach einem Ereignis aufgerufen. - boolean isAutoWaitForIdle()
Liefert true, falls der Robot automatisch waitForIdle() nach einem Ereignis aufruft.
10.9.5 Bildschirmabzüge (Screenshots)
Eine sehr interessante Methode der Robot-Klasse ist createScreenCapture(). Mit ihr lässt sich ein Bildschirmabzug (engl. screenshot) ohne Mauszeiger (zumindest unter Windows, Linux, Solaris und Mac OS X) machen. Die Methode createScreenCapture() erwartet als Argument ein Rectangle-Objekt (speichert x, y, height und width), das den zu »fotografierenden« Bereich spezifiziert. Das Ergebnis von createScreenCapture() ist ein BufferedImage-Objekt, das direkt mit ImageIO in eine Datei kommen kann.
Listing 10.28: com/tutego/insel/ui/image/Screenshot.java, main()
BufferedImage bi = new Robot().createScreenCapture(
new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
ImageIO.write( bi, "jpg", new File("c:/screenshot.jpg") );
System.exit( 0 );
Mit createScreenCapture() lassen sich sehr interessante Lösungen realisieren. So lässt sich zum Beispiel überwachen, ob sich Bildschirminhalte ändern. Mit einer Bildanalyse ließe sich auch herausfinden, was der Anwender sich gerade anschaut.
In einem kleinen Beispiel wollen wir feststellen, ob der Benutzer aktiv vor dem Bildschirm ist oder nicht. Wir machen dies an der Anzahl der Pixel fest, die sich jede Sekunde verändern. Ein Programm soll dazu jede Sekunde einen Bildschirmabzug nehmen und ausgeben, um wie viel Prozent sich die Pixel gegenüber dem Vorgängerbild verändert haben.
Listing 10.29: com/tutego/insel/ui/image/ImageDiffs.java
package com.tutego.insel.ui.image;
import java.awt.*;
import java.awt.image.*;
public class ImageDiffs
{
public static void main( String args[] ) throws Exception
{
Rectangle screenSize = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
BufferedImage image1 = new Robot().createScreenCapture( screenSize );
while ( true )
{
Thread.sleep( 1000 );
BufferedImage image2 = new Robot().createScreenCapture( screenSize );
DataBuffer dataBuffer1 = image1.getData().getDataBuffer();
DataBuffer dataBuffer2 = image2.getData().getDataBuffer();
int total = dataBuffer1.getSize(), diff = 0;
for ( int i = 0; i < total; i++ )
if ( dataBuffer1.getElem( i ) != dataBuffer2.getElem( i ) )
diff++;
System.out.printf( "Pixel total=%d, unterschiedliche Pixel=%d, Unterschied=%.2f%%%n",
total, diff, (double) 100 * diff / total );
image1 = image2;
}
}
}
Nachdem der Bildschirmabzug gemacht wurde, gilt es, auf die Pixel zuzugreifen und die beiden Bilder zu vergleichen. Um das Bild abzulaufen, könnten wir entweder vom BufferedImage mit der Methode getRGB(int x, int y) arbeiten oder uns – anders macht es getRGB() auch nicht – das Datenmodell der Grafik holen und es einfach komplett auflaufen; die Größe und Breite der beiden Bildschirmabzüge ist ja immer gleich, und so spielen die Höhe und Breite keine Rolle. Von den beiden DataBuffer-Objekten, die die Rohdaten der Grafik repräsentieren, erfragt getElem() den Farbwert. Wenn es Unterschiede gibt, inkrementiert das Programm einen Zähler. Nach dem Ablaufen aller Pixel folgt eine Statistik, die etwa so beginnen kann:
Pixel total=2304000, unterschiedliche Pixel=297, Unterschied=0,01 %
Pixel total=2304000, unterschiedliche Pixel=1708, Unterschied=0,07 %
Pixel total=2304000, unterschiedliche Pixel=1689, Unterschied=0,07 %
Pixel total=2304000, unterschiedliche Pixel=1000568, Unterschied=43,43 %
Pixel total=2304000, unterschiedliche Pixel=1002525, Unterschied=43,51 %
Pixel total=2304000, unterschiedliche Pixel=2087, Unterschied=0,09 %
10.9.6 Funktionsweise und Beschränkungen
Für die Steuerung auf der Rechnerseite sind insbesondere unter Unix-Systemen beim netzwerkfähigen X-Window-System (X11) einige Anforderungen zu erfüllen. Hier sind Rechte erforderlich, um auf der unteren Ebene Ereignisse platzieren zu können. Ein X-Window-System benötigt hierfür die aktivierte Standarderweiterung XTEST 2.2. Verletzt die aktuelle Architektur diese Vorgaben, so wird während der Konstruktion eines Robot-Objekts eine AWTException ausgelöst.
10.9.7 MouseInfo und PointerInfo
Mit der Klasse Robot lassen sich zwar Tastatur- und Maus-Ereignisse produzieren und Screenshots nehmen, doch Informationen über die aktuelle absolute Mausposition liefert die Klasse nicht. Die Klasse MouseInfo liefert diese Information. Die statische Methode MouseInfo.getPointerInfo() liefert ein PointerInfo-Objekt, das Aussagen über die anzeigende Einheit und über die Position des Mauszeigers macht. MouseInfo.getNumberOfButtons() liefert die Anzahl der Knöpfe.
Die Anzahl der Anwendungsfälle ist vielfältig. So ermöglicht dies zum Beispiel eine Farbpipette, mit der sich die Farbe eines Punktes nehmen lässt, der unter dem Mauszeiger steht.
Point location = MouseInfo.getPointerInfo().getLocation();
Color pixelColor = new Robot().getPixelColor( location.x, location.y );
Ein anderes Beispiel ist eine Bildschirmlupe.
Listing 10.30: com/tutego/insel/ui/image/Magnifier.java, main()
final ImageIcon icon = new ImageIcon();
final JLabel label = new JLabel( icon );
new Timer( 100, new ActionListener() {
@Override public void actionPerformed( ActionEvent e )
{
try
{
Rectangle location = new Rectangle(
MouseInfo.getPointerInfo().getLocation(), new Dimension( 40, 40 ) );
location.translate( –20, –20 );
BufferedImage image = new Robot().createScreenCapture( location );
icon.setImage( image.getScaledInstance( image.getWidth()*8,
image.getHeight()*8, Image.SCALE_FAST ) );
label.repaint();
}
catch ( AWTException ae ) { }
}
} ).start();
JOptionPane.showMessageDialog( null, label );
Abbildung 10.30: Screenshot der Anwendung Magnifier
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.