Praktische Informatik 1


Kapitel 8: Modellbasierte Softwareentwicklung



Yüklə 3,49 Mb.
səhifə21/25
tarix08.08.2018
ölçüsü3,49 Mb.
#61957
1   ...   17   18   19   20   21   22   23   24   25

Kapitel 8: Modellbasierte Softwareentwicklung

8.1 UML Klassendiagramme und Zustandsmaschinen


– entfällt –

8.2 Codegenerierung und Modelltransformationen


– entfällt –

Kapitel 9: Spezielle Programmierkonzepte

9.1 Benutzungsschnittstellen, Ereignisbehandlung


Viele der bislang betrachteten Programme bekamen ihre Eingabe als Parameter beim Aufruf, und lieferten ihre Ausgabe als Ergebnis bei Terminierung ab. Interaktive Programme kommunizieren mit den Benutzern über Ein- und Ausgabegeräte. Bei textbasierten Programmen erfolgt die Ausgabe oft in einem Textfeld („Konsole“); in Java etwa durch die Methode System.out.println(…). Die Komponente „out“ bezeichnet dabei den Standard-Ausgabestrom des Systems, vom Typ PrintStream. (Ähnlich bezeichnet err die Standardausgabe für Fehlermeldungen, und in den Standard-Eingabestrom. Über System.in lassen sich also vom Benutzer auf der Konsolschnittstelle eingegebene Werte einlesen. Beim Aufruf der Methode System.in.read wartet das Programm, bis der Benutzer Text eingibt und die Eingabetaste drückt.

Das folgende Programmfragment liest eine Zahl von der Konsole und gibt sie (um 1 erhöht) wieder aus:



int i=0;

int len = 20; byte[] b = new byte[len];

try {

int l = System.in.read(b, 0, len)-1;

String s = new String(b,0,l-1);

i = Integer.parseInt(s);

} catch (IOException e) {}

System.out.println(i+1);

System.in.read erwartet als Eingabe eine Referenz auf (hinreichend großes) Byte-Array, liest die Bytes von Konsole und gibt die Anzahl der gelesenen Zeichen (inklusive dem „Zeilenwechsel-Zeichen“) als Ergebnis zurück. Die tatsächlich eingegebene Zeichenzahl ist also um eins niedriger. Da Benutzerinteraktionen grundsätzlich Fehler erzeugen können, müssen sie in einen try–Block eingeschlossen werden, siehe Abschnitt 9.3. Mit der Anweisung String s = new String(b,0,l-1); werden die Zeichen 0 bis l-1 dieses Byte-Arrays in eine Zeichenreihe umgewandelt. Integer.parseInt(s) schließlich wandelt diese Zeichenreihe (sofern sie nach den Java-Regeln eine korrekte Zahl darstellt) in ein Objekt der Art Integer um. Etwas einfacher wird es, wenn wir aus System.in einen BufferedReader erstellen, da wir mit diesem direkt Strings einlesen können (Methode readLine()):

BufferedReader konsole = new BufferedReader(
new InputStreamReader (System.in)) ;

try {i = Integer.parseInt(konsole.readLine());}

catch (IOException e){}
Für viele Programme ist allerdings eine rein textbasierte Ein-Ausgabe nicht ausreichend. Üblicherweise wird ein Programm heute mit dem Benutzer über eine GUI (graphical user interface) interagieren, d.h. das Programm stellt graphische Elemente wie Knöpfe, Regler, Textfelder usw. auf dem Bildschirm zur Verfügung, über die die Mensch-Maschine-Kommunikation erfolgt. Dazu gibt es inzwischen sehr viele verschiedene Möglichkeiten. Programme, die eine GUI bereitstellen, reagieren auf Ereignisse (events). In der ablaufgesteuerten Programmierung ist ein Programm eine Folge von Anweisungen (d.h. Methodenaufrufen); bei der ereignisgesteuerten Programmierung ist ein Programm eine Sammlung von Behandlungsroutinen (= Methoden) für verschiedene Ereignisse der GUI.
Typische Ereignisse (events) sind:


  • Mausklick, Mausziehen, Mausbewegung, Return-Taste, Tastatureingabe, …

  • Signale des Zeitgebers, Unterbrechungen, …

  • programmerzeugte Ereignisse

In einem ereignisgesteuerten Programm wird für jedes Fenster ein Prozess gestartet, der auf die Ereignisse wartet und sie bearbeitet. Für jedes Ereignis wird eine Bearbeitungsroutine (event handler) registriert, die beim Eintreten des Ereignisses bestimmte Aktionen auslöst.
Zur konkreten Realisierung von GUIs in Java existieren zwei weit verbreitete Bibliotheken: AWT und Swing.

  • AWT (Abstract Window Toolkit) ist eine plattformunabhängige schwergewichtige Bibliothek (stützt sich auf Betriebssystem-Routinen ab)

  • Swing ist Bestandteil der Java-Runtime (leichtgewichtig), hat ein spezifisches „Look-and-Feel“ sowie gegenüber AWT einige zusätzliche Komponenten und ist generell „moderner“.

Das folgende Programm erzeugt zwei Fenster und zeigt sie auf dem Bildschirm an:



import java.awt.*;

class Fenster extends Frame {

Fenster (String titel) {



super(titel); //Setzen des Textes in der Titelzeile des Fensters

setSize(160,120); // Größe in Pixeln einstellen

setVisible(true); // das Fenster ist anfangs sichtbar

}

}


public class FensterDemo {

public static void main(String[] args) {

Fenster zumHof = new Fenster("zum Hof");

Fenster zurTür = new Fenster("zur Tür");

}

}



Damit diese Fenster z.B. auf das Schaltfeld „Beenden“ (in Windows: das rote Kreuz rechts oben) reagieren, muss man ihnen einen entsprechenden Fensterbeobachter zuordnen:

import java.awt.*;

import java.awt.event.*;

class FensterBeobachter extends WindowAdapter{

public void windowClosing(WindowEvent e) {// Schließen des Fensters

System.exit(0); // Aktion beim Schließen: z.B. Programmende

}

}

class Fenster extends Frame {



Fenster (String titel) {

FensterBeobachter fb = new FensterBeobachter();

addWindowListener(fb); // hier wird der Beobachter registriert

super(titel); ...); // wie oben

}

}



