|
Das Programm reagiert auf Mausereignisse in zweierlei Weise. Erstens wird nach einem Klick auf den Rahmen die Sortierung des Spielfelds umgekehrt, und zweitens erlaubt das Programm das Verschieben der Spielsteine per Drag & Drop.
Um auf Mausereignisse zu reagieren, definieren wir die beiden Klassen MyMouseListener und MyMouseMotionListener, die in der init-Methode des Applets instanziert und durch Aufruf von addMouseListener bzw. addMouseMotionListener als Ereignisempfänger registriert werden. Seit der Version 1.1 des JDK gibt es in Java das Delegation Based Event Handling, bei dem die Ereignisempfänger von den Ereignisquellen getrennt sind und nur dann Nachrichten erhalten, wenn sie sich zuvor beim Ereignissender registriert haben. Dies erlaubt eine verbesserte Strukturierung großer Programme.
Aus Performance-Gründen wurden die Ereignisempfänger für Mausklick- und Mausbewegungsereignisse getrennt. Dadurch werden Programme, die sich lediglich für Mausklicks interessieren, nicht ständig mit Ereignissen bombardiert, wenn der Mauszeiger auf dem Bildschirm bewegt wird. Wir wollen uns zunächst die Klasse MyMouseListener ansehen, die für Mausklickereignisse zuständig ist. |
![]() |
|
![]() |
MyMouseListener enthält die Methode mousePressed, die genau dann aufgerufen wird, wenn der Anwender eine der Maustasten drückt.
001 public void mousePressed(MouseEvent event) 002 { 003 sourcefield = getFieldFromCursor(event.getX(), event.getY()); 004 if (sourcefield.x == -1 || sourcefield.y == -1) { 005 swapRandomization(); 006 repaint(); 007 } 008 lastpoint.x = -1; 009 lastpoint.y = -1; 010 } |
Zunächst wird durch Aufruf von getFieldFromCursor überprüft, ob der Mausklick innerhalb des Spielfelds lag. Ist das nicht der Fall, gibt die Methode ein Point-Objekt mit den Koordinaten (-1, -1) zurück, und das Programm ruft swapRandomization auf, um die Ordnung des Spielfelds umzukehren. Es geht in diesem Fall davon aus, daß der Mausklick auf dem Rahmen erfolgte. Sowohl getFieldFromCursor als auch swapRandomization sind lokale Methoden der Klasse MyMouseListener:
001 /** 002 * Liefert den zur Mausposition passenden horizontalen und 003 * vertikalen Index des darunterliegenden Steins. Liegt der 004 * Punkt auf dem Rahmen, wird (-1,-1) zurückgegeben. 005 */ 006 private Point getFieldFromCursor(int x, int y) 007 { 008 Insets insets = getInsets(); 009 Point topleft = new Point(); 010 topleft.x = insets.left + bordersize; 011 topleft.y = insets.top + bordersize; 012 Point ret = new Point(-1, -1); 013 if (x >= topleft.x) { 014 if (x < topleft.x + 4 * fieldsize.width) { 015 if (y >= topleft.y) { 016 if (y < topleft.y + 4 * fieldsize.height) { 017 ret.x = (x - topleft.x) / fieldsize.width; 018 ret.y = (y - topleft.y) / fieldsize.height; 019 drawoffset.x = x - topleft.x - 020 ret.x * fieldsize.width; 021 drawoffset.y = y - topleft.y - 022 ret.y * fieldsize.height; 023 } 024 } 025 } 026 } 027 return ret; 028 } 029 030 031 /** 032 * Kehrt die Steineordnung um: falls sie sortiert sind, 033 * werden sie gemischt und umgekehrt. 034 */ 035 private void swapRandomization() 036 { 037 //Sind die Felder sortiert? 038 boolean sorted = true; 039 for (int i = 0; i <= 15; ++i) { 040 if (aFields[i / 4][i % 4] != i) { 041 sorted = false; 042 break; 043 } 044 } 045 //Neu mischen bzw. sortieren 046 randomizeField(sorted); 047 } |
swaprandomization ist sehr einfach aufgebaut. In einer Schleife wird zunächst überprüft, ob alle Spielsteine in aufsteigender Reihenfolge angeordnet sind. Abhängig vom Ergebnis wird randomizeField mit dem Sortieren oder Vermischen der Spielsteine beauftragt.
getFieldFromCursor bestimmt zunächst die linke obere Ecke des Client-Bereichs und die Größe des Spielfelds. Anschließend wird überprüft, ob zum Zeitpunkt des Mausklicks der Mauszeiger innerhalb dieses Bereichs gelegen hat. Ist dies nicht der Fall, wird (-1, -1) zurückgegeben, andernfalls wird der Index des Spielsteins ermittelt, über dem der Mauszeiger stand. Dazu wird der jeweilige x- bzw. y-Offset in die Client-Area durch die Breite bzw. Länge eines einzelnen Spielsteins dividiert.
Anschließend wird der x- und y-Abstand des Mauszeigers von der linken oberen Ecke des unter dem Mauszeiger befindlichen Spielsteins ermittelt und in drawoffset gespeichert. Dieser Wert wird benötigt, um beim Ziehen der Maus das Rechteck konsistent mit dem Mauszeiger mitführen zu können. Das zweite Mausereignis, auf das unser Programm reagieren muß, tritt auf, wenn die Maustaste losgelassen wird. In diesem Fall wird die Methode mouseReleased aufgerufen: |
![]() |
|
![]() |
001 /** 002 * Maustaste losgelassen. 003 */ 004 public void mouseReleased(MouseEvent event) 005 { 006 if (sourcefield.x != -1 && sourcefield.y != -1) { 007 Point destfield; 008 destfield = getFieldFromCursor(event.getX(),event.getY()); 009 if (destfield.x != -1 && destfield.y != -1) { 010 if (aFields[destfield.y][destfield.x] == 15) { 011 if (areNeighbours(sourcefield, destfield)) { 012 aFields[destfield.y][destfield.x] = 013 aFields[sourcefield.y][sourcefield.x]; 014 aFields[sourcefield.y][sourcefield.x] = 15; 015 } 016 } 017 } 018 repaint(); 019 } 020 sourcefield.x = -1; 021 sourcefield.y = -1; 022 } |
mouseReleased überprüft zunächst, ob der zugehörige Mausklick innerhalb des Spielfelds ausgelöst wurde. Ist dies nicht der Fall (beispielsweise, weil die Maus vom Rand ins Spielfeld gezogen wurde), erfolgt keine weitere Bearbeitung. Andernfalls wird durch erneuten Aufruf von getFieldFromCursor der Spielstein ermittelt, auf dem die Maus gelandet ist. Falls dieser innerhalb des Spielfelds liegt, wird geprüft, ob die Maus auf der Lücke gelandet ist. Ist auch das der Fall, wird die Methode areNeighbours aufgerufen, um festzustellen, ob der Stein, auf dem die Maustaste gedrückt wurde, und die Lücke, auf der sie losgelassen wurde, Nachbarn auf dem Spielfeld sind:
001 /** 002 * Testet, ob die durch p1 und p2 bezeichneten Spielsteine 003 * Nachbarn sind. 004 */ 005 private boolean areNeighbours(Point p1, Point p2) 006 { 007 int aNeighbours[][] = {{-1,0},{0,-1},{0,1},{1,0}}; 008 for (int i = 0; i < aNeighbours.length; ++i) { 009 if (p1.x + aNeighbours[i][0] == p2.x) { 010 if (p1.y + aNeighbours[i][1] == p2.y) { 011 return true; 012 } 013 } 014 } 015 return false; 016 } |
Nur, wenn alle diese Bedingungen erfüllt sind, handelt es sich um eine gültige Operation, und der Spielstein darf verschoben werden. Dazu wird im Array aFields einfach an die Zielposition der Wert des Spielsteins, auf dem die Maus gedrückt wurde, abgelegt und der Inhalt des Quellsteins anschließend mit 15, also der Nummer der Lücke, überschrieben. Damit die Aktion auf dem Bildschirm sichtbar wird, ruft die Methode anschließend repaint auf, und das Fenster wird neu aufgebaut.
Um während des Ziehens der Maus einen visuellen Effekt zu erzielen, wird schließlich noch die Methode mouseDragged in der Klasse MyMouseMotionListener implementiert. Diese wird immer dann aufgerufen, wenn eine Mausbewegung bei gedrückter Maustaste erfolgt:
001 /** 002 * Maus wurde bei gedrückter Taste bewegt. 003 */ 004 public void mouseDragged(MouseEvent event) 005 { 006 if (sourcefield.x != -1 && sourcefield.y != -1) { 007 Graphics g = getGraphics(); 008 g.setXORMode(getBackground()); 009 g.setColor(Color.black); 010 //Das zuletzt gezeichnete Rechteck entfernen 011 if (lastpoint.x != -1) { 012 g.drawRect( 013 lastpoint.x - drawoffset.x, 014 lastpoint.y - drawoffset.y, 015 fieldsize.width, 016 fieldsize.height 017 ); 018 } 019 //Neues Rechteck zeichnen 020 g.drawRect( 021 event.getX() - drawoffset.x, 022 event.getY() - drawoffset.y, 023 fieldsize.width, 024 fieldsize.height 025 ); 026 lastpoint.x = event.getX(); 027 lastpoint.y = event.getY(); 028 g.dispose(); 029 } 030 } |
mouseDragged überprüft zunächst, ob das Ziehen der Maus wirklich innerhalb des Spielfeldes gestartet wurde. Ist dies nicht der Fall, wäre die in sourcefield gespeicherte Position des Startfeldes (-1, -1) und mouseDragged würde keine weiteren Aktionen vornehmen. Ist dies aber der Fall, beschafft sich die Methode durch Aufruf von getGraphics zunächst einen Grafikkontext, um (außerhalb von paint) im Client-Bereich des Fensters Ausgaben vornehmen zu können.
Programme, die ein Drag & Drop implementieren, visualisieren dies in der Regel so, daß sie während der Drag-Operation eine vereinfachte Version des Quellobjekts anzeigen und synchron mit der Maus mitführen. Um dabei nicht jeweils ein vollständiges Neuzeichnen erforderlich zu machen, bietet der Device-Kontext einen XOR-Modus an, bei dem alle Zeichenoperationen die jeweilige Pixelfarbe des Untergrundes invertieren. Aus einem schwarzen Pixel wird ein weißes und umgekehrt, und farbige Pixel werden in eine gut zu unterscheidende Komplementärfarbe verwandelt. Wird im XOR-Modus eine Zeichenfunktion zweimal nacheinander aufgerufen, so hat der Bildschirm nach dem zweiten Aufruf exakt dasselbe Aussehen wie vor dem ersten. |
![]() |
|
![]() |
Wir rufen also zunächst setXORMode auf, um den Grafikkontext in den XOR-Modus zu versetzen. Mit der erwähnten Technik zeichnen wir einen rechteckigen Rahmen, der im Abstand von drawoffset zur Mausposition liegt und synchron mit ihr bewegt wird. Erfolgt ein neuer Aufruf von mouseDragged, wird der beim vorigen Aufruf gezeichnete Rahmen erneut gezeichnet und damit unsichtbar gemacht. Anschließend wird ein Rahmen an der neuen Position gezeichnet und diese Position für den nächsten Aufruf in der Variable lastpoint gespeichert. Zum Schluß werden die vom Grafikkontext belegten Ressourcen durch Aufruf von dispose zurückgegeben. Dieser Aufruf ist nur nötig, wenn der Grafikkontext durch Aufruf von getGraphics selbst beschafft wurde. Abbildung 2.5 zeigt die Bildschirmausgabe während einer Drag-Operation.
Abbildung 2.5: Die Darstellung des Drag & Drop
Weiterführende Informationen zur Ereignisbehandlung finden sich in Kapitel 18 und Kapitel 19. Dort werden die Grundlagen des Delegation Based Event Handling erläutert, konkrete Architekturvorschläge vorgestellt und die möglichen Ereignistypen und die zur Reaktion darauf erforderlichen Klassen, Interfaces und Methoden vorgestellt. Die Anwendung des XOR-Modus findet sich nur in diesem Beispiel, sie wird im Buch nicht weiter vertieft. Seit dem JDK 1.2 gibt es ein eigenes Drag & Drop-API, mit dem Daten zwischen verschiedenen Anwendungen oder mit dem Betriebssystem ausgetauscht werden können. Das API wird in diesem Buch nicht behandelt. |
![]() |
|
![]() |
|
Go To Java 2, Addison Wesley, Version 1.0.2, © 1999 Guido Krüger, http://www.gkrueger.com |