Benutzeroberflächen 1

Werbung
Ereignisbehandlung
– 21 –
3 Ereignisbehandlung
Dieses Kapitel beschäftigt sich mit der Ereignisbehandlung, d.h. der Reaktion
eines Programms auf Eingaben durch benutzende Personen. Nach einigen
ersten Beispielen hierzu aus dem Lehrbuch wird das Thema durch zwei
Betrachtungen vertieft:
• Zunächst wird gezeigt, wie die Ereignisbehandlung in Swing gemäß des
Entwurfsmusters Observer (Beobachter) gestaltet ist. Sie werden erfahren,
was Entwurfsmuster im Allgemeinen sind, was das Entwurfsmuster
Beobachter ist und wie es mit Hilfe eines Klassendiagramms beschrieben
werden kann.
• Anschließend werden drei Möglichkeiten diskutiert, wie auf unterschiedliche Ereignisse unterschiedlich reagiert werden kann.
Lesen Sie den Abschnitt 4.1.2 (S. 180-184) des Lehrbuchs.
Auch hier wollen wir noch einmal zu Übungszwecken eines der Programme
durch ein Klassendiagramm darstellen. Abbildung 3.1 zeigt das Diagramm
zu dem Programm aus Listing 4.4.
ActionListener
JFrame
ButtonExample1
benutzt
MyHandler
benutzt
JButton
Abbildung 3.1: Klassendiagramm zum Programm aus Listing 4.4 des Lehrbuchs
3.1 Entwurfsmuster Observer
Für Programmierneulinge könnten die Programme aus den Listings 4.4, 4.5
und 4.6 ungewöhnlich sein: Es wird eine Methode actionPerformed definiert,
die an keiner Stelle im selbst geschriebenen Programmcode aufgerufen wird,
die aber bei Ausführung des Programms dennoch irgendwie aktiviert wird.
Dieses Prinzip wird manchmal auch als das Hollywood-Prinzip bezeichnet:
„Don’t call us – we call you“. Dies soll angeblich in Hollywood den Kandidatinnen und Kandidaten nach dem Vorsprechen für eine Filmrolle (Casting)
mit auf den Heimweg gegeben werden.
– 22 –
Frameworks
Entwurfsmuster
Entwufsmuster
Observer
(Beobachter)
Grundlagen der Programmierung grafischer Benutzeroberflächen in Java
Dieses Prinzip ist ein Wesensmerkmal von so genannten Frameworks wie
Swing. In einer Initialisierungsphase (in unserem Beispiel in der MainMethode) werden für unterschiedliche Ereignisse (in unserem Beispiel das
Drücken eines Buttons) anwendungsspezifische Reaktionen angemeldet (in
unserem Beispiel durch Aufruf der Methode addActionListener). Nach der
Initialisierungsphase liegt die Steuerung des Programms nicht mehr in den
Händen des Anwendungscodes, sondern des Frameworks. Das Framework
verarbeitet die Ereignisse und ruft entsprechend den angemeldeten Reaktionen anwendungsspezifische Codeteile auf.
Dieses Verhalten wird durch das Entwurfsmuster Observer (Beobachter)
beschrieben. Entwurfsmuster sind auf Erfahrung basierende Vorschläge, wie
bestimmte Aufgabenstellungen in Software umgesetzt werden können.
Entwurfsmuster haben ein höheres Abstraktionsniveau als Klassenbibliotheken, da Entwurfsmuster keinen Programmcode darstellen, sondern nur
beschreiben, wie Klassen und Schnittstellen in einer bestimmten Situation
zusammenwirken sollen. Die konkreten Klassen und Schnittstellen mit ihren
Methoden sind aber abhängig von der jeweiligen Anwendung. Die in der
Beschreibung des Entwurfsmusters verwendeten Klassen und Schnittstellen
sowie die verwendeten Methoden sind nur beispielhaft zu verstehen.
Natürlich werden solche Entwurfsmuster mit Hilfe von Klassendiagrammen
beschrieben. Das Klassendiagramm zum Entwurfsmuster Observer ist in
Abbildung 3.2 abgebildet.
Observable
*
Observer1
Observer
Observer2
Abbildung 3.2: Klassendiagramm zum Entwurfsmuster Observer (Beobachter)
Observer ist eine Schnittstelle mit mindestens einer Methode, die beim
Eintritt eines bestimmten Ereignisses aufgerufen werden soll. Observer1 und
Observer2 sind stellvertretend zwei Klassen, welche die Observer-Schnittstelle implementieren und entsprechend Implementierungen für die
Methode(n) der Observer-Schnittstelle beinhalten. Observable ist eine Klasse,
die sich beobachten lässt (d.h. etwas Beobachtbares). Dazu können mehrere
Objekte des Typs Observer an Observable angemeldet werden, wobei sich
das Observable-Objekt alle angemeldeten Objekte merken muss, da es diese
bei späterem Eintreten des interessierenden Ereignisses benachrichtigen
muss.
Ereignisbehandlung
Zur Verdeutlichung entwickeln wir dazu ein Beispiel. Als beobachtbares
Objekt stellen wir uns einen Messfühler vor, der periodisch eine bestimmte
Größe (Temperatur, Druck, Windgeschwindigkeit, Anzahl der vorbeifahrenden Autos pro Sekunde usw.) misst. An diesem Messfühler können sich
Beobachter anmelden, die unterschiedlich auf die Meldung neuer Messwerte
reagieren können (Wert in eine Log-Datei schreiben, eine grafische Darstellung der N letzten Werte aktualisieren, bei Über- bzw. Unterschreitung
bestimmter Grenzwerte Alarm durch Senden einer SMS auslösen usw.). Alle
Beobachter müssen die Schnittstelle MeasurementObserver und damit die
Methode valueMeasured implementieren:
interface MeasurementObserver
{
public void valueMeasured(double newValue);
}
class MeasurementDevice
{
private ArrayList<MeasurementObserver> observers;
public MeasurementDevice()
{
observers = new ArrayList<MeasurementObserver>();
}
public void addMeasurementObserver(MeasurementObserver mo)
{
observers.add(mo);
}
private void fireEvent(double newValue)
{
for(MeasurementObserver mo : observers)
{
mo.valueMeasured(newValue);
}
}
public void doMeasuring()
{
for(int i = 0; i < 3; i++)
{
//random value between -100 and +100:
double measuredValue = -100 + Math.random() * 200;
fireEvent(measuredValue);
}
}
}
class LogObserver implements MeasurementObserver
{
public void valueMeasured(double newValue)
{
System.out.println("Eintrag in Log-Datei: "
+ newValue);
}
}
– 23 –
Messfühler als
Beispiel für das
Entwurfsmuster
Beobachter
– 24 –
Grundlagen der Programmierung grafischer Benutzeroberflächen in Java
class GraphicObserver implements MeasurementObserver
{
public void valueMeasured(double newValue)
{
System.out.println("Aktualisierung der Grafik mit "
+ "neuem Wert: " + newValue);
}
}
class AlarmObserver implements MeasurementObserver
{
private double minValue;
private double maxValue;
public AlarmObserver(double minValue, double maxValue)
{
this.minValue = minValue;
this.maxValue = maxValue;
}
public void valueMeasured(double newValue)
{
if(newValue > maxValue || newValue < minValue)
{
System.out.println("Alarm (z.B. als SMS oder "
+ "E-Mail) senden mit Wert: "
+ newValue);
}
}
}
public class DesignPatternObserverExample
{
public static void main(String[] args)
{
MeasurementDevice device = new MeasurementDevice();
LogObserver logger = new LogObserver();
device.addMeasurementObserver(logger);
GraphicObserver graphics = new GraphicObserver();
device.addMeasurementObserver(graphics);
AlarmObserver alarm = new AlarmObserver(-50, 50);
device.addMeasurementObserver(alarm);
device.doMeasuring();
}
}
Die aus MeasurementObserver abgeleiteten Klassen sind sozusagen die
anwendungsspezifischen Codeteile, während der Rest das Mess-Framework
darstellt.
Ereignisbehandlung
Zusammenfassung
Die Swing-Interaktionselemente wie z.B. ein JButton können durch
Aktionen der Benutzerinnen und Benutzer Ereignisse auslösen. Sie stellen so genannte Ereignisquellen dar. An diese Ereignisquellen können
Objekte angemeldet werden, die abhängig von der Ereignisquelle eine
bestimmte Schnittstelle implementieren müssen. Tritt das Ereignis ein,
werden alle angemeldeten Objekte durch Aufruf einer der Methoden
der implementierten Schnittstelle über das Eintreten des Ereignisses
informiert. Das Grundprinzip dieses Zusammenwirkens wird im
Entwurfsmuster Beobachter festgehalten. Die Ereignisquellen spielen
hierbei die Rolle der Beobachtbaren, während die benachrichtigten
Objekte Beobachter genannt werden. Ein vollständig selbst implementiertes Beispiel für dieses Zusammenspiel verdeutlicht das Entwurfsmuster Beobachter.
"
Übungsaufgaben
3.1
Welche Ausgabe kann das Beispielprogramm dieses Abschnitts erzeugen? Überlegen Sie zuerst und prüfen Sie Ihre Überlegungen dann
durch Ausführung des Programms nach!
3.2
Zeichnen Sie ein Sequenzdiagramm für den Aufruf der Methode
doMeasuring auf dem Objekt device in der Main-Methode! Nehmen Sie
dabei der Einfachheit halber an, dass die Schleife in der Methode
doMeasuring nur ein einziges Mal durchlaufen wird! Zeichnen Sie die
Aufrufe der Methoden Math.random und System.out.println nicht in
das Sequenzdiagramm ein!
3.2 Unterschiedliche Reaktionen auf unterschiedliche Ereignisse
In den Beispielen des Abschnitts 4.1.2 aus dem Lehrbuch gibt es immer nur
eine einzige Ereignisquelle. Wenn aber mehrere Ereignisquellen vorhanden
sind (z.B. mehrere Buttons), dann will man in der Regel auf das Drücken der
Buttons in unterschiedlicher Weise reagieren (sonst bräuchte man ja nur
einen Button). Es gibt prinzipiell drei Möglichkeiten, mit diesem Problem
umzugehen:
– 25 –
Herunterladen