public class FensterDemo {

public static void main(String[] args) {

Fenster f = new Fenster("PI-1");

}

}
Innerhalb einem Fenster kann man verschiedene Knöpfe definieren. Jedem Knopf wird ein Knopfbeobachter zugewiesen, der auf das Ereignis „Knopf wird gedrückt“ reagiert:


class KnopfBeobachter implements ActionListener{

public void actionPerformed(ActionEvent e) {

System.out.println("Knopf gedrückt!"); //Aktion beim Klick

}

}
class Fenster extends Frame {



Fenster (String titel) {

...

Button k = new Button("Knopf!"); // neuen Knopf definieren

KnopfBeobachter kb = new KnopfBeobachter(); // neuer Beobachter kb

k.addActionListener(kb); // Beobachter kb Knopf k zuordnen

add(k); // Knopf zum aktuellen Fensterobjekt hinzufügen

}

}


Zur Verwendung aktiver Komponenten (Knöpfe, Schalter, …) in einer GUI sind also immer folgende Schritte nötig

  • Erzeugen – Button b = new Button ()

  • Ereignisbehandlung zuordnen – b.add.ActionListener(...)

  • Hinzufügen zum Fenster – add(b)

  • Methode zur Ereignisbehandlung schreiben

    • actionPerformed, itemStateChanged, adjustmentValueChanged,...

Jedem Schaltelement können beliebig viele Beobachter zugeordnet werden, die auf die unterschiedlichen Ereignisse reagieren. Beobachter müssen nicht als eigene Variablen benannt werden, sondern können auch anonym existieren:


