200 likes | 293 Views
Ereignisbehandlung mit Java. Computer gestern und heute. Pionierzeit: Auf einem Computer läuft genau ein Programm und beansprucht sämtliche Rechenzeit für sich. Wenn das Programm beendet ist, gibt es ein Ergebnis aus. Das Programmiermodell ist die sequenzielle Programmierung.
E N D
Ereignisbehandlung mit Java Michael Weiss
Computer gestern und heute • Pionierzeit: • Auf einem Computer läuft genau ein Programm und beansprucht sämtliche Rechenzeit für sich. • Wenn das Programm beendet ist, gibt es ein Ergebnis aus. • Das Programmiermodell ist die sequenzielle Programmierung. • Moderner Computer: • Auf einem Computer laufen viele Programme quasi gleichzeitig. Die meiste Zeit haben diese Programme jedoch nichts zu tun. • Ein Programm, das etwas tun soll, bekommt ein Signal. Dieses geht von der Hardware (z.B. Tastatur, Maus, Timer, Netzwerkkarte) aus und kommt über das Betriebssystem zum Programm. • Das Programmiermodell ist die ereignisgesteuerte Programmierung. Michael Weiss
Ereignissteuerung in Java • Ereignisse werden ausgelöst: • durch Hardware (z.B. Mausklicks, Tastendruck) • durch das Betriebssystem (z.B.: es ist Zeit, ein Fenster neu zu zeichnen) • durch Timer • durch GUI-Elemente (z.B. Button angeklickt, Slider verschoben, Fenster vergrössert usw.) • Um Ereignisse zu behandeln, muss man Java mitteilen • dass man auf ein Ereignis reagieren möchte • wie man auf ein Ereignis reagieren möchte Michael Weiss
Ich möchte auf ein Button-Ereignis reagieren! Bsp. Pause-Button im Uhr-Projekt: hatPauseJButton.addActionListener(hatEreignisVerarbeiter); Hierbei ist hatEreignisVerarbeiterer ein Objekt einer von der Klasse ActionListenerabgeleiteten, selbst geschriebenen Klasse namens EreignisVerarbeiter. Die Klasse ActionListenerbesitzt lediglich eine einzige Methode, die zudem abstrakt ist: abstractvoidactionPerformed(ActionEvent e); Michael Weiss
Einschub I: Interfaces • Klassen, welche lediglich abstrakte Methoden und Konstanten besitzen, werden in Java Interfaces genannt. • Klassen, welche die Konstanten eines Interfaces übernehmen und deren abstrakte Methoden mit konkreten Methoden überschreiben, erweitern das Interface nicht, sondern implementieren es. • Während eine Klasse nur eine einzige Überklasse erweitern kann, kann sie beliebig viele Interfaces implementieren. • Beispiel ActionListener: publicinterfaceActionListener { voidActionPerformed(ActionEventevt); } (Hinweis: Dieses Interface existiert bereits. Sie müssen es aber noch implementieren.) Michael Weiss
ActionListener implementieren classEreignisVerarbeiterimplementsActionListener{ publicvoidactionPerformed(ActionEventevt){ // hier der Code, was geschehen soll, // wenn ActionPerformed() aufgerufen wurde } } • Das Objekt der Klasse ActionEvent, auf das mit evt verwiesen wird, enthält u.a. Informationen darüber, welches Objekt das Ereignis ausgelöst hat und wann das Ereignis ausgelöst wurde. • Es ist daher möglich, dass das selbe EreignisVerarbeiter-Objekt verschiedene Ereignisse bearbeitet: publicvoidactionPerformed(ActionEventevt){ Object source =evt.getSource(); if(source ==hatPauseJButton){ // Hier Code um Uhr anzuhalten }elseif(source==hatStellenJButton){ // Hier Code um Uhr zu stellen } } Michael Weiss
Einschub II: Innere Klassen • Der Code von actionPerformed() benötigt Zugriff auf die privaten Objekte der Klasse Uhr. • Ausserhalb der Klasse Uhr wird die Klasse EreignisVerarbeiter nicht benötigt. • Wir nutzen daher die Möglichkeit von Java, Klassen zu verschachteln und definieren EreignisVerarbeiter als private innere Klasse von Uhr: publicclass Uhr { // hier Variablen, Konstruktoren und Methoden der Klasse Uhr // innere Klasse privateclassEreignisVerarbeiterimplementsActionListener{ publicvoidactionPerformed(ActionEventevt){ // Code von actionPerformed() } }// Ende der inneren Klasse }// Ende der äusseren Klasse Michael Weiss
Es gibt nicht nur Button-Events • Die Methode addActionListener() kann ausser beim JButton u.a. noch für folgende Ereignisauslöser benutzt werden: • JCheckBox • JCheckBoxMenuItem • JRadioButton • JRadioButtonMenuItem • JToggleButton • Timer (aus javax.swing) • Die meisten GUI-Elemente können jedoch verschiedene Ereignisse behandeln und benutzen daher speziell für sie zugeschnittene Listener. Für jeden Typ Listener muss man eine eigene innere private Klasse schreiben. Michael Weiss
Beispiel: KeyListener Um Tastatureingaben in einem Fenster zu verarbeiten, wird ein KeyListener benutzt. Das folgende einfache Programm schreibt den Namen der gedrückten Taste auf den Bildschirm. Michael Weiss
importjavax.swing.*;importjava.awt.*;importjava.awt.event.*;importjava.awt.geom.*;importjavax.swing.*;importjava.awt.*;importjava.awt.event.*;importjava.awt.geom.*; importjava.awt.font.*; publicclassTastatur { JFramehatJFrame; ZeichenJPanelhatZeichenJPanel; String zKeyText=" "; MyKeyListenerhatMyKeyListener; publicTastatur() { hatJFrame=newJFrame("Tastaturcode"); hatJFrame.setBounds(100,100,400,200); hatZeichenJPanel=newZeichenJPanel(); hatJFrame.add(hatZeichenJPanel); hatMyKeyListener=newMyKeyListener(); hatJFrame.addKeyListener(hatMyKeyListener); hatJFrame.setResizable(false); hatJFrame.setVisible(true); } privateclassZeichenJPanelextendsJPanel{ Rectangle2D.Float r; Font f =new Font("Arial",Font.PLAIN,30); publicvoidpaintComponent(Graphics g){ super.paintComponent(g); g.setColor(Color.black); g.setFont(f); r =(Rectangle2D.Float)((newTextLayout(zKeyText,f, // Platzbedarf des Textes ermitteln ((Graphics2D)g).getFontRenderContext())).getBounds()); g.drawString(zKeyText,(int)((this.getWidth()-r.getWidth())/2), // Text in der Fenstermitte (int)((this.getHeight()+r.getHeight())/2)); // platzieren } } Michael Weiss
privateclassMyKeyListenerimplementsKeyListener{ publicvoidkeyPressed(KeyEventevt){ zKeyText=evt.getKeyText(evt.getKeyCode()); hatZeichenJPanel.repaint(); } publicvoidkeyReleased(KeyEventevt){} publicvoidkeyTyped(KeyEventevt){}// würde benötigt, um z.B. die Eingabe eines ê oder @ // zu melden (beide brauchen Tastenkombinationen) }// Ende der inneren Klasse MyKeyListener staticpublicvoid main(String[]args) { new Tastatur(); } } • Obwohl die Methoden keyReleased()und keyTyped() gar nicht gebraucht werden, müssen sie implementiert werden, weil MyKeyListener das Interface KeyListener implementiert und dieses Interface entsprechende abstrakte Methoden aufweist. • Manche Listener haben sehr viele Methoden. Damit man nicht immer alle Methoden implementieren muss, die man gar nicht braucht, stellt Java für die Listener sogenannte Adapterklassen zur Verfügung, in denen alle abstrakten Interface-Methoden als leere Methoden implementiert sind. Z.B. gibt es eine Klasse KeyAdapter: importjava.awt.event.*; publicclassKeyAdapterimplementsKeyListener{ publicvoidkeyPressed(KeyEventevt){} publicvoidkeyReleased(KeyEventevt){} publicvoidkeyTyped(KeyEventevt){} } (Diese Klasse müssen Sie nicht mehr schreiben!) Michael Weiss
Wird die Adapterklasse benutzt, sieht MyKeyListener so aus: privateclassMyKeyListenerextendsKeyAdapter{ publicvoidkeyPressed(KeyEventevt){ zKeyText=evt.getKeyText(evt.getKeyCode()); hatZeichenJPanel.repaint(); } }// Ende der inneren Klasse MyKeyListener • In einem komplexen Anwendung besteht die Gefahr, dass durch die Verteilung der Ereignisbehandlung auf viele Listener-Methoden die Übersicht verloren geht. • Es ist daher vorteilhaft, die gesamte Ereignisbehandlung in einer einzigen Methode durchzuführen, welche von allen Listener-Methoden aufgerufen wird. • Das Beispiel auf den folgenden Seiten erweitert das Tastatur-Beispiel. Michael Weiss
importjavax.swing.*;importjava.awt.*;importjava.awt.event.*; importjava.awt.geom.*;importjava.awt.font.*;importjava.util.EventObject; publicclassTastaturUndMaus { privateJFramehatJFrame; privateZeichenJPanelhatZeichenJPanel; privateJPanelhatSouthJPanel; privateJButtonhatBeendenJButton; privateJCheckBoxhatKursivJCheckBox; private String zKeyText=" "; privateMyKeyListenerhatMyKeyListener; privateMyMouseListenerhatMyMouseListener; privateMyActionListenerhatMyActionListener; privatefinalstaticint KEY_PRESSED =0, MOUSE_CLICKED =1, MOUSE_ENTERED =2, MOUSE_EXITED =3,MOUSE_PRESSED =4, MOUSE_RELEASED =5, ACTION_EVENT =6; publicTastaturUndMaus() { hatJFrame=newJFrame("Tastatur und Maus"); hatJFrame.setBounds(100,100,400,300); hatJFrame.setLayout(newBorderLayout()); hatZeichenJPanel=newZeichenJPanel(); hatZeichenJPanel.setBackground(Color.WHITE); hatJFrame.add(hatZeichenJPanel,BorderLayout.CENTER); hatSouthJPanel=newJPanel(); hatJFrame.add(hatSouthJPanel,BorderLayout.SOUTH); hatSouthJPanel.setLayout(newFlowLayout(FlowLayout.CENTER,10,10)); // Layout, um GUI-Ele- // mentein "natürlicher" Grösseungezwungen gemeinsam in einem Panel zu platzieren hatKursivJCheckBox=newJCheckBox("Kursiv"); hatKursivJCheckBox.setFocusable(false); hatSouthJPanel.add(hatKursivJCheckBox); hatBeendenJButton=newJButton("Beenden"); hatBeendenJButton.setFocusable(false); hatSouthJPanel.add(hatBeendenJButton); hatJFrame.setResizable(false); hatJFrame.setVisible(true); Michael Weiss
hatMyActionListener=newMyActionListener(); hatMyKeyListener=newMyKeyListener(); hatMyMouseListener=newMyMouseListener(); hatKursivJCheckBox.addActionListener(hatMyActionListener); hatBeendenJButton.addActionListener(hatMyActionListener); hatZeichenJPanel.addMouseListener(hatMyMouseListener); hatZeichenJPanel.addKeyListener(hatMyKeyListener); hatZeichenJPanel.requestFocusInWindow(); } privateclassMyKeyListenerextendsKeyAdapter{ publicvoidkeyPressed(KeyEventevt){ verarbeiteEreignis(evt, KEY_PRESSED); } } privateclassMyMouseListenerimplementsMouseListener{ publicvoidmouseClicked(MouseEventevt){ verarbeiteEreignis(evt, MOUSE_CLICKED); } publicvoidmouseEntered(MouseEventevt){ verarbeiteEreignis(evt, MOUSE_ENTERED); } publicvoidmouseExited(MouseEventevt){ verarbeiteEreignis(evt, MOUSE_EXITED); } publicvoidmousePressed(MouseEventevt){ verarbeiteEreignis(evt, MOUSE_PRESSED); } publicvoidmouseReleased(MouseEventevt){ verarbeiteEreignis(evt, MOUSE_RELEASED); } } privateclassMyActionListenerimplementsActionListener{ publicvoidactionPerformed(ActionEventevt){ verarbeiteEreignis(evt, ACTION_EVENT); } } Michael Weiss
privatevoidverarbeiteEreignis(EventObjectevt,int type){ Object obj=evt.getSource(); if(evtinstanceofKeyEvent){ KeyEventke=(KeyEvent)evt; zKeyText=ke.getKeyText(ke.getKeyCode()); }elseif(evtinstanceofMouseEvent){ switch(type){ case MOUSE_CLICKED: zKeyText="Mouse clicked"; break; case MOUSE_ENTERED: zKeyText="Mouse entered"; break; case MOUSE_EXITED: zKeyText="Mouse exited"; break; case MOUSE_PRESSED: zKeyText="Mouse pressed"; break; case MOUSE_RELEASED: zKeyText="Mouse released"; break; default: zKeyText="ERROR!"; break; } }elseif(evtinstanceofActionEvent){ if(obj==hatKursivJCheckBox){ hatZeichenJPanel.setItalicFont(hatKursivJCheckBox.isSelected()); }elseif(obj==hatBeendenJButton){ System.exit(0); } } hatZeichenJPanel.repaint(); } Mit dem Schlüsselwort instanceoflässt sich testen, ob ein Objekt zu einer bestimmten Klasse gehört, ob also z.B. evt ein Objekt der Klasse KeyEvent ist. Michael Weiss
privateclassZeichenJPanelextendsJPanel{ Rectangle2D.Float r; Font fp=new Font("Arial",Font.PLAIN,30); Font fi =new Font("Arial",Font.ITALIC,30); Font f =fp; publicvoidsetItalicFont(booleanyesorno){ f =yesorno? fi :fp; // Falls yesorno == true ist, setze f = fi, sonst setze f = fp. } publicvoidpaintComponent(Graphics g){ super.paintComponent(g); g.setColor(Color.black); g.setFont(f); r =(Rectangle2D.Float)((newTextLayout(zKeyText,f, ((Graphics2D)g).getFontRenderContext())).getBounds()); g.drawString(zKeyText,(int)((this.getWidth()-r.getWidth())/2), (int)((this.getHeight()+r.getHeight())/2)); } } staticpublicvoid main(String[]args) { try{// 5 Zeilen, damit sich das Programm ins OS integriert UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }catch(Exception e){ System.err.println("no system look and feel available"); }; newTastaturUndMaus(); } } Michael Weiss
Timer-Events • Der Timerist ein Objekt, welches die Fähigkeit besitzt, alle t Millisekunden die Methode actionPerformed() einer selbst geschriebenen Klasse (z.B. MyActionListener) aufzurufen, welche das Interface ActionListener implementiert. • Im Gegensatz zum JButton übergibt man den ActionListener nicht mit einer Methode addActionListener, sondern bereits im Konstruktor. • Mit start() und stop() kann der Timer ein- und ausgeschaltet werden, mit setDelay(int t) kann die Anzahl Millisekunden verändert werden, nach denen der Timer jeweils die Methode actionPerformed() aufruft. • Im folgenden Beispiel zählt der Timer Sekunden und gibt sie auf dem Bildschirm aus. Michael Weiss
importjavax.swing.*; importjava.awt.*; importjava.awt.event.*; importjava.awt.geom.*;importjava.awt.font.*;importjava.util.EventObject; publicclassZaehler { privateJFramehatJFrame; privateZeichenJPanelhatZeichenJPanel; privateJPanelhatSouthJPanel; privateJButtonhatBeendenJButton; private String zText="0"; privateMyActionListenerhatMyActionListener; private Timer hatTimer; privateint t =0; publicZaehler() { hatJFrame=newJFrame("Zähler"); hatJFrame.setBounds(100,100,400,300); hatJFrame.setLayout(newBorderLayout()); hatZeichenJPanel=newZeichenJPanel(); hatJFrame.add(hatZeichenJPanel,BorderLayout.CENTER); hatSouthJPanel=newJPanel(); hatJFrame.add(hatSouthJPanel,BorderLayout.SOUTH); hatSouthJPanel.setLayout(newFlowLayout(FlowLayout.CENTER,10,10)); hatBeendenJButton=newJButton("Beenden"); hatBeendenJButton.setFocusable(false); hatSouthJPanel.add(hatBeendenJButton); hatJFrame.setResizable(false); hatJFrame.setVisible(true); hatMyActionListener=newMyActionListener(); hatTimer=new Timer(1000,hatMyActionListener); hatTimer.start(); hatBeendenJButton.addActionListener(hatMyActionListener); } Michael Weiss
privateclassMyActionListenerimplementsActionListener{ publicvoidactionPerformed(ActionEventevt){ verarbeiteEreignis(evt); } } privatevoidverarbeiteEreignis(EventObjectevt){ Object obj=evt.getSource(); if(obj==hatTimer){ t++; zText= t+"";// einfachste Methode, eine Zahl in einen String zu verwandeln hatZeichenJPanel.repaint(); }elseif(obj==hatBeendenJButton){ System.exit(0); } } privateclassZeichenJPanelextendsJPanel{ Rectangle2D.Float r; Font f =new Font("Arial",Font.PLAIN,30); publicvoidpaintComponent(Graphics g){ super.paintComponent(g); g.setColor(Color.black); g.setFont(f); r =(Rectangle2D.Float)((newTextLayout(zText,f, ((Graphics2D)g).getFontRenderContext())).getBounds()); g.drawString(zText,(int)((this.getWidth()-r.getWidth())/2), (int)((this.getHeight()+r.getHeight())/2)); } } Michael Weiss
staticpublicvoid main(String[]args) { try{// 5 Zeilen, damit sich das Programm ins OS integriert UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }catch(Exception e){ System.err.println("no system look and feel available"); }; newZaehler(); } } In diesem Fall erzeugen alle Ereigniserzeuger (Timer und JButton) ActionEvents, weswegen die Methode verarbeiteEreignis() auch ohne Verlust an Übersichtlichkeit in actionPerformed() hätte integriert werden können. Michael Weiss