Liste P: Programmieren mit Java WS 2001/2002 Prof. Dr. V. Turau FH Wiesbaden Kapitel 7: Ereignis-basierte Kommunikation Folie 157 : Beispiel: Die Klasse MouseEvent Wichtige Methoden der Klasse MouseEvent: Methode public int getClickCount() Bedeutung Anzahl der Mausclicks für dieses Ereignis public int getX() x-Koordinate des Ereignisses publicint getY() y-Koordinate des Ereignisses public long getWhen() public boolean isAltDown() public boolean isShiftDown() public Component getComponent() Zeitstempel Wurde die Alt-Taste beim Klicken gedrückt Wurde die Shift-Taste beim Klicken gedrückt Ereignisquelle Folie 158 : Demo: MouseEvents public class MouseEventDemo extends JFrame implements MouseListener { public void init() { Feld feld = new Feld(new Color(0.98f, 0.97f, 0.85f)); contentPane.add(feld); feld.addMouseListener(this); addMouseListener(this); } public void mousePressed(MouseEvent e) { ausgabe("Mouse pressed (Anzahl clicks: " + e.getClickCount() + ", (" + e.getX() + ", " + e.getY() + "))", e); } public void mouseReleased(MouseEvent e) { ausgabe("Mouse released (Anzahl clicks: " + e.getClickCount() + ", (" + e.getX() + ", " + e.getY() + "))", e); } Folie 159 : Demo: MouseEvents (Forts.) public void mouseEntered(MouseEvent e) { ausgabe("Mouse entered", e); } public void mouseExited(MouseEvent e) { ausgabe("Mouse exited", e); } public void mouseClicked(MouseEvent e) { ausgabe("Mouse clicked (Anzahl clicks: " + e.getClickCount() + ")", e); } void ausgabe(String beschreibung, MouseEvent e) { System.out.println(beschreibung + " festgestellt in " + e.getComponent().getClass().getName()); } } Folie 160 : Demo: MouseEvents (Ausgabe) Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse Mouse entered festgestellt in MouseEventDemo entered festgestellt in Feld exited festgestellt in Feld pressed (Anzahl clicks: 1, (303, 261)) festgestellt in MouseEventDemo released (Anzahl clicks: 1, (193, 289)) festgestellt in MouseEventDemo entered festgestellt in Feld pressed (Anzahl clicks: 1, (194, 131)) festgestellt in Feld released (Anzahl clicks: 1, (194, 131)) festgestellt in Feld clicked (Anzahl clicks: 1) festgestellt in Feld pressed (Anzahl clicks: 2, (194, 131)) festgestellt in Feld released (Anzahl clicks: 2, (194, 131)) festgestellt in Feld clicked (Anzahl clicks: 2) festgestellt in Feld exited festgestellt in Feld exited festgestellt in MouseEventDemo Folie 161 : Ereignisempfänger In vielen Fällen ist man nicht an allen Ereignissen einer Kategorie interessiert (z.B. nur an mouseClicked) Trotzdem muss man alle Methoden implementieren Abhilfe: Adapterklassen Sie implementieren alle Methoden eines Interfaces (jeweils keine Aktionen) Werden Ereignisempfänger als Unterklassen solcher Adapterklassen realisiert, so muss man nur noch die gewünschten Methoden überschreiben Adapterklassen sind nur für Listener-Interfaces mit mehreren Methoden interessant (Warum?) Folie 162 : Adapterklassen Beispiel: MouseAdapter public class MouseAdapter implements MouseListener { public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } Anwendung: MouseEventDemo (nur mouseClicked-Events) public class MouseEventDemo extends JFrame, MouseAdapter { .. Problem: MouseEventDemo kann nur eine Oberklasse haben Folie 163 : Adapterklassen Lösung: Getrennte Ereignisempfängerklasse public class MouseEventHandling extends MouseAdapter { public void mouseClicked(MouseEvent e) { System.out.println("Mouse clicked (Anzahl clicks: " + e.getClickCount() + ", (" + e.getX() + ", " + e.getY() + "))" + e.getComponent().getClass().getName()); } } Verwendung in MouseEventDemo: public void init() { Feld feld = new Feld(new Color(0.98f, 0.97f, 0.85f)); contentPane.add(feld); MouseEventHandling meh = new MouseEventHandling(); feld.addMouseListener(meh); addMouseListener(meh); } Folie 164 : Getrennte Ereignisempfängerklassen Vorteile: Bessere Trennung von Struktur und Funktion Verwendung von Adapterklassen ist möglich Nachteile: Ereignisempfängerklasse hat keinen Zugriff auf die Komponenten der Anwendung Folie 165 : Getrennte Ereignisempfängerklassen (Beispiel) Beispiel: KommunikationsFrame mit Button zum Löschen public class KommunikationsFrame extends JFrame implements ActionListener { JTextField f1, f2; private Container contentPane; private final String RESET = "Löschen"; public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals(RESET)) { f1.setText(""); f2.setText(""); } else f2.setText(f1.getText().toUpperCase()); } Ereignisempfängerklasse braucht Zugriff auf f1, f2 Folie 166 : Getrennte Ereignisempfängerklassen (Beispiel) Lösung: Private Instanzvariablen public class KommunikationsHandler implements ActionListener { JTextField f1, f2; public KommunikationsHandler(JTextField f1, JTextField f2) { this.f1 = f1; this.f2 = f2; } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals(RESET)) { f1.setText(""); f2.setText(""); } else f2.setText(f1.getText().toUpperCase()); } Folie 167 : Getrennte Ereignisempfängerklassen (Verwendung) public void init() { action = new JButton("Mache zu Großbuchstaben"); contentPane.add(action); JButton reset = new JButton("Löschen"); contentPane.add(reset); JButton b = new JButton("Mache zu Großbuchstaben"); contentPane.add(b); f1 = new JTextField(30); contentPane.add(f1); f2 = new JTextField(30); f2.setEditable(false); contentPane.add(f2); KommunikationsHandler kh = new KommunikationsHandler(f1, f2); action.addActionListener(kh); reset.addActionListener(kh); } Folie 168 : Ereignisempfängerklassen Benötigen Ereignisempfängerklassen den Zugriff auf Instanzvariablen, so ist der Umgang mit getrennten Ereignisempfängerklassen oft etwas umständlich Abhilfe: Innere Klassen Anonyme innere Klassen Diese Konzepte wurden in Java 1.1 erstmals eingeführt Folie 169 : Innere Klassen Innere Klassen sind Klassen in Klassen Struktur: class Klasse { . . . class InnereKlasse { . . . } } Verwendung: Die innere Klasse hat nur Bedeutung innerhalb der Klasse Innere Klassen haben direkten Zugriff auf Instanzvariablen und Instanzmethoden der umgebenden Klasse Folie 170 : Innere Klassen (Anwendung) Anwendung mit zwei Buttons (Wandle in Großbuchstaben und Reset public class KommunikationsFrame extends JFrame { JTextField f1, f2; ..... class KommunikationsHandlerReset implements ActionListener { public void actionPerformed(ActionEvent e) { f1.setText(""); f2.setText(""); } } class KommunikationsHandlerDo implements ActionListener { public void actionPerformed(ActionEvent e) { f2.setText(f1.getText().toUpperCase()); } } Folie 171 : Innere Klassen (Anwendung) public void init() { JButton b = new JButton("Mache zu Großbuchstaben"); contentPane.add(b); b.addActionListener(new KommunikationsHandlerDo()); JButton reset = new JButton(RESET); contentPane.add(reset); reset.addActionListener(new KommunikationsHandlerReset()); f1 = new JTextField(30); contentPane.add(f1); f2 = new JTextField(30); f2.setEditable(false); contentPane.add(f2); } } Vorteil: Code zur Ereignisbehandlung an einer Stelle konzentriert Folie 172 : Anonyme Klassen Anonyme Klassen sind innere Klassen ohne Namen Anonyme Klassen implementieren ein Interface oder sind Unterklassen einer existierenden Klasse Anonyme Klassen erschweren die Lesbarkeit des Codes und sollten nur für sehr kurze Klassenvereinbarungen verwendet werden Hauptanwednung: Ereignisbehandlung Folie 173 : Anonyme Klassen (Beispiel 1) Anwendung mit zwei Buttons (Wandle in Großbuchstaben und Reset public class KommunikationsFrame extends JFrame { JTextField f1, f2; .... public void init() { JButton b = new JButton("Mache zu Großbuchstaben"); contentPane.add(b); b.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { f2.setText(f1.getText().toUpperCase()); } } ); JButton reset = new JButton(RESET); contentPane.add(reset); reset.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { f1.setText(""); f2.setText(""); } } ); ... Folie 174 : Anonyme Klassen (Beispiel 2) Anwendung Anwendung beenden, wenn entsprechendes Icon gedrückt wird Hierzu muss das Interface WindowListener implementiert werden (7 Methoden) In den meisten Fällen benötigt man nur die Methode windowClosing(WindowEvent e) In diesem Fall bietet sich die Verwendung der Adapterklasse WindowAdapter an Folie 175 : Anonyme Klassen (Beispiel 2) public class KommunikationsFrame extends JFrame { ... public KommunikationsFrame5 () { contentPane = getContentPane(); contentPane.setLayout(new FlowLayout()); contentPane.setBackground(Color.black); setTitle("Kommunikation"); addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); } ... Folie 176 : Ereignisempfängerklassen Möglichkeiten zur Implementierung von Ereignisempfängerklassen Eine Anwendungsklasse verarbeitet alle auftretenden Ereignissee selber (Hierzu muss sie die entsprechenden Listener-Interfaces implementieren) Eine seperate Klasse verarbeitet alle auftretenden Ereignisse (Hierzu muss sie und nicht die Anwendungsklasse die entsprechenden Listener-Interfaces implementieren) Die Implementierung kann durch die Verwendung von Adapterklassen und inneren/anonymen Klassen vereinfacht werden Folie 177 : Animationen Zur Erstellung von Animationen benötigt man eine periodische Ereignisquelle Die Klasse Timer kann dazu verwendet werden, in vorgegebenen Zeitabständen ein ActionEvent auszulösen Konstruktor der Klasse Timer public Timer(int verzögerung, ActionListener listener) Nach jeweils verzögerung Millisekunden wird ein ActionEvent generiert und listener wird benachrichtigt Zusätzlicher Listener können mit addActionListener registriert werden Timer können mit den Methoden start und stop jederzeit gestartet bzw. angehaltem werden Folie 178 : Beispiel Eine Folge von Bildern soll hintereinander angezeit werden Hierdurch entsteht der Eindruck der Bewegung Ein Timer steuert den Bildwechsel Die Bildfrequenz kann über einen Slider gesteuert werden Ein Slider erlaubt die Einstellung einer nummerischen Größe, nach Veränderungen wird ein ChangeEvent ausgelöst Wird die Anwendung zu einem Icon verkleinert, so soll die Animation angehalten werden Folie 179 : Sliderdemo Folie 180 : Sliderdemo Beteiligte Komponenten: Timer: löst ActionEvents aus Slider: löst ChangeEvents aus JFrame: löst WindowEvents aus Implementierung der Listener: ActionListener durch die Klasse selber ChangeListener als innere Klasse WindowListener als anonyme Klasse Folie 181 : Sliderdemo (Erzeugung der Komponenten) public class SliderDemo extends JFrame implements ActionListener { static final int FPS_INIT = 10; int frameNumber = 0; int delay = 1000 / FPS_INIT; Timer timer; boolean frozen = false; JLabel picture; private JPanel contentPane; public SliderDemo(String title) { contentPane = (JPanel)getContentPane(); setTitle(title); JLabel sliderLabel = new JLabel("Frames Pro Sekunde", JLabel.CENTER); sliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT); JSlider framesPerSecond = new JSlider(JSlider.HORIZONTAL, 0, 50, FPS_INIT); // Markierungen des Sliders framesPerSecond.setMajorTickSpacing(10); framesPerSecond.setMinorTickSpacing(1); framesPerSecond.setPaintTicks(true); framesPerSecond.setPaintLabels(true); Folie 182 : Sliderdemo (Erzeugung der Komponenten) picture = new JLabel(new ImageIcon( "images/T" + frameNumber + ".gif"), JLabel.CENTER); picture.setAlignmentX(Component.CENTER_ALIGNMENT); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); contentPane.add(sliderLabel); contentPane.add(framesPerSecond); contentPane.add(picture); contentPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); timer = new Timer(delay, this); timer.setInitialDelay(delay * 20); timer.setCoalesce(true); Folie 183 : Sliderdemo (Verarbeitung der Window-Events) addWindowListener( new WindowAdapter() { public void windowIconified(WindowEvent e) { stopAnimation(); } public void windowDeiconified(WindowEvent e) { startAnimation(); } public void windowClosing(WindowEvent e) { System.exit(0); } } ); }// Ende des Konstruktors Folie 184 : Sliderdemo (Innere Klasse für Slider-Änderungen) class SliderListener implements ChangeListener { public void stateChanged(ChangeEvent e) { JSlider source = (JSlider)e.getSource(); //Wenn der Benutzer gerade den Wert verändert machen wir nichts if (!source.getValueIsAdjusting()) { int fps = (int)source.getValue(); if (fps == 0) { if (!frozen) stopAnimation(); } else { delay = 1000 / fps; timer.setDelay(delay); timer.setInitialDelay(delay * 20); if (frozen) startAnimation(); } } } } Folie 185 : Sliderdemo (Verarbeitung der Timer-Events) public void actionPerformed(ActionEvent e) { if (frameNumber==13) { frameNumber = 0; } else { frameNumber++; } picture.setIcon(new ImageIcon("images/T" + frameNumber + ".gif")); } public void startAnimation() { timer.start(); frozen = false; } public void stopAnimation() { timer.stop(); frozen = true; } Folie 186 : Sliderdemo (Hauptprogramm) public static void main(String[] args) { SliderDemo animator = new SliderDemo("SliderDemo"); animator.pack(); animator.setVisible(true); animator.startAnimation(); } Programm über Web-Seite verfügbar