setLayout(new FlowLayout());

Button knopf2 = new Button("Knopf2");

knopf2.addActionListener (new ActionListener (){

public void actionPerformed(ActionEvent e) {

System.out.println("Knopf2 gedrückt!"); }});

add(knopf2);

...
Für die graphische Gestaltung der Benutzerinteraktion ist eine Vielzahl von automatischen Optionen verfügbar; zu Layout-Fragen konsultiert man am besten die entsprechende Dokumentation. Gängige Layouts sind z.B.




  • BorderLayout

  • FlowLayout

  • GridLayout

  • CardLayout

  • GridBagLayout

Es ist auch eine Schachtelung von Layouts möglich; darüber hinaus lässt sich natürlich jedes Objekt auch absolut innerhalb des Fensters positionieren.


Als ein etwas größeres Beispiel betrachten wir jetzt eine Stoppuhr, die durch entsprechende Mausklicks gestartet und gestoppt wird, und bei der die Zeitanzeige im Format (Minuten, Sekunden, Hunderstelsekunden) in Textfeldern des Fensters erscheint. Die Klasse enthält die Zeitanzeige mit drei Label (Minuten, Sekunden, Hundertstel) sowie die beiden Knöpfe StartStop und Reset.

class Stoppuhr extends Frame {

Label zeitAnzeigeMin, zeitAnzeigeSec, zeitAnzeigeCsec;

Button startstop, reset;

long startTime, elapsedTime, elapsedLast = 0;

boolean running = false;
Stoppuhr(){ // Konstruktor erzeugt Layout

zeitAnzeigeMin = new Label();

zeitAnzeigeMin.setBounds(50, 50, 40, 25);

add(zeitAnzeigeMin);

zeitAnzeigeSec = new Label();

zeitAnzeigeSec.setBounds(100, 50, 40, 25);

add(zeitAnzeigeSec);

zeitAnzeigeCsec = new Label();

zeitAnzeigeCsec.setBounds(150, 50, 40, 25);

add(zeitAnzeigeCsec);


startstop = new Button("Start");

startstop.setBounds(50,100,60,25);

startstop.addActionListener(new ButtonListenerStartStop());

add(startstop);


reset = new Button("Reset");

reset.setBounds(120,100,60,25);

reset.addActionListener(new ButtonListenerReset());

add(reset);


addWindowListener(new WindowAdapter (){

public void windowClosing(WindowEvent e) { System.exit(0); }

});


add(new Button()); // sonst Layoutfehler?!?

}
public static void main(String[] args) {

Stoppuhr uhr = new Stoppuhr(); // erzeuge neues Fenster

uhr.setBounds(0, 0, 250, 180); //250 Pixel breit, 180 Pixel hoch

uhr.setVisible(true);

uhr.new Zeitanzeige ().start(); // starte separaten Thread

}
Die Main-Methode startet die Uhranzeige als parallelen Thread, der ständig die Anzeige aktualisiert:
class Zeitanzeige extends Thread{

public void run(){

while(true){

if (running){

elapsedTime = (System.currentTimeMillis() - startTime)

+ elapsedLast; // nach stop, weiter- statt neuzählen

int csec = (int) (elapsedTime % 1000 / 10);

int sec = (int) (elapsedTime / 1000) % 60;

int min = (int) (elapsedTime / 60000);

zeitAnzeigeCsec.setText(((csec < 10)? "0" : "") + csec);

zeitAnzeigeSec.setText(((sec < 10)? "0" : "") + sec);

zeitAnzeigeMin.setText(((min < 10)? "0" : "") + min);

}

}

}



}

Schließlich müssen wir noch die Aktionen definieren, die beim Klicken der Knöpfe ausgeführt werden:


class ButtonListenerStartStop implements ActionListener{

public void actionPerformed(ActionEvent e){

startTime = System.currentTimeMillis(); // neue Startzeit



if (! running){

running = true;

startstop.setLabel("Stop");

} else {

running = false;

elapsedLast = elapsedTime; // Speichern des gestoppten Stands

startstop.setLabel("Start");

}

}



}

class ButtonListenerReset implements ActionListener{

public void actionPerformed(ActionEvent e){

startTime = System.currentTimeMillis();

elapsedLast = 0;

zeitAnzeigeMin.setText("00");

zeitAnzeigeSec.setText("00");

zeitAnzeigeCsec.setText("00");

startstop.setLabel("Start");

}

}



}

Das entstehende Fenster sieht wie folgt aus:



Graphikprogrammierung
In einem interaktiven Programm möchte man mit der Maus normalerweise noch mehr machen, als nur Knöpfe anzuklicken. Mausevents (Ziehen, Doppelklick, Rechtsklick usw.) werden nach dem selben Schema wie Knopfdrücke behandelt
addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) {

x1 = e.getX(); y1 = e.getY(); } ...
Auf diese Weise ist es möglich, graphische Elemente (Linien, Kreise, Vielecke, Polygone…) als Objekt der Klasse Graphics auszugeben und mit der Maus zu bearbeiten.


  • Verwendete Methoden: drawLine, fillPolygon, drawOval, …

  • Konstruktor getGraphics()

Als Beispiel implementieren wir ein Malprogramm: Nach Klicken auf die Knöpfe „Rechteck“ oder „Oval“ können entsprechende Figuren durch Ziehen mit der Maus gezeichnet werden.


import java.awt.*;

import java.awt.event.*;

class GrafikFenster extends Frame{

private static int x1, y1, x2, y2;

private static int modus = 0;

GrafikFenster(String titel, int breite, int höhe){



super(titel);

setLayout(new FlowLayout());

Button rechteckButton = new Button ("Rechteck");

rechteckButton.addActionListener(new ActionListener() {



public void actionPerformed(ActionEvent event) {

modus = 1; } });

add(rechteckButton);

Button ovalButton = new Button ("Oval");

ovalButton.addActionListener(new ActionListener() {



public void actionPerformed(ActionEvent event) {

modus = 2; } });

add(ovalButton);

addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) {

x1 = e.getX(); y1 = e.getY();

}

public void mouseReleased(MouseEvent e) {



x2 = e.getX(); y2 = e.getY();

Graphics g = getGraphics();

g.setColor(Color.blue);

int w=Math.abs(x1-x2), h=Math.abs(y1-y2);

switch (modus) {

case 1: g.fillRect(x1,y1,w,h); break;

case 2: g.fillOval(x1,y1,w,h); break;

default: break;

}

}



});

addWindowListener(new WindowAdapter() {



public void windowClosing(WindowEvent e) {

dispose(); }

});

setBackground(Color.lightGray);



setSize(breite, höhe);

setVisible(true);

}

}

public class GrafikDemo {



public static void main(String[] args) {

GrafikFenster f = new GrafikFenster("Künstler",800,600);

}

}
Animationen



Ein Problem des obigen Programms ist es, dass bei Veränderung des Fensters (z.B. Vergrößern) die Zeichnung verloren geht. Das kann mit der Methode paint (Graphics g) zur Ausgabe der Zeichnung gelöst werden. paint wird bei Fensterveränderungen automatisch aufgerufen und muss so programmiert werden, dass es alle Zeichnungsobjekte neu ausgibt. Eine Möglichkeit dazu besteht darin, alle Zeichnungsobjekte in einer Collection (z.B. Set) einzutragen und beim Neuzeichnen iterativ alle Elemente auszugeben. Die selbe Technik kann auch für Animationen benutzt werden, indem paint() in eigenem Thread (etwa alle 40 ms) aufgerufen wird.


Yüklə 3,49 Mb.

Dostları ilə paylaş:
1   ...   17   18   19   20   21   22   23   24   25




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə