1 Einführung in die objektorientierte Programmierung (OOP) 1 Einführung in die objektorientierte Programmierung (OOP) 1.1 Java Was ist eine Klasse, was ist ein Objekt? Wenn ein Kind sagt, dass der vor ihm stehende Dackel „Fifi“ ein Hund ist, dass aber auch der Schäferhund „Fakir“ ein Hund ist, dann hat das Kind im Prinzip gelernt, zwischen Klassen und Objekten im Sinne von OOP zu unterscheiden. Den „Hund“ gibt es tatsächlich nicht, er ist lediglich eine Abstraktion, eine Idee. Wirklich existieren nur „Fifi“ und „Fakir“. In OOP sagen wir auch „Fifi“ und „Fakir“ sind Instanzen oder Objekte der Klasse „Hund“. Etwas allgemeiner können wir sagen, Klassen werden dazu benutzt, Dinge der realen und der gedachten Welt zu modellieren. Solche „Dinge“ können Mitarbeiter in einer Firma sein, oder eine komplizierte Gleichung in der Mathematik. Eine Klasse können wir auch mit einem Stempel vergleichen. Jedes Mal, wenn wir den Stempel auf ein Papier drücken, erzeugen wir ein Objekt, für das der Stempel den Bauplan liefert. In die Felder ‚Name’ und ‚Gehalt’ (vgl. dazu die Abbildung) lassen sich dann konkrete Werte eintragen. Natürlich sind Objekte, die nicht auf dem Papier, sondern im Speicher eines Rechner existieren, wesentlich flexibler, so kann man mit Methoden den konkreten Namens- bzw. Gehaltseintrag wieder verändern, wenn dies z.B. durch Heirat bzw. Gehaltserhöhung angezeigt ist. In unserem Kurs wollen wir uns mit Klassen wie Quadrat, Vieleck und Kreis beschäftigen, wobei wir die wesentlichen Elemente der OOP wie Klassen, abstrakte Klassen, Vererbung und Polymorphie kennen lernen werden. Wenn wir jetzt Klassen wie Quadrat, Vieleck und Kreis modellieren und schließlich implementieren, so sei betont, dass wir lediglich die sog. Fachklassen programmieren. Eine grafische Oberfläche (GUI = Graphical User Interface) werden wir erst später kennen lernen. Wir werden dabei von Anfang an mit der Forderung ernst machen: „Trenne Fachklasse von den GUI-Klassen“. Die Codes gewinnen dadurch an Sicherheit und ihre Wiederverwertbarkeit wird deutlich erhöht. 1.2 Das Miniprojekt „Figuren“ Ziel des Miniprojekts ist es, ein Programm zu schreiben, das es erlaubt, Quadrate, Kreise und Vielecke unterschiedlicher Größe und Farbe zu zeichnen. Vier Screenshots mögen das Projekt veranschaulichen. copyleft:munz 1 Fachschulen Lörrach 1 1.3 Einführung in die objektorientierte Programmierung (OOP) Java Trenne Fachklassen von den GUI-Klassen! In den Fachklassen werden lediglich die funktionalen Eigenschaften modelliert. Eingabefelder, Buttons, Auswahlbuttons, allgemein das ganze Layout eines Programms gehören eindeutig zur GUI (= Graphical User Interface). Aufgabe 1: 1.4 Über welche Attribute und welche Methoden sollten die Fachklassen Quadrat, Kreis und VielEck verfügen? Die Fachklassen Quadrat und Kreis Um ein Quadrat zu modellieren, fragen wir zunächst nach den wesentlichen Eigenschaften, die ein Quadrat haben muss, um es z.B. zeichnen zu können. copyleft:munz 2 Fachschulen Lörrach 1 Einführung in die objektorientierte Programmierung (OOP) Java So gibt man z.B. seinen Mittelpunkt (= Schnittpunkt der Diagonalen) durch seine x- und y-Koordinaten an. Weiter soll das Quadrat eine Seitenlänge haben. Da wir das Quadrat mit einer Farbe gefüllt zeichnen wollen, besitzt es schließlich noch eine Farbe. Wir nennen diese Eigenschaften Attribute einer Klasse. Genauer wäre der Begriff Objekt-Attribute, denn jede Instanz eines Quadrats hat, unabhängig von der Existenz weiterer Instanzen, ihren eigenen Mittelpunkt, ihre eigene Seitenlänge, ihre eigene Farbe. Jedes Objekt belegt somit die Attribute mit eigenen Werten.1 Von den Attributen verlangt man häufig, dass sie gekapselt sind, d.h., anderen (d.h. anderen Klassen) wird ein direkter Zugriff auf die Werte verboten. Ob und wie man auf die Werte der Attribute eines Objektes zugreifen kann, entscheidet das Objekt über seine Methoden. Um Klassen unabhängig von der implementierten Sprache darstellen zu können, wurde die UML (Unified Modelling Language) entwickelt, die sich mittlerweile als Standard durchgesetzt hat. In der UML-Notation hat unsere Klasse Quadrat die links oben stehende Gestalt: Die Attribute sind alle privat - in Java private -, erkennbar an dem „–“ vor dem Bezeichner. Niemand kann „von außen“ schreibend oder lesend auf ihre Werte zugreifen. Die Methoden dagegen sind öffentlich - in Java public -, erkennbar an den „+“-Zeichen vor den Methodennamen. An der Signatur des Konstruktors lässt sich folgendes ablesen: Erzeugt man eine Instanz der Klasse Quadrat, so verfügt das erzeugte Objekt über einen wohldefinierten Mittelpunkt, eine bestimmte Seite und eine konkrete Farbe. Ansonsten besitzt ein Quadrat Methoden um den Mittelpunkt, die Farbe und die Seitenlänge zu setzen (setMittelpunkt(…), setFarbe(…), setSeite(…)) und Methoden um Farbe und Seitenlänge abzufragen (getFarbe(), getSeite()). Schließlich hat die Klasse eine Methode, die es erlaubt, das Quadrat zeichnen zu lassen. Die UML-Notation für den Kreis zeigt dessen gesamte Funktionalität. Attribute und Methoden sind analog zu der Klasse Quadrat konstruiert, so dass sie im Einzelnen nicht mehr aufgezählt und dokumentiert werden müssen. Auffällig ist, dass die Klassen Kreis und Quadrat viele Attribute und Methoden gemeinsam haben. Das legt nahe, eine Klasse zu konstruieren, die alle gemeinsamen Attribute und Methoden von Quadrat und Kreis aufnimmt. Diese neue Kasse wollen wir Figur nennen. 1.5 Die abstrakte Klasse Figur Welche Attribute und Methoden die Klasse Figur aus den Klassen Quadrat und Kreis übernimmt, lässt sich leicht der Klassendarstellung unten entnehmen. Der Name der Klasse Figur ist in der UML-Notation kursiv geschrieben, da Figur eine abstrakte Klasse ist - dazu später mehr. Die Pfeile von Quadrat und Kreis zu Figur zeigen dem Leser, dass beide von Figur erben, d.h. alle Attribute und Methoden, die in Figur implementiert sind, stehen auch den Quadrat- und Kreisobjekten zur Verfügung, so als wären sie dort implementiert. Die Attribute in Figur, aber auch die in den Klassen Kreis und Quadrat haben jetzt das „#“-Zeichen – in Java protected – vorangestellt. Das hat zur Folge, dass auf die Attribute nicht nur von der Klasse selbst, sondern auch von ihren Unterklassen direkt zugegriffen werden kann. Für alle anderen Klassen reagieren sie, als wären sie privat. Um flexibel zu bleiben, haben wir die Attribute in den Klassen Quadrat und Kreis ebenfalls protected gesetzt, damit Klassen, die von Quadrat bzw. Kreis abgeleitet sind, ebenfalls auf diese Attribute zugreifen können. Die Methoden zeichne(…) taucht mit gleicher Signatur in Figur und „ihren Erben“ auf. Einerseits wird die von Figur geerbte Methode zeichne(…) in den Klassen Quadrat und Kreis überschrieben um ihr die klassenspezifischen Funktionalität zu geben. Andererseits sollten Dinge wie das Set1 Der geschilderte Fall kommt am häufigsten vor. Es macht aber manchmal auch Sinn, dem Objekt zu verbieten, eine eigene Kopie eines Attributs anzulegen. Wir sprechen dann von Klassen-Attributen. copyleft:munz 3 Fachschulen Lörrach 1 Einführung in die objektorientierte Programmierung (OOP) Java zen der Farben immer noch in Figur passieren, denn das ist etwas, was nicht(!) spezifisch Quadrat oder Kreis ist. Folglich schafft man sich eine neue Methode, sie heißt zeigeDich(…), die das Farbe setzten übernimmt. Dann muss aber zeigeDich(…) die Methode zeichne(…)aufrufen. Dazu muss zeichne(…) in Figur angelegt sein, obwohl ihre Funktionalität erst in Quadrat bzw. Kreis implementiert wird. Um sicher zu stellen, dass alle von Figur abgeleitete Klassen die Methode zeichne(…) auch wirklich implementieren, notieren wir von zeichne(…) in Figur nur den Methodenkopf und setzten davor das Schlüsselwort abstract. Zwangsläufig wird dadurch die ganze Klasse Figur abstrakt. Eine Klasse, die von Figur erbt, muss die geerbte abstrakte Methode überschreiben2, genauer, implementieren, andernfalls würde der Compiler eine Fehlermeldung erzeugen. In der UML-Notation erkennt man eine abstrakte Klasse an der kursiven Schreibweise ihres Namens. Um eine konkrete Figur, also eine Instanz von Quadrat bzw. Kreis zeichnen zu lassen, brauchen wir nur für diese Objekte die Methode zeigeDich(…) aufzurufen, zeichne(…) braucht also nicht mehr öffentlich zu sein, wir schützen sie, in dem wir sie protected setzen. Aufgabe 2: 1.6 Formulieren Sie die Klasse VielEck in UML-Notation. Pakete Alle Fachklassen zu diesem Projekt gehören logisch zusammen. In der objektorientierten Programmierung gibt es die Möglichkeit, diese Zusammengehörigkeit mit Hilfe von Paketen (packages) auszudrücken. In Java erzeugt man hierzu einen Ordner, wo alle Klassen, die zu dem Paket gehören, gesammelt werden. Ordnernamen und Paket-Bezeichnung sind also identisch. Man kann auch Unterpakete erzeugen. Dazu erzeugt man entsprechend Unterordner und in der Paketbezeichnung trennt man diese anstatt durch einen Backslash durch einen Punkt. In Eclipse einfacher!! Im Ordner figur (Pakete werden immer mit Kleinbuchstaben geschrieben) befinden sich die Klassen Figur, Kreis, Quadrat und VielEck. In jeder dieser Klassen steht als erste Zeile package figur; Andere Klassen, die nicht zum Paket figur gehören, müssen mit import figur.*; die Klassen aus dem Paket figur importieren. 2 Auf die durch Überschreibung zugedeckte Methode kann man mit super immer noch zugreifen, sie ist also in den Unterklassen durch Überschreiben nicht verloren gegangen. copyleft:munz 4 Fachschulen Lörrach 1 Einführung in die objektorientierte Programmierung (OOP) copyleft:munz 5 Java Fachschulen Lörrach 1 1.7 Einführung in die objektorientierte Programmierung (OOP) Java Die Quelltexte Die abtrakte Klasse Figur: package figur; import java.awt.*; public abstract class Figur { protected int xMitte, yMitte; protected Color farbe; } public Figur(int xMitte, int yMitte, Color farbe) { this.xMitte = xMitte; this.yMitte = yMitte; this.farbe = farbe; } public void setMittelpunkt(int xMitte, int yMitte) { this.xMitte = xMitte; this.yMitte = yMitte; } public void setFarbe(Color farbe) { this.farbe = farbe; } public Color getFarbe() { return farbe; } public void zeigeDich(Graphics g) { if (g != null) { g.setColor(farbe); zeichne(g); } } protected abstract void zeichne(Graphics g); Die Klasse Quadrat: package figur; import java.awt.*; public class Quadrat extends Figur { protected int seite; public Quadrat(int xMitte, int yMitte, int seite, Color farbe) { super(xMitte, yMitte, farbe); setSeite(seite); } public void setSeite(int seite) { if (seite >= 0) // Zusicherung this.seite = seite; } public int getSeite() { return seite; } protected void zeichne(Graphics g) { g.fillRect(xMitte - seite/2, yMitte - seite/2, 2*(seite/2), 2*(seite/2) ); } } copyleft:munz 6 Fachschulen Lörrach 1 Einführung in die objektorientierte Programmierung (OOP) Java Die Klasse Kreis: package figur; import java.awt.*; public class Kreis extends Figur { protected int radius = 0; public Kreis(int xMitte, int yMitte, int radius, Color farbe) { super(xMitte, yMitte, farbe); setRadius(radius); } public void setRadius(int radius) { if (radius >= 0) // Zusicherung this.radius = radius; } public int getRadius() { return radius; } protected void zeichne(Graphics g) { g.fillOval(xMitte-radius, yMitte-radius, 2*radius, 2*radius); } } Erläuterungen zu den Quelltexten: Die Attribute xMitte, yMitte und farbe sind in der Klasse Figur außerhalb der Methoden deklariert. Man nennt sie deshalb Objektvariable, da sich ihre Gültigkeit auf das ganze von der Klasse erzeugte Objekt erstreckt. Nach der Deklaration der Attribute folgt die Deklaration der Konstruktoren und der restlichen Methoden. Konstruktoren erkennt man daran, dass sie als Bezeichner den Namen der Klasse tragen. Sie haben keinen Rückgabewert. In einem Konstruktor wird all das untergebracht, was nötig ist, um ein Objekt dieser Klasse beim Erzeugen von Anfang an in einen bestimmten Zustand zu versetzen. Dieser Zustand ist dadurch definiert, dass die Attribute ganz bestimmte Werte haben. Da Figur eine abstrakte Klasse ist, lassen sich von ihr keine Objekte erzeugen. Die Programmzeile Figur eineFigur = new Figur(100,100, Color.BLUE); ist nicht zulässig, sie würde zu einem Compiler-Fehler führen. Anders mit den Klassen Quadrat und Kreis. Instanziiert man etwa ein Quadrat-Objekt, wir nennen es z.B. einQuadrat, durch Quadrat einQuadrat = new Quadrat(50, 100, 200, Color.BLUE); so hat das erzeugte Quadrat einen Mittelpunkt (50, 100) eine Seitenlänge von 200 Pixel und die Füllfarbe blau. Wir erwähnen, dass jede Klasse einen Konstruktor besitzt. Wird er nicht explizit implementiert, so erzeugt nämlich der Compiler einen Standardkonstruktor, der im Fall der Klasse Quadrat die Gestalt Quadrat() hätte. Diesen Standardkonstruktor gibt es nicht mehr, sobald man einen eigenen erstellt hat. Will man auf ihn trotzdem nicht verzichten, muss man ihn explizit programmieren. Wir sehen also, dass eine Klasse mehr als einen Konstruktor haben kann. In der folgenden Programmsequenz, wird eineFigur zunächst als vom Typ Figur deklariert (nicht instanziiert). Figur eineFigur; if (…) eineFigur = new Quadrat(…); else eineFigur = new Kreis(…); eineFigur.zeigeDich(); Abhängig von der Erfüllung einer Bedingung (if…) wird eineFigur als ein Quadrat- bzw. als ein Kreis-Objekt instanziiert. Für dieses Objekt wird zeigeDich(…), das sie von Figur geerbt haben, aufgerufen, diese ruft ihrerseits die Methode zeichne(…) auf, die in ihrer Funktionalität der Klasse Quadrat bzw. Kreis angepasst ist. Je nach Erfüllen der Bedingung wird also ein Quadrat oder ein Kreis gezeichnet. Wir werden später ein Programm vorstellen, das dieses sog. dynamische Binden ausnützt. copyleft:munz 7 Fachschulen Lörrach 1 Einführung in die objektorientierte Programmierung (OOP) Aufgabe 3: Java Implementieren Sie die Klasse VielEck, die wie Quadrat und Kreis von Figur erbt. Ist die Eckenzahl 6 und die Farbe rot, so soll das Sechseck in der Form dargestellt werden, wie es die Abbildung zeigt. Das regelmäßige Sechseck kann man sich einem unsichtbaren Kreis einbeschrieben vorstellen. Dieser Kreis ist durch den Mittelpunkt des Vielecks und einen Radius festgelegt. Zur Implementierung dient die ebenfalls unten stehende UML-Darstellung der Klasse als Vorlage. Der Konstruktor wird analog zu den Konstruktoren von Quadrat und Kreis implementiert, ebenso die restlichen Methoden. Für das Zeichnen des Vielecks benutze man die Methode. fillPolygon(…) aus der Klasse Graphics. copyleft:munz 8 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2 Grafik-Programmierung (GUI) Bei der Entwicklung von Java wurde von Anfang an Wert auf die Gestaltung graphischer Oberflächen (GUI = Graphical User Interface) gelegt, d.h. es werden folgende Eigenschaften unterstützt: Einfache Grafik-Methoden zum Zeichen von Linien oder Figuren, zur Text-Darstellung und zum Füllen von Flächen. Erweiterte Grafik-Methoden zum Manipulieren von Bitmaps und zur Sound-Ausgabe. Dialog-Elemente und Dialog-Fenster zur Kommunikation mit dem Anwender. Methoden zum Reagieren auf Maus-, Tastatur- und Fenster-Ereignisse. 2.1 Das Abstract Windowing Toolkit (AWT) Das Abstract Windowing Toolkit (AWT) ist eine Sammlung von Klassen, mit denen plattformunabhängig Benutzerschnittstellen wie Grafik-Fenster oder Sound programmiert werden können. Als Programmierer muss man sich somit nicht um die plattformspezifischen Fenster-Elemente kümmern, ein entsprechendes Java-Programm läuft ohne neu kompiliert werden zu müssen auf jedem Computer, auf dem die Java-Virtual-Machine (VM) installiert ist. Bei den AWT-Klassen spricht man von „schwergewichtigen“ Komponenten, da sie auf Komponenten zurückgreifen, die vom Betriebssystem zur Verfügung gestellt werden. In der Praxis hat sich jedoch gezeigt, dass die Gemeinsamkeiten der von den verschiedenen Betriebsystemen bereitgestellten Grafikfunktionen nicht sehr groß sind. Das AWT muss deshalb mit einem recht kleinen Funktionsumfang auskommen. Außerdem ist das Aussehen der Grafik-Fenster ebenfalls plattformabhängig. Durch die Betriebssystemabhängigkeit ist es sehr aufwendig, fensterspezifische Eigenschaften auf andere Plattformen zu portieren. 2.2 Swing Um diese Nachteile auszugleichen hat SUN ab dem JDK 1.2 die Java Foundation Classes (JFC) geschaffen, deren wichtigster Bestandteil die Swing-Komponenten sind. Swing, das im Dezember 1998 offiziell frei gegeben wurde, ist eine Sammlung „leichtgewichtiger“ Grafik-Elemente. Als „leichtgewichtig“ bezeichnet man die Eigenschaft von Swing, vollständig die Kontrolle über Aussehen und Wirkungsweise der betreffenden Elemente zu übernehmen und dies nicht dem jeweiligen Betriebssystem zu überlassen. Aussehen und Wirkung (pluggable look and feel) lassen sich beliebig ändern. Von SUN und anderen Anbietern werden PL&F bereitgestellt. Wenn man möchte, kann man sich ein PL&F auch selbst erstellen. Hier drei Beispiele für verschiedene PL&F wie sie mit dem JDK mitgeliefert werden: Java look and feel copyleft:munz Windows look and feel 9 CDE/Motif look and feel Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Vorteile von Swing: MVC (model-view-controller) Architektur, d.h. Trennung von Daten, GUI-Anwendungen und Benutzer-Eingaben Plattformunabhängig, verschiedene pluggable look and feels. Sehr umfangreiche Komponenten-Bibliothek Unterstützung der Java-Beans-Convention, dadurch einheitliche Methoden-Bezeichner Unterstützung von „drag & drop“ Bereitstellung aufwendiger Grafik-Routinen Die Nachteile von Swing: Da alle Komponenten selbst gezeichnet werden müssen, benötigt Swing eine hohe CPU-Leistung und recht viel Hauptspeicher. Swing wird nicht von den gängigen Web-Browsern unterstützt, da diese bisher nur das JDK 1.0 und gelegentlich das JDK 1.1 implementieren. Damit die Browser auch Applets mit Swing-Komponenten darstellen können, muss man ein besonderes Plug-In installieren, das mit dem JDK oder JRE mitgeliefert wird. Der HTML-Code für Swing-Applets enthält deshalb auch nicht die üblichen Applet-Tags, sondern Code um Plug-Ins einzubetten. Leider verfahren die verschiedenen Browserhersteller, insbesondere Microsoft und Netscape, unterschiedlich beim Einbetten von Plug-Ins. Der HTML-Code wird dadurch unübersichtlich. Zum Testen von Swing-Applets bietet sich deshalb der AppletViewer von SUN an, der auch die gewöhnlichen Applet-Tags versteht. Weiterhin liefert SUN mit dem JDK den HTMLConverter aus, ein komfortables Werkzeug, um aus HTML-Code mit Applet-Tags entsprechenden HTML-Code für das Java-Plug-In zu machen. Obwohl alle Swing-Komponenten vom AWT abgeleitet sind, wird dringend empfohlen, niemals gleichzeitig AWT- und Swing-Komponenten nebeneinander zu verwenden. Es könnten sonst unerwünschte Nebeneffekte auftreten. 2.3 Andere Java Foundation Classes (JFC) Andere Firmen bieten konkurrierende Schnittstellen-Klassen an, so genannte Java Foundation Classes (JFC): Netscape bietet die Internet Foundation Classes (IFC) an, die mit den Swing-Klassen eine gemeinsame Basis haben. Microsoft geht mit den Application Foundation Classes (AFC) völlig eigene Wege, wie sie bei Microsoft typisch sind. copyleft:munz 10 Fachschulen Lörrach 2 2.4 Grafik-Programmierung (GUI) Java Grafik-Programmierung mit Swing Das Erstellen von Grafiken mit Swing funktioniert im Prinzip genauso wie mit AWT. Dennoch gibt es einige Unterschiede und einige Regeln, die zu beachten sind. Grafiken, die mit Swing erstellt werden, sollten grundsätzlich in leichtgewichtigen Komponenten erstellt werden. Das sind Komponenten der Klasse JComponent und deren Unterklassen, insbesondere JPanel, JButton oder JLabel. Diese müssen dann letztendlich in einen so genannten Top-Level-Container wie JFrame, JDialog oder JApplet eingebettet werden. Das Standardverfahren, um eine Zeichenfläche zu erzeugen, ist eine Unterklasse von JPanel zu bilden (in AWT entspricht das der Klasse Canvas). Das ist besser, als Unterklassen direkt von JComponent zu bilden, da JPanel als Unterklasse von JComponent einige zusätzliche Eigenschaften hat. Instanzen von JPanel sind in der Regel opak, d.h. sie haben einen nicht transparenten Hintergrund, und es ist das so genannte Double-Buffering eingeschaltet, das ein Flackern beim Bildaufbau verhindert. Dazu wird die Grafik zunächst unsichtbar in einem imaginären Bildbereich vollständig erstellt. Anschließend wird dieser Bildbereich mit dem sichtbaren Bereich ausgetauscht. Das folgende Programm kann als Grundgerüst zum Erzeugen von Grafiken dienen. Zunächst wird eine Klasse JGrafik als Unterklasse von JFrame erstellt. Diese enthält eine Instanz zeichenFlaeche der Klasse GrafikPanel als Unterklasse von JPanel. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JGrafik extends JFrame { private GrafikPanel zeichenFlaeche; public JGrafik(String title) { super (title); addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); zeichenFlaeche = new GrafikPanel(300, 200); cp.add(zeichenFlaeche); pack(); setVisible(true); } } copyleft:munz public static void main (String[] args) { new JGrafik("Grafik-Beispiel"); } 11 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die eigentliche Zeichenfläche ist eine Unterklasse von JPanel: import java.awt.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; public GrafikPanel(int breite, int hoehe) { super(); this.breite = breite; this.hoehe = hoehe; } public Dimension getPreferredSize() { return new Dimension(breite, hoehe); } } public void paintComponent(Graphics g) { super.paintComponent(g); // sollte immer aufgerufen werden! // Hier kommen die eigentlichen Grafikanweisungen hin. } Zum Erstellen von Grafiken überschreibt man die Methode paintComponent aus javax.swing.JComponent. Die Syntax von paintComponent lautet: public void paintComponent(Graphics g). Dabei ist g der Grafik-Kontext der entsprechenden Klasse und eine Instanz der Klasse Graphics. Obwohl es in JComponent auch die Methode paint gibt, wie die gleichnamige Methode in java.awt.Component, wird empfohlen paintComponent anstatt paint zu verwenden. Das ist ökonomischer, da paint zusätzlich weitere Seiteneffekte auslöst. So muss paint beispielsweise sehr aufwendig die Reihenfolge der zu zeichnenden Objekte ermitteln, wenn sich mehrere Objekte gegenseitig überdecken. Da paint letztlich ohnehin aufgerufen wird, wäre das doppelter Aufwand. Damit Swing die selbst erstellte Komponente richtig in die Überdeckungshierachie einordnen kann, sollte man zunächst durch super.paintComponent die entsprechende Methode der Oberklasse aufrufen. Der Aufruf von paintComponent selbst erfolgt dann automatisch durch Swing und sollte niemals aus dem Programm heraus erfolgen. Das Grundgerüst soll jetzt so erweitert werden, dass in der Mitte des Grafikfensters ein weißes Rechteck mit schwarzem Rand gezeichnet wird. Dazu muss paintComponent ein wenig erweitert werden. public void paintComponent(Graphics g) { super.paintComponent(g); int dx = breite*2/3; int dy = hoehe*2/3; int x = (breite - dx)/2; int y = (hoehe - dy)/2; } copyleft:munz g.setColor(Color.WHITE); g.fillRect(x, y, dx, dy); g.setColor(Color.BLACK); g.drawRect(x, y, dx, dy); 12 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Das Ergebnis: Eine weitere wichtige Veränderung von Swing gegenüber AWT ist das automatische Erkennen, ob eine Komponente ganz oder in Teilen neu gezeichnet werden muss oder nicht. Das ist immer dann der Fall, wenn sie z.B. durch andere Windows-Fenster teilweise oder ganz abgedeckt gewesen war. Manchmal ist es jedoch erforderlich, ein Neuzeichnen aus dem Programm heraus zu veranlassen. Das ist immer dann der Fall, wenn die Grafik durch ein Ereignis verändert werden soll. Im obigen Beispiel könnte man eine Schaltfläche einfügen, so dass bei jedem Klick die Farbe des Rechtecks verändert wird. In diesem Fall erzwingt man ein Neuzeichnen durch Aufruf der Methode repaint. Dabei ruft Swing den RepaintManager auf, der ein Neuzeichnen der Komponenten für den betreffenden Fensterbereich und in der richtigen Reihenfolge veranlasst. Aufgabe 4: 2.5 Zeichnen Sie ein Rechteck statt in ein JPanel in eine Schaltfläche (JButton) oder in ein Beschriftungsobjekt (JLabel). Die wichtigsten Methoden zum Zeichnen grafischer Objekte Die Methoden zum Zeichnen von grafischen Objekten findet man in der Klasse java.awt.Graphics. Es ist eine abstrakte Klasse, somit kann man selbst keine Instanz dieser Klasse erzeugen. Das ist aber auch nicht nötig, da alle Klassen, die ein Zeichnen ermöglichen, die abstrakten Methoden in Graphics entsprechend implementieren. Beim Aufruf von paintComponent(Graphics g) wird der Grafikkontext g erst zur Laufzeit ermittelt. Ein Versuch diesen Grafikkontext z.B. mit getGraphics oder mit createGraphics schon vor dem eigentlichen Zeichnen ermitteln zu wollen, wird scheitern, da createGraphics bzw. getGraphics dann null zurückliefern. Falls ein vorzeitiges Erstellen von Grafiken nötig sein sollte, erzeugt man eine Instanz image von java.awt.BufferedImage und kann dann mit image.createGraphics() ihren Grafikkontext vom Typ Graphics2D erzeugen oder mit image.getGraphics() den Grafikkontext ermitteln, der in diesem Fall aus Kompatibilitätsgründen vom Typ Graphics ist. Zum eigentlichen Zeichnen ruft man dann innerhalb von paintComponent die Methode drawImage(image) auf. copyleft:munz 13 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Hier sind die wichtigsten Methoden aus Graphics aufgelistet. Manche Methoden haben verschiedene Signaturen. Eine genaue Auflistung findet man in der Dokumentation zum Java-API. public void drawImage( Image img, int x, int y, ImageObserver observer ); Zeichnet am Punkt (x, y) soviel vom Image, wie gerade verfügbar ist. public void drawLine(int x1, int y1, int x2, int y2); zieht eine Linie vom Punkt (x1, y1) zum Punkt (x2, y2). public void drawRect(int x, int y, int width, int height); zeichnet ein Rechteck, dessen linke obere Ecke bei (x, y) liegt und die Breite width und die Höhe height hat. public void drawRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ); zeichnet ein abgerundetes Rechteck, dessen linke obere Ecke bei (x, y) liegt, die Breite width und die Höhe height hat und arcWidth und arcHeight die Ellipsenabschnitte der abgerundeten Ecken bedeuten. public void fillRect(int x, int y, int width, int height); zeichnet ein ausgefülltes Rechteck. public void fillRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ); zeichnet ein ausgefülltes und abgerundetes Rechteck. public void drawPolygon(int x_arr[], int y_arr[], int count); zeichnet ein geschlossenes Polygon mit count Ecken. x_arr und y_arr sind jeweils ein Feld von x– bzw. y–Koordinaten. public void drawPolyline(int x_arr[], int y_arr[], int count); zeichnet ein nicht geschlossenes Polygon. public void fillPolygon(int x_arr[], int y_arr[], int count); zeichnet ein ausgefülltes Polygon. public void drawOval(int x, int y, int width, int height); zeichnet eine Ellipse, die in ein Rechteck eingeschlossen ist, dessen linke obere Ecke bei (x, y) liegt und die Breite width und die Höhe height hat. Für Kreise müssen width und height denselben Wert haben. public void fillOval(int x, int y, int width, int height); zeichnet eine ausgefüllte Ellipse bzw. einen ausgefüllten Kreis. public void drawArc( int x, int y, int width, int height, int startAngle, int arcAngle ); zeichnet einen Ellipsenbogen. Die Parameter haben die gleiche Bedeutung wie bei drawOval. startAngle gibt den Winkel ab der 0°–Position an, die bei 3 Uh r liegt, und arcAngle ist der Winkel im Gegenuhrzeigersinn ab startAngle. Winkel werden dabei im Gradmaß gemessen. public void fillArc( int x, int y, int width, int height, int startAngle, int arcAngle ); zeichnet einen ausgefüllten Ellipsenbogen. Die Endpunkte werden durch eine Strecke verbunden. public void clearRect(int x, int y, int width, int height); löscht einen Rechteckbereich (d.h. füllt ihn mit der Hintergrundfarbe) mit der Breite width und der Höhe height. Die linke obere Ecke ist bei (x, y). copyleft:munz 14 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java public void copyArea( int x, int y, int width, int height, int dx, int dy ); kopiert das Rechteck bei (x, y) mit der Breite width und der Höhe height nach (x+dx, y+dy). public void drawString(String str, int x, int y); schreibt den Text str beginnend bei (x, y) in ein Fenster. Dabei wird der Text in einem systemabhängigen Standard–Font ausgegeben. Zeichnen Sie anstatt eines Rechtecks eine Ellipse, ein Quadrat, ein Kreis, ein Dreieck, ein Vieleck, … Aufgabe 5: 2.6 Grafiken mit der Klasse BufferedImage Das folgende Beispiel erzeugt eine Grafik mit einem zufälligen Strichmuster: public void paintComponent(Graphics g) { super.paintComponent(g); int x0 = 80, y0 = 20, len = 100, maxX = breite - x0; } for (int x = x0; x < maxX; x++) { if (Math.random() < 0.5) g.drawLine(x, y0, x, y0+len); } Leider hat das Programm einen Haken. Da ein Grafik-Fenster bei jeder Veränderung (Größenänderung, Überdeckung durch andere Fenster der Desktops usw.) neu gezeichnet werden muss, wird bei jedem Neuzeichnen paintComponent aufgerufen und dabei jedes Mal ein neues Zufallsmuster erzeugt. Besonders augenfällig wird das, wenn das Grafik-Fenster nur teilweise verdeckt wurde wie man auf dem zweiten Bild sehen kann. Damit das Grafikmuster bei einer Wiederherstellung des Fensters erhalten bleibt, zeichnet man es in ein BufferedImage. Dann wird nur in das Image neu gezeichnet, dessen Grafik-Kontext man mit getGraphics erhält. Die Methode drawImage, die sich auf den Grafik-Kontext der Klasse GrafikPanel bezieht, stellt das Image schließlich dar. Wenn man jetzt zum Hauptprogramm eine Schaltfläche hinzufügt, kann man das Muster gewollt auf Knopfdruck verändern. copyleft:munz 15 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Für die Klasse GrafikPanel ist dann allerdings etwas mehr Aufwand nötig: import java.awt.*; import java.awt.image.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; private BufferedImage image; } 2.7 public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); erstelleGrafik(); } public void erstelleGrafik() { int b = breite - 80, h = hoehe - 80; image = new BufferedImage(b, h, BufferedImage.TYPE_INT_RGB); Graphics2D g2D = image.createGraphics(); g2D.setColor(getBackground()); g2D.fillRect(0, 0, b, h); } public void zeichneGrafik() { int b = image.getWidth(), h = image.getHeight(); g2D.setColor(getBackground()); g2D.fillRect(0, 0, b, h); g2D.setColor(Color.BLACK); for (int x = 0; x < b; x++) { if (Math.random() < 0.5) g2D.drawLine(x, 0, x, h); } } public void paintComponent(Graphics g) { super.paintComponent(g); int x = (breite - image.getWidth())/2; int y = (hoehe - image.getHeight())/2; g.drawImage(image, x, y, this); } Java 2D Grafik API Graphics2D ist ein 2D Graphics Application Programming Interface (API), das eine Vielzahl von Grafikerweiterungen anbietet. Die wichtigsten sind: Koordinatensystem mit Koordinaten des Datentyps float oder double Zeichnen von Linien beliebiger Stärke und beliebigem Muster Gestaltung von Linien-Enden und Linien-Verbindungen Füllen von Flächen mit Gradienten und Mustern Verschieben, Rotieren Skalieren und Beschneiden von Text und Grafik Überlagerung von Text and Grafik Kantenglättung und verschiedene Verfahren beim Rendern von Grafik-Darstellungen Die folgenden Beispiele zeigen, wie man die Klasse Graphics2D einsetzt. Durch Type-Casting wandelt man den Grafikkontext vom Typ Graphics in einen Grafikkontext vom Typ Graphics2D um. Mit setStroke kann man dann die verschiednen Linienarten zuweisen, die man zuvor mit einem der Konstruktoren aus der Klasse BasicStroke erzeugt hat. copyleft:munz 16 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Damit die Grafiken nicht so „pixelig“ ausschauen, bietet die Klasse Graphics2D die Möglichkeit, Grafiken mit Kantenglättung („Antialiasing“) darzustellen. Dazu erzeugt man eine Instanz von RenderingHints mit einem geeigneten Schlüssel/Wert Paar und passt den speziellen Schlüssel an den speziellen Wert dieses RenderingHints-Objekts mit Hilfe der Methode put aus RenderingHints an. Dem Grafikkontext übergibt man zum Schluss dieses RenderingHints-Objekt mit der Methode add aus Graphics2D. Das erste Beispiel erzeugt einen Ellipsenbogen mit runden Linien-Enden: import java.awt.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; private RenderingHints renderHints; } public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); // Zum Glätten der Kanten Antialiasing einschalten renderHints = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); renderHints.put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ); } public void paintComponent(Graphics g) { Graphics2D g2D = (Graphics2D) g; super.paintComponent(g2D); g2D.setRenderingHints(renderHints); BasicStroke stroke = new BasicStroke( 20.0f, // Linien-Breite BasicStroke.CAP_ROUND, // Linien-Ende BasicStroke.JOIN_ROUND // Linien-Verbindung ); g2D.setStroke(stroke); g2D.drawArc(20, 20, breite - 40, hoehe - 40, 0, 180); } Das zweite Beispiel erzeugt einen Ellipsenbogen mit geraden Linien-Enden und einem benutzerdefinierten Linien-Muster: public void paintComponent(Graphics g) { Graphics2D g2D = (Graphics2D) g; super.paintComponent(g2D); g2D.setRenderingHints(renderHints); BasicStroke stroke = new BasicStroke( 20.0f, // Linien-Breite BasicStroke.CAP_BUTT, // Linien-Ende BasicStroke.JOIN_ROUND, // Linien verbinden 0.0f, // Verbindungsgrenze new float[] {10.0f, 10.0f, 2.0f, 10.0f}, // Dash-Pattern 0.0f); // Dash-Pattern Offset g2D.setStroke(stroke); g2D.drawArc(20, 20, breite - 40, hoehe - 40, 0, 180); } copyleft:munz 17 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Zwei Bögen mit verschiedenen Linienmustern: 2.8 Font–Objekte Um benutzerdefinierte Fonts zu erzeugen, verwendet man den Konstruktor public Font(String name, int style, int size); Der Parameter name gibt den Namen des gewünschten Fonts an, z.B. „Arial“ oder „Times New Roman“. Diese Namen entsprechen den Namen real existierender True-Type-Fonts. Man kann statt realer Font-Namen auch logische Font-Namen angeben, die man in der Regel immer verwenden sollte: SansSerif (wird in Windows im Allgemeinen durch „Arial“ ersetzt) Serif (Windows: „Times New Roman“) Monospaced (Windows: „Courier New“) Dialog (Windows: „Arial“) Für style setzt man eine der Konstanten: Font.PLAIN (standard) Font.BOLD (fett), Font.ITALIC (kursiv) Font.BOLD | Font.ITALIC (bitweise „oder“, ergibt fett und kursiv) size gibt die Schriftgröße in point an (dabei gilt: 1 pica = 12 pt = 12/72 inch = 4,22 mm). Mit public void setFont(Font font); wird das Font–Objekt in den Grafikkontext eingetragen, mit public Font getFont(); kann der aktuelle Font abgefragt werden. Beispiel: font = new Font("Serif", Font.PLAIN, 18); g.setFont(font); g.drawString("Beispiel-Text", 10, 20); 2.8.1 Font–Informationen Mit den Methoden public String getFamily(); public int getStyle(); public int getSize(); können Informationen über den aktuellen Font abgefragt werden. 2.8.2 Font–Metriken Mit public FontMetrics getFontMetrics(Font font); public FontMetrics getFontMetrics(); ermittelt man die Größeneigenschaften von Zeichen des Fonts font, bzw. des aktuellen Fonts (parameterlose Variante). copyleft:munz 18 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Das Bild zeigt am Beispiel der Schriftart SansSerif (Times New Roman) mit der Schriftgöße 12 pt, welche Größen eines Zeichens gemessen werden. Es stehen folgende Methoden zur Verfügung: public int charWidth(char ch); Breite des Zeichens ch public int stringWidth(String s); Breite des Strings s public int getAscent(); Oberlänge public int getDescent(); Unterlänge public int getHeight(); Höhe public int getLeading(); Zeilenabstand Beispiel: font = new Font("Times New Roman", Font.PLAIN, 36); g.setFont(font); g.drawString("mgdAW", 10, 20); FontMetrics fm = getFontMetrics(font); System.out.println("Breite: " + fm.stringWidth("mgdAW")); System.out.println("Oberlänge: " + fm.getAscent()); System.out.println("Unterlänge: " + fm.getDescent()); System.out.println("Höhe: " + fm.getHeight()); System.out.println("Zeilenabstand: " + fm.getLeading()); copyleft:munz 19 Fachschulen Lörrach 2 2.9 Grafik-Programmierung (GUI) Java Aufgaben Zeichnen Sie die nachfolgenden Figuren: Aufgabe 6: Zeichnen Sie ein Gitter mit breiten Aufgabe 7: Strichen in unterschiedlicher Farbe. Achten Sie genau auf die Farbe der Kreuzungspunkte. Ändern Sie das Programm so ab, dass die Kreuzungspunkte genau die andere Farbe erhalten. Aufgabe 8: Ändern Sie das Programm nochmals ab. Die Farben der Kreuzungspunkte sollen jetzt abwechselnd wie bei einem Schottenmuster sein. Erzeugen Sie einen Stapel „gepackter“ Kreise. Aufgabe 10: Erzeugen Sie eine Figur mit gestrichelten Linien. copyleft:munz Aufgabe 9: Aufgabe 11: Stellen Sie Text in unterschiedlichen Schriftarten dar. 20 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.10 Die Swing-Komponenten 2.10.1 Die Unterklassen von JComponent Schaltflächen, Textfelder usw. sind von javax.swing.JComponent abgeleitet. Diese Klasse ist eine direkte Unterklasse von java.awt.Container, welche wiederum direkt von java.awt.Component abgeleitet ist. Sämtliche Swing-Elemente aufzuzählen und ihre Funktionsweise zu beschreiben würde den Rahmen dieser Handreichungen sprengen. Da sei auf die dicken Handbücher verwiesen oder besser auf die Online-Hilfe und Online-Dokumentation. Das folgende Schema zeigt ein (unvollständiges) Teilklassen-Diagramm der Swing-Komponenten, die von javax.swing.JComponent abgeleitet sind (die rechts stehenden Klassen sind Unter-Klassen der weiter links stehenden Klassen). Sie ersetzen die entsprechenden AWT-Komponenten. JComponent AbstractButton JButton JMenuItem JToggleButton BasicInternalFrameTitlePane Box Box.Filler JColorChooser JComboBox JFileChooser JInternalFrame JInternalFrame.JDesktopIcon JLabel JLayeredPane JList JMenuBar JOptionPane JPanel JPopupMenu JProgressBar JRootPane JScrollBar JScrollPane JSeparator JSlider JSpinner JSplitPane JTabbedPane JTable JTableHeader JTextComponent JCheckBoxMenuItem JMenu JRadioButtonMenuItem JCheckBox JRadioButton JDesktopPane JEditorPane JTextArea JTextField JTextPane JPasswordField JToolBar JToolTip JTree JViewport copyleft:munz 21 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.10.2 Die Fenster in Swing Eine weitere wichtige Gruppe von Swing-Elementen sind die verschiedenen Fenster in Swing. Besonders häufig wird die Klasse javax.swing.JFrame verwendet. Als weitere Fensterklasse gibt es die Dialog-Fenster, wie sie häufig bei PopUp-Meldungen eingesetzt werden. Hier eine Übersicht über die Vererbungshierachie bei den Fenster-Klassen. Dabei ist java.awt.Window eine direkte Unterklasse von java.awt.Container: java.awt.Window jawa.awt.Dialog java.awt.Frame javax.swing.JWindow javax.swing.JDialog javax.swing.JFrame 2.10.3 JFrame Ein JFrame stellt ein typisches Windows-Fenster dar, das Titel-Leiste, und ggf. Menü- und Symbolleisten besitzt. Außerdem hat ein JFrame Knöpfe zum Vergrößern, Verkleinern und Verstecken des Fensters. Es hat einen Rahmen sowie Bereiche von wo aus man das Fenster vergrößern und verkleinern kann. Die eigentliche Fensterfläche enthält dann die oben aufgeführten Elemente. Die Abbildung zeigt den Aufbau eines JFrames. Ein JFrame enthält ein JRootPane, welches wiederum ein JLayeredPane enthält. Diese Schichten wird man im Allgemeinen nicht weiter bearbeiten. Die wichtigste Schicht ist das ContentPane, welches von der Klasse Container abgeleitet ist. Mit getContentPane erhält man auf diese Schicht Zugriff, mit setContentPane kann man das ContentPane ersetzten (z.B. durch ein JPanel). Im ContentPane befinden sich auch die Menüleiste. Das GlassPane ist eine „durchsichtige“ Schicht in vorderster Ebene. Bringt man hier Elemente unter, überdecken sie die Elemente des ContentPanes. 2.11 Die LayoutManager Wenn man einer Swing-Komponente wie z.B. JFrame mehrere Schaltflächen hinzufügt, packt Java sie einfach übereinander. Zum richtigen Anordnen der Komponenten helfen die LayoutManager. Da Java-Programme auf verschiedenen Rechnern mit unterschiedlichen Betriebsystemen laufen sollen, kann man das Aussehen eines Grafikfensters als Programmierer nicht vorhersehen. Die einzelnen Grafik-Komponenten ordnen sich nach gewissen Regeln selbst an, ähnlich wie bei einer HTML-Seite. Die wichtigsten LayoutManager sind: FlowLayout BorderLayout GridLayout GridBagLayout BoxLayout copyleft:munz 22 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.11.1 Beispiele: Wenn man auf den Einsatz eines LayoutManagers verzichtet, muss man Größe und Position der Grafik-Komponenten mit der Methode setBounds selbst festlegen. Damit kann man das Aussehen eines Fensters pixelgenau festlegen, ähnlich den Fenstern in Windows. Möglicherweise ist dann das so erstellte Fenster für eine andere Bildschirmauflösung ungeeignet. Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(null); JButton jbTest1 = new JButton("B 1"); jbTest1.setBounds(10, 10, 100, 200); cp.add(jbTest1); JButton jbTest2 = new JButton("B 2"); jbTest2.setBounds(120, 140, 220, 60); cp.add(jbTest2); JButton jbTest3 = new JButton("B 3"); jbTest3.setBounds(300, 10, 80, 80); cp.add(jbTest3); Das FlowLayout reiht die Komponenten von links nach rechts und von oben nach unten an, so wie Platz ist: Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(new FlowLayout()); for (int i = 0; i < anzButton; i++) { jbTest[i] = new JButton(Integer.toString(i)); cp.add(jbTest[i]); } copyleft:munz 23 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Das GridLayout ordnet die Komponenten in einem Gitter an und passt ihre Größe der Größe der Gitterzellen an. Alle Komponenten erhalten dieselbe Größe. Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(new GridLayout(3, 2)); for (int i = 0; i < anzButton; i++) { jbTest[i] = new JButton(Integer.toString(i)); cp.add(jbTest[i]); } Das GridBagLayout ordnet ähnlich wie das GridLayout die Elemente in einem Gitter an. Die Elemente können zusätzlich Zeilen oder Spalten übergreifend angeordnet werden. Außerdem sind Abstandsdefinitionen zu den Zellenrändern möglich. Das GridBagLayout ist sehr umständlich zu handhaben, weshalb hier auf weitere Erläuterungen verzichtet wird. Das BorderLayout hat fünf Bereiche, einer im Zentrum und jeweils einen am Rand: Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(new BorderLayout()); cp.add(new cp.add(new cp.add(new cp.add(new cp.add(new JButton("North"), BorderLayout.NORTH); JButton("South"), BorderLayout.SOUTH); JButton("East"), BorderLayout.EAST); JButton("West"), BorderLayout.WEST); JButton("Center"), BorderLayout.CENTER); Das BoxLayout ordnet die Elemente in einer Reihe entweder horizontal (BoxLayout.X_AXIS) oder vertikal (BoxLayout.Y_AXIS) an, wobei jede Komponente, wenn es möglich ist, ihre individuelle Größe behält. copyleft:munz 24 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(new BoxLayout(cp, BoxLayout.Y_AXIS)); JButton jbTest1 = cp.add(jbTest1); JButton jbTest2 = cp.add(jbTest2); JButton jbTest3 = cp.add(jbTest3); JButton jbTest4 = cp.add(jbTest4); new JButton("B Nr. 1"); new JButton("Button 2"); new JButton("Schaltfläche 3"); new JButton("B 4"); In den meisten Fällen hat man mehr Komponenten als bei diesen sehr einfachen Beispielen. Komplexere Layouts erzielt man, wenn man verschiedene Instanzen von JPanel erzeugt, jedem JPanel ein bestimmtes Layout zuweist und diese JPanel geeignet schachtelt. Für das BoxLayout gibt es noch eine andere Alternative. Anstatt ein JPanel mit BoxLayout zu erzeugen, gibt es die einfacher zu handhabende Klasse javax.swing.Box: Die Programm-Zeile JPanel panel = new JPanel(new BoxLayout(panel, BoxLayout.X_AXIS)); wird ersetzt durch Box box = new Box(BoxLayout.X_AXIS); Die Klasse Box enthält weitere einfache Möglichkeiten, die Anordnung von Elementen zu beeinflussen. Mit den statischen Methoden Box.createGlue(), Box.createHorizontalGlue() und Box.CreateVerticalGlue() lassen sich unsichtbare Elemente variabler Größe erzeugen. Mit Box.createHorizontalStrut(int width) und Box.createVerticalStrut(int width) entsprechend Elemente fester Breite bzw. Höhe. Außerdem kennt jedes Element, das von javax.swing.JComponent abgeleitet ist, die Methoden setAlignmentX(float alignmentX) und setAlignmentY(float alignmentY). Mit diesen Methoden kann man die Lage der Ausrichtungsmarken von Komponenten verändern. Das Argument 0.0f steht für links bzw. oben, 0.5f für mittig und 1.0f für rechts bzw. unten. Hier ein komplexeres Beispiel: copyleft:munz 25 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Container cp = getContentPane(); cp.setBackground(Color.WHITE); cp.setLayout(new BorderLayout()); Box box1 = new Box(BoxLayout.Y_AXIS); box1.setAlignmentX(0.0f); box1.setBorder(BorderFactory.createTitledBorder("Box 1")); JButton jbB1_1 = new JButton("B 1"); jbB1_1.setAlignmentX(0.5f); JButton jbB1_2 = new JButton("Button 2"); jbB1_2.setAlignmentX(0.5f); JButton jbB1_3 = new JButton("Schaltfläche 3"); jbB1_3.setAlignmentX(0.5f); JButton jbB1_4 = new JButton("B. Nr. 4"); box1.add(jbB1_1); box1.add(jbB1_2); box1.add(jbB1_3); box1.add(jbB1_4); Box box2 = new Box(BoxLayout.X_AXIS); box2.setAlignmentX(0.0f); box2.setBorder(BorderFactory.createTitledBorder("Box 2")); JLabel jlB2 = new JLabel(" Ein Label "); jlB2.setOpaque(true); jlB2.setFont(new Font("SansSerif", Font.BOLD, 36)); JButton jbB2_1 = new JButton("Klick mich!"); jbB2_1.setAlignmentY(0.0f); JButton jbB2_2 = new JButton("hier drücken"); jbB2_2.setAlignmentY(1.0f); box2.add(jlB2); box2.add(jbB2_1); box2.add(jbB2_2); Box box3 = new Box(BoxLayout.Y_AXIS); box3.setBorder(BorderFactory.createTitledBorder("Box 3")); JButton jbB3 = new JButton("beenden"); box3.add(box1); box3.add(box2); box3.add(Box.createVerticalStrut(10)); box3.add(jbB3); box3.add(Box.createVerticalGlue()); cp.add(box3); 2.12 Rahmen Jeder von JComponent abgeleiteten Komponente lässt sich mit der Methode setBorder(Border border) ein Rahmen hinzufügen. Das geht sehr einfach mit Hilfe der Klasse javax.swing.BorderFactory, wo die wichtigsten Rahmen vordefiniert sind. Um einen Rahmen zu erzeugen verwendet man eine der statischen Methoden BorderFactory.createXXXBorder, wobei createXXXBorder für eine der folgenden Methoden steht. Für die jeweiligen Argumente sei auf die Dokumentation zum Java-API verwiesen. createBevelBorder Erzeugt einen Rahmen mit 3D-Effekt, je nach dem erhaben oder versenkt. createLoweredBevelBorder Erzeugt einen vertieften Rahmen mit 3D-Effekt. createRaisedBevelBorder Erzeugt einen erhabenen Rahmen mit 3D-Effekt. createCompoundBorder Erzeugt einen Rahmen, der aus zwei weiteren Rahmen zusammengesetzt ist. createEmptyBorder Erzeugt einen leeren Rahmen, besonders wichtig um Leerraum um Komponenten zu schaffen. copyleft:munz 26 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java createEtchedBorder Erzeugt einen Rahmen mit Relief-Effekt. createLineBorder Erzeugt eine farbige Linie um die Komponente createMatteBorder Erzeugt einen Rahmen, der sich nach gewissen Regeln beliebig gestalten lässt. createTitledBorder Erzeugt einen Rahmen mit Titel. 2.13 Ereignisse Wenn man auf eine Schaltfläche klickt, Text in ein Textfeld eingibt, ein Fenster schließt oder an einem Schieberegler zieht, löst man Ereignisse aus, auf die das Programm entsprechend reagieren soll. In Java werden Ereignisse seit der Version 1.1 mit Hilfe eines Verfahrens verwaltet, das sich Event Delegation Model nennt. Das Betriebssystem informiert die GUI-Anwendung über Ereignisse wie z.B. Mausklicks, Tastatureingaben oder Veränderungen von Fenstern durch Versenden von Botschaften (Event). Diese Botschaften werden von Ereignisquellen, z.B. einer Schaltfläche ausgelöst. Empfänger dieser Botschaften sind Ereignisabhorcher (EventListener), die auf die empfangenen Botschaften entsprechend reagieren können. Damit die Nachrichtenquelle den zugehörigen EventListener kennt, muss sich der EventListener bei der Nachrichtenquelle registrieren. Der Vorteil dieses Verfahrens besteht zum einen darin, dass nur solche Nachrichten transportiert werden, zu denen es auch Empfänger gibt. Das erhöht die Leistungsfähigkeit des Programms. Weiterhin dient es der klaren Trennung zwischen Programmcode zur Oberflächengestaltung (GUI-Klassen) und Programm-Code zur eigentlichen Funktionsweise (Fachklassen). Der dadurch etwas größere Programmieraufwand macht sich bezahlt durch einen weniger fehleranfälligen Code, der auch bei sehr großen Programmen übersichtlich bleibt. Je nach Größe des Programms und Anzahl der Ereignisse bieten sich verschiedene Musterlösungen an. 2.13.1 Auf Button-Klicks reagieren Zunächst soll an einem einfachen Beispiel die Behandlung von Button-Klicks erläutert werden. In einem Fenster befinden sich drei Schaltflächen, mit denen die Hintergrundfarbe geändert werden kann. Eine vierte Schaltfläche beendet beim Klicken das Programm. Um ein GUI-Fenster zu erhalten, muss die zu erstellende Klasse von JFrame abgeleitet werden. Da Elemente dieser Klasse auf Ereignisse reagieren können sollen, müsste man diese Klasse zusätzlich von einer Klasse ableiten, die die Ereignissteuerung bereitstellt. Da in Java Mehrfachvererbung nicht zulässig ist, klappt dieses Verfahren nicht. Java stellt für diesen Fall Interfaces zur Verfügung. Ein Interface stellt dabei nur die MethodenKöpfe zur Verfügung. Die Methoden-Rümpfe müssen dann jeweils vom Programmierer an die Bedürfnisse des Programms angepasst werden. Hier der Code des Beispielprogramms: Zunächst die üblichen Pakete einbinden … import java.awt.*; import java.awt.event.*; import javax.swing.*; Die Klasse JRotGelbGruen von JFrame ableiten und da die Schaltflächen auf Ereignisse reagieren sollen, müssen Aktionsabhorcher (ActionListener) hinzugefügt werden. Das Interface ActionListener stellt dazu eine abstrakte Methode actionPerformed zur Verfügung, die je nach Bedarf implementiert werden muss. public class JRotGelbGruen extends JFrame implements ActionListener { copyleft:munz 27 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die notwendigen Objekte deklarieren. Hier werden die Schaltflächen deklariert; die Variable bgColor speichert die ursprüngliche Hintergrundfarbe. private Color bgColor; private JButton jbRot, jbGelb, jbGruen, jbNeutral, jbStop; private JPanel jpButtons; Hier beginnt der Konstruktor: public JRotGelbGruen(String title) { Den Konstruktor der Ober-Klasse aufrufen: super(title); Den ContentPane des JFrame JRotGelbGruen in der Instanz cp speichern: Container cp = getContentPane(); Eine Instanz jpButtons von JPanel erzeugen, das die Schaltflächen enthält. Die Anordnung der Schaltflächen wird durch ein FlowLayout geregelt. Dann die Hintergrundfarbe sichern. jpButtons = new JPanel(); jpButtons.setLayout(new FlowLayout()); bgColor = jpButtons.getBackground(); Jetzt müssen die Schaltflächen instanziiert und dem JPanel jpButtons hinzugefügt werden. Außerdem muss jeder Schaltfläche beim ActionListener angemeldet werden. Das geschieht mit der Methode addActionListener. Als Eingabe-Wert erwartet addActionListener einen ActionListener. In diesem Fall ist das die Klasse JRotGelbGruen selbst, was durch das Schlüsselwort this ausgedrückt wird, da JRotGelbGruen ja das Interface ActionListener implementiert. jbRot = new JButton("rot"); jbRot.addActionListener(this); jpButtons.add(jbRot); jbGelb = new JButton("gelb"); jbGelb.addActionListener(this); jpButtons.add(jbGelb); jbGruen = new JButton("grün"); jbGruen.addActionListener(this); jpButtons.add(jbGruen); jbNeutral = new JButton("neutral"); jbNeutral.addActionListener(this); jpButtons.add(jbNeutral); jbStop = new JButton("Ende"); jbStop.addActionListener(this); jpButtons.add(jbStop); Das JPanel jpButtons zum ContentPane des JFrame hinzufügen: cp.add(jpButtons); Die übliche Routine zum ordentlichen Beenden des Programms: addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent event) { System.exit(0); } } ); Größe festlegen und sicherstellen, dass das Fenster auch angezeigt wird: setSize(400, 200); setVisible(true); } copyleft:munz 28 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Hier beginnt die eigentliche Arbeit. Jeder Schaltfläche muss eine entsprechende Aktion zugeordnet werden. Das geschieht folgendermaßen. Mit getSource wird die Ereignisquelle ermittelt. Dann erfolgt je nach Ereignisquelle eine bestimmte Reaktion wie z.B. das Ändern der Hintergundfarbe. public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); } if if if if if (obj (obj (obj (obj (obj == == == == == jbRot) jpButtons.setBackground(Color.red); jbGelb) jpButtons.setBackground(Color.yellow); jbGruen) jpButtons.setBackground(Color.green); jbNeutral) jpButtons.setBackground(bgColor); jbStop) System.exit(0); Die Methode main hat wie üblich nicht viel zu tun … public static void main(String[] args) { new JRotGelbGruen("Rot - Gelb - Grün"); } } Das Fenster nach Anklicken der Schaltfläche „gelb“: 2.13.2 Auf Texteingaben reagieren Es soll ein Programm entwickelt werden, das zwei Textfelder enthält, eines für Euro-Werte und eines für Dollar-Werte. Bei Änderung des Dollar-Wertes soll der Euro-Wert entsprechend geändert werden und umgekehrt. Die Eingabe wird dabei mit Drücken der Eingabetaste abgeschlossen. Zunächst der Programm-Code mit Erläuterungen: Die üblichen Pakete importieren: import java.text.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; Neu ist das Paket java.text. Es enthält Werkzeuge zur formatierten Textausgabe. Die Zahlenwerte sollen auf zwei Dezimalen gerundet ausgegeben werden. Das Dezimaltrennzeichen ist das Komma, das Tausender-Trennzeichen der Punkt. Zunächst die Klasse JEuroDollarRechner von JFrame ableiten und das Interface ActionListener hinzufügen: public class JEuroDollarRechner extends JFrame implements ActionListener { Die Label und Textfelder deklarieren: private JLabel jlblEuro, jlblDollar, jlblKurs, jlblTitel; private JPanel jpNorth, jpCenter, jpSouth; private JTextField jtfEuro, jtfDollar; Einige notwendige Konstanten und Variablen deklarieren und initialisieren: private final static double KURS = 1.06886; // Kurs am 6.5.03 private double wertEuro = 1.0, wertDollar = KURS; copyleft:munz 29 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Eine Instanz von DecimalFormat zur formatierten Ausgabe deklarieren:; private DecimalFormat decF; Hier beginnt der Konstruktor: public JEuroDollarRechner(String title) { super(title); Das Format zur Dezimalausgabe festlegen: decF = new DecimalFormat("#,##0.00"); Das ContentPane von JEuroDollarRechner ermitteln und ein BorderLayout festlegen: Container cp = getContentPane(); cp.setLayout(new BorderLayout()); Der Titel: jpNorth = new JPanel(); jlblTitel = new JLabel("Euro-Dollar-Rechner"); jpNorth.add(jlblTitel); cp.add(jpNorth, BorderLayout.NORTH); Das jpCenter-Panel enthält die Textfelder mit den zugehörigen Labels. Zum Anordnen eignet sich eine 2×2-Matrix, realisiert durch ein GridLayout(2, 2): jpCenter = new JPanel(); jpCenter.setLayout(new GridLayout(2, 2)); jlblEuro = new JLabel("Euro: "); jpCenter.add(jlblEuro); Die Textfelder mit Startwerten füllen: jtfEuro = new JTextField(decF.format(wertEuro), 12); jtfEuro.setHorizontalAlignment(JTextField.RIGHT); jpCenter.add(jtfEuro); Damit das Textfeld auf die Eingabe reagieren kann, muss ihm ein ActionListener hinzugefügt werden: jtfEuro.addActionListener(this); jlblDollar = new JLabel("Dollar: "); jpCenter.add(jlblDollar); jtfDollar = new JTextField(decF.format(wertDollar), 12); jtfDollar.setHorizontalAlignment(JTextField.RIGHT); jpCenter.add(jtfDollar); Ebenfalls einen ActionListener hinzufügen: jtfDollar.addActionListener(this); cp.add(jpCenter, BorderLayout.CENTER); jpSouth = new JPanel(); jlblKurs = new JLabel("Kurs = " + Double.toString(KURS)); jpSouth.add(jlblKurs); cp.add(jpSouth, BorderLayout.SOUTH); Wie üblich zum Beenden: this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit( 0 ); } }); Packen und anzeigen: pack(); setVisible(true); } copyleft:munz 30 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Hier kommt die Hauptarbeit: public void actionPerformed(ActionEvent actionEvent) { Object obj = actionEvent.getSource(); if (obj == jtfEuro) { Die Eingabe könnte fehlerhaft sein. Deshalb muss sie ggf. abgefangen werden: try { Das Textfeld auslesen, in ein Dezimal-Format umwandeln und als double-Wert speichern: wertEuro = decF.parse(jtfEuro.getText()).doubleValue(); jtfEuro.setText(decF.format(wertEuro)); wertDollar = wertEuro*KURS; jtfDollar.setText(decF.format(wertDollar)); } Falls ein Eingabefehler aufgetreten ist, das Textfeld wieder mit dem ursprünglichen Wert füllen: catch (ParseException e) { jtfEuro.setText(decF.format(wertEuro)); } } Genauso mit den Dollar-Feld: if (obj == jtfDollar) { try { wertDollar = decF.parse(jtfDollar.getText()).doubleValue(); jtfDollar.setText(decF.format(wertDollar)); wertEuro = wertDollar/KURS; jtfEuro.setText(decF.format(wertEuro)); } catch (ParseException e) { jtfDollar.setText(decF.format(wertDollar)); } } } Die Methode main erstellt die Instanz von JEuroDollarRechner … public static void main(String[] args) { new JEuroDollarRechner("Euro-Dollar-Rechner"); } } 2.13.3 Die gleichzeitige Verwendung mehrerer EventListeners Der Euro-Dollar-Rechner soll so erweitert werden, dass bei Aktivierung eines Textfeldes automatisch der gesamte Text markiert wird. Im Folgenden sind nur die Änderungen aufgeführt. Zum ActionListener kommt nun ein FocusListener hinzu: public class JEuroDollarRechner extends JFrame implements ActionListener, FocusListener { copyleft:munz 31 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Da die Textfelder auf Eingabe und auf Veränderung reagieren sollen, müssen sie sich sowohl beim ActionListener als auch beim FocusListener anmelden: jtfEuro.addActionListener(this); jtfEuro.addFocusListener(this); … jtfDollar.addActionListener(this); jtfDollar.addFocusListener(this); Für den FocusListener müssen zwei Methoden implementiert werden: public void focusGained(FocusEvent focusEvent) { JTextField jtf = (JTextField) focusEvent.getSource(); jtf.selectAll(); } und public void focusLost(FocusEvent focusEvent) { JTextField jtf = (JTextField) focusEvent.getSource(); jtf.setText(decF.format( (jtf == jtfEuro ? wertEuro : wertDollar))); jtf.setCaretPosition(0); } 2.13.4 Ereignisse bei sehr vielen GUI-Elementen. Wenn eine Anwendung sehr viele Elemente besitzt, die Ereignisse auslösen können, sind die oben vorgestellten Verfahren sehr aufwendig zu implementieren. Hier verfährt man besser wie folgt: Man fasst GUI-Objekte mit ähnlichen Verhaltensweisen zusammen, indem man für diese eine eigene Klasse als Unterklasse von z.B. JButton erstellt und für diese Klasse einen benötigten EventListener implementiert. Diese Klasse kann als anonyme Klasse, als innere Klasse oder als externe Klasse realisiert sein. Das Beispielprogramm hierzu erstellt ein Feld von n×n Schaltflächen. Wenn auf eine der Schaltflächen gedrückt wird, soll in einem Label die Zeilen- und Spaltennummer der betreffenden Schaltfläche erscheinen. Die üblichen Pakete importieren: import java.awt.*; import java.awt.event.*; import javax.swing.*; Die Container-Klasse erstellen: public class ButtonFeld extends JFrame { private static final int ANZ = 5; Ein n×n-Array für die Schaltflächen deklarieren, die Instanzen der Klasse ActionButton sind: private ActionButton[][] buttons; Weitere GUI-Objekte: private JPanel buttonPanel; private JLabel label; Der Konstruktor: public ButtonFeld (String title) { super(title); addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); copyleft:munz 32 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Das Label für die Anzeige initialisieren: label = new JLabel( "kein Button gedrückt", SwingConstants.CENTER ); cp.add(label, BorderLayout.NORTH); buttonPanel = new JPanel(); cp.add(buttonPanel, BorderLayout.CENTER); buttonPanel.setLayout(new GridLayout(ANZ, ANZ)); Das n×n-Feld initialisieren: buttons = new ActionButton[ANZ][ANZ]; for (int i = 0; i < ANZ; i++) for (int j = 0; j < ANZ; j++) { buttons[i][j] = new ActionButton(i+1, j+1); buttonPanel.add(buttons[i][j]); } Für die Anzeige: } pack(); setVisible(true); Die Methode main: public static void main (String[] args) { new ButtonFeld("ButtonFeld"); } Für die Schaltflächen eine innere Klasse erstellen. Diese ist von JButton abgeleitet und implementiert einen ActionListener: private class ActionButton extends JButton implements ActionListener { private int zeile, spalte; Der Konstruktor der inneren Klasse: ActionButton(int i, int j) { zeile = i; spalte = j; setText("[" + i + ", " + j + "]"); addActionListener(this); } Die Methode actionPerformed muss implementiert werden: public void actionPerformed(ActionEvent e) { label.setText( "Button in " + zeile + ". Zeile, " + spalte + ". Spalte gedrückt" ); } // Ende von 'actionPerformed' } // Ende der inneren Klasse 'ActionButton' } // Ende der Hauptklasse 'ButtonFeld' Bei diesem Beispielprogramm hat jede Schaltfläche ihren eigenen ActionListener. Das hat den Vorteil, dass jede Schaltfläche selbst „weiß“ ob sie gedrückt wurde und wie sie dann zu reagieren hat. copyleft:munz 33 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.13.5 Benutzen von Adapter-Klassen In den obigen Programmen wurde die Ereignissteuerung durch Implementieren spezieller Interfaces realisiert. Dabei müssen alle Methoden des Interfaces implementiert werden, selbst wenn eine Methode überhaupt nicht benötigt wird. Der Programmcode enthält dann viele Methodendeklarationen mit leeren Methodenrümpfen. Das lässt sich durch die Verwendung von Adapter-Klassen vermeiden. Java stellt für jedes Interface, dass mehr als eine Methodendeklaration enthält, eine entsprechende Adapter-Klasse zur Verfügung. Eine Adapter-Klasse ist somit eine Implementierung eines Interfaces, wobei zunächst jede Methode in der Adapter-Klasse mit einem leeren Methodenrumpf implementiert wird. Bei der Programmerstellung leitet man die Ereignissteuerung von der benötigten Adapter-Klasse ab und braucht nur die benötigten Methoden zu überschreiben. Wenn der Code in der benötigten Methode nur aus wenigen Zeilen besteht, kann man seine eigene Klasse sogar als anonyme Klasse schreiben. Anonym bedeutet, dass weder für die abgeleitete Klasse noch deren Instanzen Bezeichner vergeben werden. Beim Compilieren wird dann eine Datei erzeugt, die den gleichen Dateinamen wie die Hauptklasse erhält, gefolgt von einem $-Zeichen mir anschließender fortlaufender Nummer, wie z.B. JGrafik$1.class. Diese Verfahren wurde in allen bisherigen Beispielprogrammen zum Schließen und Beenden des Programms angewandt. Dem Programmfenster, meist von JFrame abeleitet, wird mit der Methode addWindowListener ein WindowListener hinzugefügt. Dieser wird als anonyme Klasse von WindowAdapter abgeleitet und gleichzeitig mit der Deklaration der Methode als Parameter übergeben. Von den vielen Methoden in WindowListener wird hier nur windowClosing benötigt. Da hier eine Adapterklasse verwendet wird, muss nur eine Methode überschrieben werden. addWindowListener( //Methodenaufruf new WindowAdapter() { //Instanz der anonymen Klasse erzeugen public void windowClosing (WindowEvent evt) { //überschreiben System.exit(0); //nur eine Anweisung } } ); Im folgenden Beispielprogramm soll in einem Fenster mit der Maus gezeichnet werden können. Dazu muss auf Ereignisse reagiert werden können, die durch Klicken oder Ziehen mit der Maus ausgelöst werden. Maus-Klicks werden durch das Interface MouseListener behandelt, Ereignisse, die durch das Ziehen mit der Maus verursacht werden, durch das Interface MouseMotionListener. Das Interface MouseListener enthält die Methoden: mouseClicked(MouseEvent e) Wird aufgerufen, wenn auf ein Element mit der Maus geklickt (gedrückt und losgelassen) wurde. mouseEntered(MouseEvent e) Wird aufgerufen, wenn die Maus auf ein Element geführt wird. mouseExited(MouseEvent e) Wird aufgerufen, wenn die Maus aus einem Element heraus geführt wird. mousePressed(MouseEvent e) Wird aufgerufen, wenn man die Maustaste auf ein Element drückt. mouseReleased(MouseEvent e) Wird aufgerufen, wenn man die Maustaste auf einem Element loslässt. Das Interface MouseMotionListener enthält die Methoden: mouseDragged(MouseEvent e) Wird aufgerufen, wenn eine Maustaste gedrückt ist und die Maus dabei bewegt wird. mouseMoved(MouseEvent e) Wird aufgerufen, wenn der Maus-Zeiger bewegt wird und keine Maustaste gedrückt ist. copyleft:munz 34 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Für dieses Beispielprogramm werden die Methoden mousePressed und mouseDragged benötigt. Deshalb wird die Ereignissteuerung mit Hilfe der zugehörigen Adapter-Klassen MouseAdapter und MouseMotionAdapter realisiert. Das Programm JGrafik erhält eine zusätzliche Schaltfläche um die Zeichnung zu löschen. Als Zeichenfläche wird eine erweiterte Version von GrafikPanel verwendet, dessen Quelltext erläutert werden soll: Zunächst die benötigten Pakete importieren: import java.awt.*; import java.awt.image.*; import java.awt.event.*; import javax.swing.*; Die Klasse deklarieren: public class GrafikPanel extends JPanel { private int breite, hoehe; private int x, y; private BufferedImage image; private Graphics imgG; Der Konstruktor: public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); setBackground(Color.WHITE); erstelleGrafik(); MouseListener und MousMotionListener hinzufügen: addMouseListener(new MyMouseAdapter()); addMouseMotionListener(new MyMouseMotionAdapter()); } Ein Image und dessen Grafikkontext bereitstellen: public void erstelleGrafik() { image = new BufferedImage( breite, hoehe, BufferedImage.TYPE_INT_RGB ); imgG = image.createGraphics(); loescheGrafik(); } copyleft:munz 35 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Eine Methode zum Löschen der Grafik bereitstellen. Diese Methode wird vom ActionListener der Schaltfläche zum Löschen der Zeichnung aufgerufen: public void loescheGrafik() { imgG.setColor(getBackground()); imgG.fillRect(0, 0, breite, hoehe); imgG.setColor(Color.BLACK); repaint(); } Die Methode paintComponent stellt das Image auf der Zeichenfläche dar: public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(image, 0, 0, this); } Eine innere Klasse bereitstellen, die von MouseAdapter abgeleitet ist: private class MyMouseAdapter extends MouseAdapter { public void mousePressed(MouseEvent e) { Nur reagieren, wenn die linke Maustaste gedrückt wurde: if (SwingUtilities.isLeftMouseButton(e)) { Mausposition beim Drücken der linken Maustaste merken: x = e.getX(); y = e.getY(); } } // Ende von 'mousePressed' } // Ende von 'MyMousAdapter' Eine innere Klasse bereitstellen, die von MouseMotionAdapter abgeleitet ist: private class MyMouseMotionAdapter extends MouseMotionAdapter { public void mouseDragged(MouseEvent e) { Ebenfalls nur reagieren, wenn die linke Maustaste gedrückt wurde: if (SwingUtilities.isLeftMouseButton(e)) { int x_neu = e.getX(); int y_neu = e.getY(); Line vom gespeicherten Punkt zum aktuellen Punkt zeichnen: imgG.drawLine(x, y, x_neu, y_neu); x = x_neu; y = y_neu; Zeichnung wurde verändert, Grafik neu zeichnen: repaint(); } } // Ende von 'mouseDragged' } // Ende von 'MyMouseMotionAdapter' } // Ende von 'GrafikPanel' 2.13.6 Getrennte GUI-Klassen und Klassen zur Ereignisbehandlung Für große Programme wird empfohlen, aus Gründen der Übersichtlichkeit und der Programmsicherheit GUI-Klassen und Klassen zur Ereignisbehandlung zu trennen. Wenn die Ereignissteuerung Elemente aus der GUI-Klasse ansprechen soll, muss man sicherstellen, dass diese Elemente von der Ereignissteuerung auch angesprochen werden können. Das erreicht man z.B. dadurch, dass man die aktuelle Instanz der GUI-Klasse dem Konstruktor der Klasse zur Ereignissteuerung übergibt. Eine ausführliche Diskussion dieses Verfahrens würde den Rahmen dieser Handreichung sprengen. Es sei dafür auf die umfangreicheren Beispielprogramme auf der beiliegenden CD-ROM verwiesen. copyleft:munz 36 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.14 Die GUI-Klassen zum Miniprojekt „Figuren“ Bisher wurden zum Projekt „Figuren“ nur die zugehörigen Fachklassen vorgestellt. Die Figuren sollen aber auch konkret auf dem Bildschirm dargestellt werden. Hierzu benötigen wir weitere Klassen, GrafikPanel und FigurFrame im Paket figur_gui. JDemoFigur als Unterklasse von FigurFrame erzeugt dann in seiner Methode main eine Instanz von sich, bzw. von FigurFrame. Dabei wird das Programmfenster auf dem Bildschirm dargestellt. Genauso könnte man FigurFrame in einem JApplet darstellen. Hier muss man nur aufpassen, dass der WindowListener, der für das ordentliche Schließen und Beenden des Programms sorgt, entfernt wird, da Applets keine Programme beenden dürfen. 2.14.1 Die Quelltexte Das Zeichenfenster: package figur_gui; import figur.*; import java.awt.*; import javax.swing.*; public class GrafikPanel extends JPanel{ private int width, height; private Figur dieFigur; Der Konstruktor legt Breite und Höhe fest und erzeugt einen Rahmen. public GrafikPanel(int width, int height){ super(); this.width = width; this.height = height; setPreferredSize(new Dimension (width, height)); setBorder(BorderFactory.createLineBorder(Color.darkGray, 2)); setBackground(Color.WHITE); } Falls die Figur geändert wurde, muss sie der Zeichenfläche neu übergeben werden. public void setFigur(Figur dieFigur) { this.dieFigur = dieFigur; } Zum Darstellen von Zeichnungen muss paintComponent aus JComponent überschrieben werden. In dieser Methode wird zeigeDich von Figur aufgerufen bzw. der Hintergrund gelöscht. public void paintComponent(Graphics g){ super.paintComponent(g); // Nur wenn ein Test instanziiert ist, soll gezeichnet werden. if (dieFigur != null) dieFigur.zeigeDich(g); else { g.setColor(getBackground()); g.drawRect(0, 0, getSize().width, getSize().height); } } } copyleft:munz 37 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Der zugehörige JFrame ist das eigentliche GUI-Fenster. Zunächst werden alle benötigten Pakete importiert, insbesondere figur. Dann werden Zeichenfläche, Schaltflächen, Textfelder und Auswahlfelder erzeugt. package figur_gui; import figur.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; Da das Programm auf Benutzeraktionen reagieren soll, muss das Interface ActionListener implementiert werden. public class FigurFrame extends JFrame implements ActionListener { Lokale Konstanten und Attribute festlegen: // Konstanten private static final int BREITE = 200, HOEHE = 200; private static int xMitte, yMitte, b = 0; //Attribute private Box boxFarben, boxFiguren, boxBreite, boxEckenZahl, boxWest, boxEast; private JButton bnZeichne; private JLabel lblTitel, lblCopyRight, lblBreite, lblEckenZahl; private JTextField tfBreite, tfEckenZahl; private ButtonGroup bgFarben, bgFiguren; private JRadioButton rbRot, rbGruen, rbBlau, rbSchwarz, rbKreis, rbQuadrat, rbVielEck; private GrafikPanel zeichenFlaeche; private Figur figur; private Color farbe = Color.BLACK; private int breite = 100, eckenZahl = 6; Der Konstruktor: public FigurFrame(String titel) { super(titel); Zum ordentlichen Beenden des Programms einen WindowListener hinzufügen: addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } }); Anfangswerte festlegen: xMitte = BREITE/2; yMitte = HOEHE/2; b = Math.min(BREITE, HOEHE)*9/10; Für den JFrame ein BorderLayout festlegen und ein JLabel als Titel erstellen: JPanel cp = new JPanel(new BorderLayout(15, 5)); setContentPane(cp); lblTitel = new JLabel("Figuren", SwingConstants.CENTER); lblTitel.setFont(new Font("SansSerif", Font.BOLD, 30)); cp.add(lblTitel, BorderLayout.NORTH); In den West-Bereich des BorderLayouts wird eine Box mit horizontaler Ausrichtung eingefügt. Diese Box besteht wieder aus zwei weiteren Boxen mit vertikaler Ausrichtung, die jeweils die JRadioButtons für Farbauswahl und Figurenwahl enthalten. boxWest = new Box(BoxLayout.X_AXIS); cp.add(boxWest, BorderLayout.WEST); copyleft:munz 38 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Box für die Farbenauswahl: boxFarben = new Box(BoxLayout.Y_AXIS); bgFarben = new ButtonGroup(); rbSchwarz = new JRadioButton("schwarz", true); rbGruen = new JRadioButton("grün", false); rbRot = new JRadioButton("rot", false); rbBlau = new JRadioButton("blau", false); bgFarben.add(rbSchwarz); bgFarben.add(rbGruen); bgFarben.add(rbRot); bgFarben.add(rbBlau); boxFarben.add(rbSchwarz); boxFarben.add(rbGruen); boxFarben.add(rbRot); boxFarben.add(rbBlau); boxFarben.add(Box.createVerticalGlue()); boxWest.add(boxFarben); Die Box für die Figurenauswahl: boxFiguren = new Box(BoxLayout.Y_AXIS); bgFiguren = new ButtonGroup(); rbKreis = new JRadioButton("Kreis", true); rbKreis.addActionListener(this); rbQuadrat = new JRadioButton("Quadrat", false); rbQuadrat.addActionListener(this); rbVielEck = new JRadioButton("Vieleck", false); rbVielEck.addActionListener(this); bgFiguren.add(rbKreis); bgFiguren.add(rbQuadrat); bgFiguren.add(rbVielEck); boxFiguren.add(rbKreis); boxFiguren.add(rbQuadrat); boxFiguren.add(rbVielEck); boxFiguren.add(Box.createVerticalGlue()); boxWest.add(boxFiguren); Eine Box mit vertikaler Ausrichtung für den Ost-Bereich des BorderLayouts festlegen. Sie enthält das Textfeld zur Größeneingabe, eine weitere Box zur Eingabe der Eckenzahl und die Schaltfläche „Zeichne Figur!“. boxEast = new Box(BoxLayout.Y_AXIS); cp.add(boxEast, BorderLayout.EAST); Dimension groesseLb = new Dimension(100, 24); Dimension groesseTf = new Dimension(40, 24); boxBreite = new Box(BoxLayout.X_AXIS); lblBreite = new JLabel("Durchmesser: ", JLabel.RIGHT); groesseAnpassen(lblBreite, groesseLb); boxBreite.add(lblBreite); tfBreite = new JTextField(Integer.toString(breite)); groesseAnpassen(tfBreite, groesseTf); tfBreite.setHorizontalAlignment(JTextField.RIGHT); boxBreite.add(tfBreite); boxEast.add(boxBreite); copyleft:munz 39 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Box mit horizontaler Ausrichtung für das Textfeld zur Eingabe der Eckenzahl, dem ein Label mit zugehörigem Text zugeordnet ist. boxEckenZahl = new Box(BoxLayout.X_AXIS); lblEckenZahl = new JLabel("Anzahl Ecken: ", JLabel.RIGHT); groesseAnpassen(lblEckenZahl, groesseLb); boxEckenZahl.add(lblEckenZahl); tfEckenZahl = new JTextField(Integer.toString(eckenZahl)); groesseAnpassen(tfEckenZahl, groesseTf); tfEckenZahl.setHorizontalAlignment(JTextField.RIGHT); tfEckenZahl.setEnabled(false); boxEckenZahl.add(tfEckenZahl); boxEast.add(boxEckenZahl); bnZeichne = new JButton("Zeichne Figur!"); bnZeichne.setAlignmentX(0.5f); bnZeichne.addActionListener(this); boxEast.add(bnZeichne); Im Zentrum des BorderLayouts befindet sich die Zeichenfläche zum Darstellen der Figuren. zeichenFlaeche = new GrafikPanel(BREITE, HOEHE); zeichenFlaeche.setFigur(figur); cp.add(zeichenFlaeche, BorderLayout.CENTER); Ein kleines Label im Süd-Bereich der BorderLayouts. lblCopyRight = new JLabel( "Java II - Pohlig/Taulien, \u00a9 2002-2003", SwingConstants.RIGHT ); lblCopyRight.setFont(new Font("SansSerif", Font.PLAIN, 11)); cp.add(lblCopyRight, BorderLayout.SOUTH); Das Erscheinungsbild des Programm-Fensters festlegen: pack(); setResizable(false); // Auf dem Bildschirm zentrieren und sichtbar machen Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((screenSize.width - this.getSize().width)/2, (screenSize.height - this.getSize().height)/2 ); setVisible(true); } Immer wiederkehrende Arbeiten lagert man am besten in private Methoden aus: private void groesseAnpassen(JComponent comp, Dimension groesse) { comp.setPreferredSize(groesse); comp.setMinimumSize(groesse); comp.setMaximumSize(groesse); } Hier beginnt die Ereignissteuerung. Ereignisse können die JRadioButtons oder die Schaltfläche „Zeichne Figur!“ auslösen. public void actionPerformed(ActionEvent e) { // Welches Objekt hat das Ereignis ausgelöst? Object obj = e.getSource(); copyleft:munz 40 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Fall das Ereignis von einem JRadioButton zur Figurenwahl ausgelöst wurde, wird das Textfeld zur Eingabe der Eckenzahl aktiviert oder deaktiviert, jenachdem ob ein Vieleck gezeichnet warden soll oder nicht. if (obj == rbKreis || obj == rbQuadrat){ tfEckenZahl.setEnabled(false); } if (obj == rbVielEck){ tfEckenZahl.setEnabled(true); } Die Schaltfläche “Zeichne Figur!” wurde gedrückt. Dann wird zuerst die Figurenbreite aus dem zugehörigen Textfeld ausgelesen, ggf. wird für ein Vieleck die Eckenzahl ermittelt. Dann wird die Figurenfarbe bestimmt. Anschließend wird eine neue Figur (Kreis, Quadrat oder VielEck) erzeugt und der Klassenvariablen figur zugewiesen. if (obj == bnZeichne) { Figurenbreite auslesen, ggf. Fehler abfangen: try { breite = Integer.parseInt(tfBreite.getText()); } catch (NumberFormatException except) { tfBreite.setText(Integer.toString(breite)); } Eckenzahl auslesen, ggf. Fehler abfangen: try { eckenZahl = Integer.parseInt(tfEckenZahl.getText()); } catch (NumberFormatException except) { tfEckenZahl.setText(Integer.toString(eckenZahl)); } Figurenfarbe bestimmen. if if if if (rbSchwarz.isSelected()) farbe = Color.BLACK; (rbRot.isSelected()) farbe = Color.red; (rbGruen.isSelected()) farbe = Color.green; (rbBlau.isSelected()) farbe = Color.blue; Kreis war ausgewählt: if (rbKreis.isSelected()) { figur = new Kreis(xMitte, yMitte, breite/2, farbe); tfBreite.setText( Integer.toString(((Kreis)figur).getRadius()*2) ); } Quadrat war ausgewählt: if (rbQuadrat.isSelected()) { figur = new Quadrat(xMitte, yMitte, breite, farbe); tfBreite.setText( Integer.toString(((Quadrat)figur).getSeite()) ); } copyleft:munz 41 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Vieleck war ausgewählt: if (rbVielEck.isSelected()) { figur = new VielEck( xMitte, yMitte, breite/2, eckenZahl, farbe ); tfBreite.setText( Integer.toString(((VielEck)figur).getRadius()*2) ); tfEckenZahl.setText( Integer.toString(((VielEck)figur).getEckenZahl()) ); } Der Zeichenfläche die neue Figur übergeben und alles neu zeichnen: zeichenFlaeche.setFigur(figur); } repaint(); } } Das „Hauptprogramm“: import figur_gui.*; public class JDemoFigur extends FigurFrame { public JDemoFigur(String titel) { super(titel); } main erzeugt eine Instanz von JDemoFigur und damit von FigurFrame. Die zugehörigen Bilder sind im Kapitel zu OOP zu finden. public static void main(String args[]){ new JDemoFigur("Figuren"); } } Auch ein Applet ist möglich, in Swing ist es ein JApplet: import figur_gui.*; import javax.swing.*; import java.awt.event.*; public class JDemoFigurApplet extends JApplet { In einem Applet gibt es kein main, die Instanz von FigurFrame erzeugt man direkt in init(). Da ein Applet keine Rechte hat, ein Programm zu beenden muss man den zugehörigen WindowListener entfernen. Mit javax.swing.JComponent.getWindowListeners() kann man sich alle WindowListener besorgen, und diese dann der Reihe nach entfernen (hier gibt es wohl nur einen). public void init(){ FigurFrame frame = new FigurFrame("Figuren"); WindowListener[] winListener = frame.getWindowListeners(); for (int i = 0; i < winListener.length; i++) frame.removeWindowListener(winListener[i]); } } copyleft:munz 42 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.15 Einige GUI-Beispielprogramme 2.15.1 Fonts darstellen Das Programm stellt eine Zeichenkette mit allen ihren Metriken in einem Grafikfenster dar. Die Zeichenkette kann in einem Textfeld (JTextField) eingegeben werden. Schriftart und Schriftstil wählt man jeweils mit Hilfe eines DropDownListenfelds (JComboBox). Die Schriftgröße kann entweder mit Hilfe eines Drehfelds(JSpinner) oder eines Schiebereglers (JSlider) eingestellt werden. Eine Schaltfläche (JButton) aktualisiert ggf. die Ausgabe. Die Abbildung zeigt das gewählte Layout: Der folgende Quelltext des Programms zeigt nur diejenigen Ausschnitte, die gegenüber den vorigen Programmbeispielen Neuerungen aufweisen. Zunächst die Grafikpakete importieren. Neu ist das Paket swing.event. Hier sind Methoden enthalten, mit denen man auf Ereignisse von JSpinner und JSlider reagiert. import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; JFonts erbt von JFrame und bindet die Schnittstellen ActionListener und ChangeListener ein. public class JFont extends JFrame implements ActionListener, ChangeListener { Schriftname und Schriftstil werden in Feldern gespeichert: private String fontFamilyNames[]; private String fontStyleNames[] = { "normal", "fett", "kursiv", "fett-kursiv" }; private int fontStyles[] = {Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC}; Einige Konstanten festlegen, die bei Programmstart benötigt werden: private static final int MIN_FONT_SIZE = 25, MAX_FONT_SIZE = 200; private String text = "Font-Beispiel"; private int displayFontSize = 72; // logischer Font für den Anfang // Auswahl aus: SansSerif, Serif, Monospaced, Dialog, InputDialog private String displayFontName = "Serif"; private int displayFontStyle = fontStyles[0]; private String displayFontStyleName = fontStyleNames[0]; copyleft:munz 43 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Der darzustellende Font: private Font font; Ein JTextField für die Text-Eingabe: private JTextField jtfFontText; Ein JSpinner und ein ein JSlider für die Schriftgröße bereitstellen. Dabei ist jspModel ein SpinnerNumberModel, das das Verhalten von JSpinner festlegt. Hier wollen wir, dass die Schriftgröße in ganzahligen Schritten verändert wird. private JSpinner jspFontSize; private SpinnerNumberModel jspModel; private JSlider jslFontSize; Je eine JComboBox für Schriftname und Schriftstil anlegen: private JComboBox jcbFontName, jcbFontStyle; Eine Schaltfläche um die Anzeige ggf. zu aktualisieren: private JButton jbOK; JFonts erhält ein BorderLayout. Für den Nord- und Ost-Bereich richten wir je eine Box, für den Süd-Bereich ein JPanel ein. Der Zentral-Bereich ist das eigentliche Ausgabefenster: private Box boxNorth, boxEast; private JPanel jpSouth; private JFontPanel jfPanel; Hier beginnt der Konstruktor: public JFont(String title) { super(title); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); Die installierten Schriftarten laden und den Anfangs-Font festlegen: GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); fontFamilyNames = ge.getAvailableFontFamilyNames(); font = new Font( displayFontName, displayFontStyle, displayFontSize ); Die Aktionselemente erzeugen. Sie kommen in den Nord-Bereich des Grafikfensters. Dabei wird der Beispiel-Text, wegen des Titel-Rahmens in eine separate Box gepackt: boxNorth = new Box(BoxLayout.X_AXIS); Box boxFontText = new Box(BoxLayout.X_AXIS); boxFontText.setBorder( BorderFactory.createTitledBorder("Beispiel-Text") ); Ein Textfeld für den Eingabetext: jtfFontText = new JTextField(text); jtfFontText.addActionListener(this); boxFontText.add(jtfFontText); boxFontText.add(Box.createHorizontalGlue()); boxNorth.add(boxFontText); Eine JComboBox mit Titel-Rahmen zur Font-Name-Auswahl: jcbFontName = new JComboBox(fontFamilyNames); jcbFontName.setBorder( BorderFactory.createTitledBorder("Name") ); jcbFontName.setSelectedItem(font.getName()); jcbFontName.setEditable(false); jcbFontName.addActionListener(this); boxNorth.add(jcbFontName); copyleft:munz 44 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Eine JComboBox mit Titel-Rahmen zur Font-Stil-Auswahl: jcbFontStyle = new JComboBox(fontStyleNames); jcbFontStyle.setBorder( BorderFactory.createTitledBorder("Stil")); jcbFontStyle.setSelectedIndex(0); jcbFontStyle.setEditable(false); jcbFontStyle.addActionListener(this); boxNorth.add(jcbFontStyle); Ein JSpinner mit Titel-Rahmen für die Font-Größe: jspModel = new SpinnerNumberModel( displayFontSize, MIN_FONT_SIZE, MAX_FONT_SIZE, 1 ); jspFontSize = new JSpinner(jspModel); jspFontSize.setBorder( BorderFactory.createTitledBorder("Größe") ); jspFontSize.addChangeListener(this); boxNorth.add(jspFontSize); Alles in die Nord-Box tun: cp.add(boxNorth, BorderLayout.NORTH); Die Ost-Box für den JSlider erzeugen: boxEast = new Box(BoxLayout.Y_AXIS); boxEast.setBorder(BorderFactory.createTitledBorder("Größe")); Ein JSlider-Element mit Titel-Rahmen für die Schriftgröße: jslFontSize = new JSlider( JSlider.VERTICAL, MIN_FONT_SIZE, MAX_FONT_SIZE, displayFontSize ); jslFontSize.setBorder( BorderFactory.createEmptyBorder(10, 10, 0, 0) ); Aussehen des JSliders festlegen: jslFontSize.setMajorTickSpacing(25); jslFontSize.setMinorTickSpacing(5); jslFontSize.setPaintTicks(true); jslFontSize.setPaintLabels(true); jslFontSize.setPaintTrack(true); jslFontSize.setValue(displayFontSize); jslFontSize.addChangeListener(this); boxEast.add(jslFontSize); boxEast.add(Box.createVerticalGlue()); cp.add(boxEast, BorderLayout.EAST); Das Ausgabe-Fenster erhält zusätzliche Scroll-Balken: jfPanel = new JFontPanel(font, text); JScrollPane jfScrollPane = new JScrollPane(jfPanel); cp.add(jfScrollPane, BorderLayout.CENTER); Die Aktualisierungs-Schaltfläche: jpSouth = new JPanel(); jbOK = new JButton("OK"); jbOK.addActionListener(this); jpSouth.add(jbOK); cp.add(jpSouth, BorderLayout.SOUTH); copyleft:munz 45 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Standard-Methode um das Fenster „Windows-mäßig“ zu schließen: this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit( 0 ); } }); … und alles anzeigen: pack(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((screenSize.width - getSize().width)/2, (screenSize.height - getSize().height)/2); setVisible(true); } Die Methoden des ChangeListeners: public void stateChanged(ChangeEvent event) { Object obj = event.getSource(); Eine andere Font-Größe wurde mit dem JSlider eingestellt: if (obj == jslFontSize) { displayFontSize = jslFontSize.getValue(); jspFontSize.setValue(new Integer(displayFontSize)); } Eine andere Font-Größe wurde mit dem JSpinner eingestellt: if (obj == jspFontSize) { displayFontSize = jspModel.getNumber().intValue(); jslFontSize.setValue(displayFontSize); } Einen neuen Font erstellen: font = new Font( displayFontName, displayFontStyle, displayFontSize ); jfPanel.setFont(font); jfPanel.updateFontMetrics(); Falls der darzustellende Text über die Frame-Grenzen hinaus ragt, müssen Scroll-Leisten gezeichnet werden, bzw. entfernt werden, wenn der Text in das gesamte Fenster passt: jfPanel.revalidate(); Ggf. alles neu zeichnen: repaint(); } // Ende von stateChanged Die Methoden des ActionListeners: public void actionPerformed(ActionEvent event) { Object obj = event.getSource(); OK wurde gedrückt: if (obj == jbOK) { text = jtfFontText.getText(); displayFontName = (String) jcbFontName.getSelectedItem(); int index = jcbFontStyle.getSelectedIndex(); displayFontStyle = fontStyles[index]; displayFontStyleName = fontStyleNames[index]; } Der Text wurde verändert, die Änderung mit der Return-Taste bestätigt: if (obj == jtfFontText) { text = jtfFontText.getText(); } copyleft:munz 46 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Es wurde ein anderer Font-Name ausgewählt: if (obj == jcbFontName) { displayFontName = (String) jcbFontName.getSelectedItem(); } Es wurde ein anderer Font-Stil ausgewählt: if (obj == jcbFontStyle) { int index = jcbFontStyle.getSelectedIndex(); displayFontStyle = fontStyles[index]; displayFontStyleName = fontStyleNames[index]; } Nach allen Aktionen einen neuen Font erzeugen: font = new Font( displayFontName, displayFontStyle, displayFontSize ); jfPanel.setText(text); jfPanel.setFont(font); jfPanel.updateFontMetrics(); Falls der darzustellende Text über die Frame-Grenzen hinaus ragt, müssen Scroll-Leisten gezeichnet werden, bzw. entfernt werden, wenn der Text in das gesamte Fenster passt: jfPanel.revalidate(); Ggf. alles neu zeichnen: repaint(); } // Ende von actionPerformed main tut nicht viel: public static void main(String[] args) new JFont("Font-Beispiel"); } } copyleft:munz 47 { Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.15.2 JDraw, ein kleines Grafik-Programm Es soll ein Grafik-Programm entwickelt werden, das verschiedene Elemente wie Linien, Rechtecke, Rechtecke mit abgerundeten Ecken oder Ellipsen gefüllt oder ungefüllt darstellen kann. Beim Festhalten der Umschalttaste sollen die Linien nur im 0°-, 45°- und 90°-Winkel gezeichnet werden. Rechtecke und Ellipsen werden dann zu Quadraten bzw. Kreisen. Dieses Programm könnte man beliebig erweitern; z.B. könnte man die erzeugten Grafiken in einer Datei abspeichern oder aus einer Datei laden. Es sollte möglich sein, einzelne Figuren nachträglich zu markieren, dann zu verändern, zu verschieben oder zu löschen. Weitere Anregungen zur Erweiterung liefern die bekannten Grafikprogramme. Zur Darstellung der Figuren wurden die Methoden der Klasse Java2D verwendet, dabei wurden eine hohe Auflösung und Antialiasing verwendet. Die Farbauswahl geschieht mit Hilfe der Swing-Klasse ColorChooser. Dieses Programmbeispiel zeigt deutlich die Vorteile einer konsequent umgesetzten objektorientierten Programmierung. Auch ohne diese möglichen Erweiterungen erscheint die gestellte Aufgabe hinreichend komplex. Dennoch benötigt die Realisierung in Java nur wenige Seiten Quellcode. Die Schaltflächen werden samt zugehörigen Aktionen in einer Klasse DrawMenuBar erzeugt, die von JMenuBar abgeleitet ist. Eine JMenuBar ist Bestandteil eines JFrames und ist deshalb sehr leicht hinzuzufügen. Um das Aussehen der Schaltflächen zu ändern, mussten für jeden möglichen Zustand einer Schaltfläche (d.h. ausgewählt, nicht ausgewählt, Maus über der ausgewählten bzw. nicht ausgewählten Schaltfläche) jeweils mehrere Grafiken pro Schaltfläche im gif-Format erstellt werden. Die Klasse JMenuBar die den JColorChooser zur Farbauswahl enthält, eine Klasse zur Auswahl der Linienbreite (ComboLineWidth abgeleitet von JComboBox) und eine Klasse zum Beenden des Programms (JDrawWindowCommand abgeleitet von WindowAdapter) wurden in das Paket drawgui gepackt. Um die verschiedenen Figuren darstellen zu können, wurde eine Klasse Figur erzeugt. Eine Figur kennt im wesentlichen ihren Ort, das sind die Koordinaten der linken oberen Ecke, ihre Breite und ihre Höhe. Weitere Eigenschaften sind Randfarbe und Füllfarbe. Zudem weiß jede Figur selbst, wie sie gezeichnet wird. Da Figur aber noch nichts von Rechteck, Ellipse oder Linie weiß, können die Methoden drawBackground und drawForeground nur abstrakt ausgeführt werden. Sie bekommen also den Modifizierer abstract vor den Methodenkopf vorangestellt, ohne den Methodenrumpf weiter auszuführen. Dies bleibt den von Figur abgeleiteten Klassen vorbehalten. Alle Figuren-Klassen wurden im Paket figuren zusammengefasst. copyleft:munz 48 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Klasse Figur: package figuren; import java.awt.*; import java.awt.geom.*; abstract public class Figur { protected protected protected protected protected protected protected Point p1, p2; String name, fillName; Shape shape = null; Color lineColor = Color.BLACK; Color fillColor = Color.WHITE; float lineWidth = 1.0f; boolean filled = false; public Figur(Point p1, Point p2) { this.p1 = p1; setEndPunkt(p2); } abstract public void setEndPunkt(Point p2); public void setLineColor(Color lineColor) { this.lineColor = lineColor; } public void setFillColor(Color fillColor) { this.fillColor = fillColor; } public void setLineWidth(float lineWidth) { this.lineWidth = lineWidth; } public void setFilled(boolean filled) { this.filled = filled; } public void drawFigure(Graphics2D g2D) { if (p1 != null && p2 != null) { if (filled) { g2D.setColor(fillColor); g2D.fill(shape); } g2D.setColor(lineColor); g2D.setStroke(new BasicStroke(lineWidth)); g2D.draw(shape); } } public String toString() { return (filled ? fillName : name) + ": [" + (p1 == null ? " " : p1.x + ", " + p1.y) + "], [" + (p2 == null ? " " : p2.x + ", " + p2.y) + "]"; } } copyleft:munz 49 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Klasse Rechteck leitet sich von Figur ab: package figuren; import java.awt.*; import java.awt.geom.*; public class Rechteck extends Figur { public Rechteck(Point p1, Point p2) { super(p1, p2); name = "Rechteck"; fillName = "Gefülltes Rechteck"; } } public void setEndPunkt(Point p2) { if (p2 != null) { this.p2 = p2; shape = new Rectangle2D.Double( (p2.x > p1.x ? p1.x : p2.x), (p2.y > p1.y ? p1.y : p2.y), Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y)); } } Die Klasse Quadrat ist dann eine Unter-Klasse von Rechteck: package figuren; import java.awt.*; public class Quadrat extends Rechteck { public Quadrat(Point p1, Point p2) { super(p1, p2); name = "Quadrat"; fillName = "Gefülltes Quadrat"; } } public void setEndPunkt(Point p2) { if (p2 != null) { int d = Math.max(Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y) ); super.setEndPunkt( new Point(p1.x + (p2.x > p1.x ? d : -d), p1.y + (p2.y > p1.y ? d : -d) ) ); } } Die weiteren Klassen RundesRechteck, RundesQuadrat, Ellipse, Kreis, Linie und SonderLinie werden analog gebildet. Die in der Klasse Graphics enthaltenen Methoden drawRect, drawEllipse usw. gehen immer davon aus, dass der Startpunkt links oben liegt. Nun kann man eine Figur auch von rechts unten nach links oben aufziehen, das muss beim Zeichnen dann berücksichtigt werden. Im übrigen werden Ort und Größe auch von Ellipsen, Kreisen und Linien durch Ort und Größe eines gedachten umbeschriebenen Rechtecks bestimmt. Die Hauptklasse JDraw, sie enthält die Klasse main, wird von JFrame abgeleitet und bekommt ein BorderLayout. Der Nord-Bereich enthält die Schaltflächen zur Auswahl der Figuren, zum Füllen und copyleft:munz 50 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java zum Neuzeichnen und zur Farbauswahl. Der eigentliche Zeichenbereich ist im Zentrum, der Süd-Bereich enthält lediglich eine Statuszeile. PaintPanel muss auf Maus- und Tastatur-Ereignisse reagieren können. Das Interface MouseListener enthält mehrere abstrakte Methoden, die alle überschrieben werden müssen, wenn man dieses Interface implementieren will, selbst wenn der Methodenrumpf leer bleiben sollte. In diesem Fall erstellt man besser eine neue Klasse DrawPanelMouseListener, die von der AdapterKlasse MouseAdapter abgeleitet ist. Für das Bewegen der Maus gibt es die Klasse DrawPanelMouseMotionListener ,von MouseMotionAdapter abgeleitet, und für die Tastatursteuerung gibt es die Klasse DrawPanelKeyListener, abgeleitet von KeyAdapter. DrawPanelKeyListener, DrawPanelMouseListener und DrawPanelMouseMotionListener sind als innere Klassen von DrawPanel realisiert. Das hat den Vorteil, dass ihre Methoden direkt auf die Klassenvariablen von DrawPanel zugreifen können. Die Ereignissteuerung in JDraw ist etwas komplexer. Zum einen lösen die Schaltflächen in JDraw Ereignisse aus, zum andern hängen diese von der Belegung der Klassenvariablen in DrawPanel ab. Somit wurde die Ereignissteuerung von JDraw in eine externe Klasse JDrawActionCommand gepackt. Auch das Schließen des JDraw-Fensters ist aufwendiger als bisher und in der externen Klasse JDrawWindowCommand realisiert. Es folgen Auszüge aus den Programmcodes der vorgestellten Klassen. Die Ereignissteuerung ist vollständig in DrawMenuBar untergebracht: package drawgui; import figuren.*; Alle benötigten AWT- und Swing-Pakete importieren, java.util wird für die Container-Klasse Vector benötigt: import java.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.colorchooser.*; Zunächst einige Konstanten als Arrays festlegen: public class DrawMenuBar extends JMenuBar { // Dateinamen der Button-Icons: private final static String[] btnImgName = { "new", "fill", "linecolor", "fillcolor" }; // Dateinamen der Figur-Icons: private final static String[] figImgName = { "line", "rect", "roundrect", "oval" }; // Dateinamen der Linienbreite-Icons: private final static String[] lineImgName = { "width1", "width2", "width3", "width4", "width5" }; Texte für die kleinen Sprechblasen festlegen: private final static String[] btnFillToolTipText = { "Füllen ein", "Füllen aus" }; private final static String[] btnToolTipText = { "neu", "Linienfarbe wählen", "Füllfarbe wählen" }; copyleft:munz 51 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Texte für die Statuszeile festlegen: private final static String[][] statusText = { {"Linie", "Linie"}, {"Rechteck", "Gefülltes Rechteck"}, {"Abgerundetes Rechteck", "Gefülltes, abgerundetes Rechteck"}, {"Ellipse" "Gefüllte Ellipse"}, }; private final static int btnFigCount = figImgName.length; Alle Schaltflächen können als AbstractButton können mit einer Klasse erzeugt werden: private AbstractButton btnNew, btnLineColor, btnFillColor; private AbstractButton[] btnImage = new AbstractButton[btnFigCount]; private AbstractButton btnFilled; Die Buttons gruppieren; nur jeweils einer kann gedrückt sein: private ButtonGroup btnGroup; private PaintPanel paintPanel; private JLabel jlbStatus; Auswahl-Feld für die Linien-Breite: private ComboLineWidth comboLineWidth; public DrawMenuBar(PaintPanel paintPanel, JLabel jlbStatus) { super(); this.paintPanel = paintPanel; this.jlbStatus = jlbStatus; jlbStatus.setText(statusText[0][0]); Button „neu“: btnNew = createButton( 0, btnImgName[0], btnToolTipText[0], new NewAction() ); add(btnNew); Die Figuren-Buttons: btnGroup = new ButtonGroup(); for (int i = 0; i < btnFigCount; i++) { btnImage[i] = createButton( 1, figImgName[i], statusText[i][0], new ImageSelectAction(i) ); btnImage[i].setSelected(i == 0); btnGroup.add(btnImage[i]); add(btnImage[i]); } Der Button „Füllen ein/aus“: btnFilled = createButton( 2, btnImgName[1], btnFillToolTipText[0], new FilledAction() ); add(btnFilled); Die Buttons „Farbe wählen': btnLineColor = new PaintButton(0, btnToolTipText[1], new ColorAction()); add(btnLineColor); btnFillColor = new PaintButton(1, btnToolTipText[2], new ColorAction()); add(btnFillColor); copyleft:munz 52 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Für die Wahl der Linien-Breite: Vector list = new Vector(); for (int i = 0; i < lineImgName.length; i++) { ImageIcon iconLineWidth = createIcon("images/" + lineImgName[i] + ".gif"); if (iconLineWidth != null) { iconLineWidth.setDescription("" + (i+1) + " pt"); list.add(iconLineWidth); } } comboLineWidth = new ComboLineWidth(list, paintPanel); add(comboLineWidth); add(Box.createHorizontalGlue()); // rechts noch etwas Platz } Um Dateien einbinden zu können, macht Java keinen Unterschied, ob eine Datei von der Festplatte oder aus dem Internet geladen wird. Einen allgemeingültigen Verweis auf eine Datei nennt man URL (Unified Ressource Locator ). Es ergibt Sinn, einen Dateinamen in eine URL umzuwandeln, dann kann man diese Dateien sogar in eine jar-Datei einbinden: private ImageIcon createIcon(String path) { java.net.URL url = ClassLoader.getSystemClassLoader().getResource(path); if (url != null) return new ImageIcon(url); else return null; } Universalklasse für alle Buttons als innere Klasse: private AbstractButton createButton( int type, String imgName, String toolTipText, Action action) { AbstractButton btn; switch(type) { case 0 : btn = new JButton(action); break; case 1 : btn = new JRadioButton(action); break; case 2 : btn = new JToggleButton(action); break; default : btn = null; return btn; } Die verschiedenen Grafiken für die Buttons laden: ImageIcon icon = createIcon("images/" + imgName + ".gif"); ImageIcon iconPressed = createIcon("images/" + imgName + "_pressed.gif"); ImageIcon iconRollover = createIcon("images/" + imgName + "_rollover.gif"); ImageIcon iconRolloverSelected = createIcon("images/" + imgName + "_rollover_selected.gif"); if (iconPressed != null) { btn.setPressedIcon(iconPressed); btn.setSelectedIcon(iconPressed); } if (iconRollover != null) btn.setRolloverIcon(iconRollover); if (iconRolloverSelected != null) btn.setRolloverSelectedIcon(iconRolloverSelected); copyleft:munz 53 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java if(icon != null) { btn.setIcon(icon); btn.setPreferredSize( new Dimension( icon.getIconWidth()+2, icon.getIconHeight()+2 ) ); } btn.setBorder(null); btn.setFocusPainted(false); btn.setToolTipText(toolTipText); return btn; } Eine universelle innere Klasse für die Buttons: private class PaintButton extends JButton { private int type; PaintButton(int type, String toolTipText, Action action) { setAction(action); this.type = type; setBorder(null); Dimension dim = new Dimension(26, 26); setMinimumSize(dim); setMaximumSize(dim); setPreferredSize(dim); setToolTipText(toolTipText); } Um Grafiken darstellen zu können muss die Methode paintComponent überschrieben werden. Hier werden farbige Rechtecke auf Buttons zur Farbenwahl gezeichnet, damit man die aktuell ausgewählte Farbe erkennen kann: public void paintComponent(Graphics g) { Graphics2D g2D = (Graphics2D) g; super.paintComponent(g2D); Dimension dim = getSize(); g2D.clearRect(0, 0, dim.width - 1, dim.height - 1); Shape rect = new Rectangle2D.Float( dim.width/4.f+1, dim.height/3.f+1.f, dim.width/2.f, dim.height/3.f ); g2D.setStroke(new BasicStroke(2.0f)); switch (type) { case 0 : g2D.setColor(paintPanel.getLineColor()); g2D.draw(rect); break; case 1 : g2D.setColor(paintPanel.getFillColor()); g2D.fill(rect); g2D.setColor(Color.BLACK); g2D.draw(rect); break; } } } copyleft:munz 54 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Hier werden für die unterschiedlichen Buttons die zugehörigen Aktionen bereitgestellt. Aktionen werden von der Klasse AbstractAction abgeleitet und dem Konstruktor des betreffenden Objekts als Parameter übergeben. Aktion für Button „neu“: private class NewAction extends AbstractAction { public void actionPerformed(ActionEvent event) { if (JOptionPane.showConfirmDialog(null, "Wollen Sie wirklich alles löschen?", "Zeichnung löschen", JOptionPane.YES_NO_OPTION) == 0) { paintPanel.clear(); } } } Aktion für die Buttons zur Figuren-Wahl: private class ImageSelectAction extends AbstractAction { private int btnNr; } ImageSelectAction(int btnNr) { this.btnNr = btnNr; } public void actionPerformed(ActionEvent event) { Object obj = event.getSource(); if (obj instanceof JRadioButton) { JRadioButton btn = (JRadioButton) obj; paintPanel.setFigureType(btnNr); btn.setToolTipText( paintPanel.isFilled() ? statusText[btnNr][1] : statusText[btnNr][0] ); jlbStatus.setText( paintPanel.isFilled() ? statusText[btnNr][1] : statusText[btnNr][0] ); } } Aktion für Button „Füllen ein/aus“: private class FilledAction extends AbstractAction { public void actionPerformed(ActionEvent event) { Object obj = event.getSource(); if (obj instanceof JToggleButton) { JToggleButton btn = (JToggleButton) obj; paintPanel.setFilled(btn.isSelected()); btn.setToolTipText( paintPanel.isFilled() ? btnFillToolTipText[1] : btnFillToolTipText[0] ); jlbStatus.setText( paintPanel.isFilled() ? statusText[paintPanel.getFigureType()][1] : statusText[paintPanel.getFigureType()][0] ); } } } copyleft:munz 55 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Aktion für die Buttons „Farbe wählen“: private class ColorAction extends AbstractAction { public void actionPerformed(ActionEvent event) { Object obj = event.getSource(); if (obj == btnLineColor) { Color newColor = JColorChooser.showDialog( null, btnToolTipText[1], paintPanel.getLineColor() ); if (newColor != null) { paintPanel.setLineColor(newColor); btnLineColor.repaint(); } } if (obj == btnFillColor) { Color newColor = JColorChooser.showDialog( null, btnToolTipText[2], paintPanel.getFillColor() ); if (newColor != null) { paintPanel.setFillColor(newColor); btnFillColor.repaint(); } } } } } In der Klasse PaintPanel wird die eigentliche Arbeit getan. Hier werden Mausbewegungen und Tastaturereignisse abgefragt. package drawgui; import figuren.*; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; public class PaintPanel extends JPanel { // Variablen, auf die mit set... und get... zugegriffen wird. private int figType; private Color lineColor = Color.BLACK, fillColor = Color.WHITE; private JLabel jlbStatus; private float lineWidth = 1.0f; private boolean filled = false; // Lokale Variablen private Dimension dim; private RenderingHints renderHints; private Figur actFig; private Point anfPunkt, endPunkt; private Vector paintList; // Diverse Schalter private boolean isShiftKeyPressed = false, isMouseButtonPressed = false; copyleft:munz 56 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Der Konstruktor: public PaintPanel(Dimension dim, JLabel jlbStatus) { super(true); // mit Double-Buffering this.dim = dim; this.jlbStatus = jlbStatus; setOpaque(true); setBackground(Color.WHITE); // Zum Glätten der Kanten Antialiasing einschalten renderHints = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); renderHints.put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ); Die verschiedenen Grafikobjekte werden in einer Liste gespeichert: paintList = new Vector(); addKeyListener(new DrawPanelKeyListener()); addMouseListener(new DrawPanelMouseListener()); addMouseMotionListener(new DrawPanelMouseMotionListener()); } Diverse Methoden, um auf die verschiedenen Objektvariablen zugreifen zu können: public int getFigureType() { return figType; } public void setFigureType(int figType) { this.figType = figType; } public Color getLineColor() { return lineColor; } public void setLineColor(Color lineColor) { this.lineColor = lineColor; } public Color getFillColor() { return fillColor; } public void setFillColor(Color fillColor) { this.fillColor = fillColor; } public float getLineWidth() { return lineWidth; } public void setLineWidth(float lineWidth) { this.lineWidth = lineWidth; } public boolean isFilled() { return filled; } public void setFilled(boolean filled) { this.filled = filled; } public Dimension getPreferredSize() { return dim; } copyleft:munz 57 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Um die Grafiken darzustellen, muss paintComponent überschrieben werden. Beim Zeichnen wird die Liste der Grafik-Objekte abgearbeitet: public void paintComponent(Graphics g) { Graphics2D g2D = (Graphics2D) g; super.paintComponent(g2D); g2D.setRenderingHints(renderHints); g2D.setBackground(Color.WHITE); g2D.clearRect(0, 0, getSize().width-1, getSize().height-1); for ( Enumeration e = paintList.elements(); e.hasMoreElements(); ) { Figur fig = (Figur) e.nextElement(); fig.drawFigure(g2D); } } if (actFig != null) { actFig.drawFigure(g2D); } Alles löschen: public void clear() { paintList.clear(); anfPunkt = null; endPunkt = null; repaint(); } Die innere Klasse DrawPanelKeyListener überprüft, ob eine Taste gedrückt wurde: class DrawPanelKeyListener extends KeyAdapter { public void keyPressed(KeyEvent event) { // Shift-Taste wurde gedrueckt if (!isShiftKeyPressed && (event.getKeyCode() == KeyEvent.VK_SHIFT)) { isShiftKeyPressed = true; // Die Figur soll quadratisch werden if (actFig != null && isMouseButtonPressed) { switch(figType) { case 0: actFig = new SpezialLinie( anfPunkt, endPunkt ); break; case 1: actFig = new Quadrat( anfPunkt, endPunkt ); break; case 2: actFig = new RundesQuadrat( anfPunkt, endPunkt ); break; case 3: actFig = new Kreis( anfPunkt, endPunkt ); break; } actFig.setLineColor(lineColor); actFig.setFillColor(fillColor); actFig.setFilled(filled); actFig.setLineWidth(lineWidth); jlbStatus.setText(actFig.toString()); repaint(); } } copyleft:munz } 58 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java // Prueft, ob eine Taste losgelassen wurde public void keyReleased(KeyEvent event) { // Shift-Taste wurde losgelassen if (isShiftKeyPressed && (event.getKeyCode() == KeyEvent.VK_SHIFT)) { isShiftKeyPressed = false; // Die Figur soll nicht mehr quadratisch sein if (actFig != null && isMouseButtonPressed) { switch (figType) { case 0: actFig = new Linie( anfPunkt, endPunkt ); break; case 1: actFig = new Rechteck( anfPunkt, endPunkt ); break; case 2: actFig = new RundesRechteck( anfPunkt, endPunkt ); break; case 3: actFig = new Ellipse( anfPunkt, endPunkt ); break; } actFig.setLineColor(lineColor); actFig.setFillColor(fillColor); actFig.setFilled(filled); actFig.setLineWidth(lineWidth); jlbStatus.setText(actFig.toString()); repaint(); } } } } Die innere Klasse DrawPanelMouseListener reagiert auf Mausklicks: class DrawPanelMouseListener extends MouseAdapter { mousePressed überschreiben: public void mousePressed(MouseEvent event) { KeyBoard-Events werden nur ausgelöst, wenn DrawPanel den Fokus hat. Bei gedrückter Umschalttaste sollen die Sonderfiguren gezeichnet werden: requestFocus(); if (!isMouseButtonPressed && SwingUtilities.isLeftMouseButton(event)) { isMouseButtonPressed = true; // ganz neue Figur erstellen anfPunkt = new Point(event.getX(), event.getY()); endPunkt = (Point) anfPunkt.clone(); Je nach ausgewähltem Figur-Typ reagieren: switch (figType) { case 0: actFig = (isShiftKeyPressed ? new SpezialLinie(anfPunkt, endPunkt) : new Linie(anfPunkt, endPunkt)); break; copyleft:munz 59 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java case 1: actFig = (isShiftKeyPressed ? new Quadrat(anfPunkt, endPunkt) : new Rechteck(anfPunkt, endPunkt)); break; case 2: actFig = (isShiftKeyPressed ? new RundesQuadrat(anfPunkt, endPunkt) : new RundesRechteck(anfPunkt, endPunkt)); break; case 3: actFig = (isShiftKeyPressed ? new Kreis(anfPunkt, endPunkt) : new Ellipse(anfPunkt, endPunkt)); break; } actFig.setLineColor(lineColor); actFig.setFillColor(fillColor); actFig.setFilled(filled); actFig.setLineWidth(lineWidth); } } mouseReleased überschreiben: public void mouseReleased(MouseEvent event) { if (isMouseButtonPressed && SwingUtilities.isLeftMouseButton(event)) { isMouseButtonPressed = false; if (actFig != null) { paintList.addElement(actFig); actFig = null; } repaint(); } } } Die innere Klasse DrawPanelMouseMotionListener reagiert auf Mausbewegungen: class DrawPanelMouseMotionListener extends MouseMotionAdapter { mouseDragged überschreiben: public void mouseDragged(MouseEvent event) { if (actFig != null) { endPunkt = new Point(event.getX(), event.getY()); actFig.setEndPunkt(endPunkt); jlbStatus.setText(actFig.toString()); repaint(); } } } } copyleft:munz 60 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Klasse ComboLineWidth stellt Methoden bereit, um die unterschiedliche Linienbreite einstellen zu können. Sie ist von JComboBox abgeleitet, die eine Kombination aus Schaltflächen und einer Drop-Down-Liste darstellt: package drawgui; import import import import java.util.*; java.awt.*; java.awt.event.*; javax.swing.*; public class ComboLineWidth extends JComboBox { private PaintPanel paintPanel; private ComboBoxRenderer renderer; Als Parameter hat der Konstruktor eine Liste von Icons und einen Verweis auf PaintPanel, damit auf Variablen aus PaintPanel zugegriffen werden kann: public ComboLineWidth(Vector list, PaintPanel paintPanel) { super(list); this.paintPanel = paintPanel; addActionListener(this); setEditable(false); setToolTipText("Linienbreite"); renderer = new ComboBoxRenderer(); setRenderer(renderer); } public void actionPerformed(ActionEvent event) { JComboBox cb = (JComboBox) event.getSource(); paintPanel.setLineWidth(cb.getSelectedIndex() + 1); } Die innere Klasse ComboBoxRenderer ist von ListCellRenderer abgeleitet, was so eine Art „Stempel“ ist, um die Elemente der Liste einheitlich darzustellen. Die Elemente der Liste sind Icons mit den verschieden dick gezeichneten Linien, sie enthalten aber gleichzeitig auch den Wert der Linienstärke, den man ja zum Weiterverarbeiten benötigt: class ComboBoxRenderer extends JLabel implements ListCellRenderer { public ComboBoxRenderer() { setOpaque(true); setHorizontalAlignment(LEFT); setVerticalAlignment(CENTER); } copyleft:munz 61 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Methode getListCellRendererComponent aus ListCellRenderer muss überschrieben werden: public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } ImageIcon icon = (ImageIcon)value; setText(icon.getDescription()); setIcon(icon); return this; } } } Die Klasse JDrawWindowCommand wird von WindowAdapter abgeleitet. Sie ermöglicht das Beenden des Programms mit einem modalen Dialogfenster: package drawgui; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JDrawWindowCommand extends WindowAdapter { // Das Fenster wird mit dem Windows-Befehl geschlossen public void windowClosing(WindowEvent event) { JFrame source = (JFrame) event.getSource(); if (JOptionPane.showConfirmDialog(source, "Wollen Sie das Programm wirklich beenden?", "Programm beenden", JOptionPane.YES_NO_OPTION) == 0) { System.exit(0); } } } Die „Haupt“-Klasse JDraw: import drawgui.*; import java.awt.*; import javax.swing.*; public class JDraw extends JFrame { private PaintPanel zeichenFlaeche; private JLabel jlbStatus; public JDraw(String title) { super(title); Zum ordentlichen Schliessen des Fensters eine eigene Klasse verwenden: JDrawWindowCommand windowCommand = new JDrawWindowCommand(); copyleft:munz 62 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Die Standardeinstellungen zum Schließen überschreiben: this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); this.addWindowListener(windowCommand); Das Layout festlegen: Container cp = getContentPane(); cp.setLayout(new BorderLayout()); Dimension dim = new Dimension(400, 400); Die benötigten Elemente einbinden: jlbStatus = new JLabel(); zeichenFlaeche = new PaintPanel(dim, jlbStatus); setJMenuBar(new DrawMenuBar(zeichenFlaeche, jlbStatus)); cp.add(zeichenFlaeche); cp.add(jlbStatus, BorderLayout.SOUTH); Alles kompakt darstellen, das Fenster auf dem Bildschirm zentrieren: pack(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((screenSize.width - getSize().width)/2, (screenSize.height - getSize().height)/2); setVisible(true); } main tut nicht viel: public static void main(String[] args) { new JDraw("Grafik"); } } copyleft:munz 63 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java 2.16 Aufgaben Aufgabe 12: Lösen einer quadratischen Gleichung. Die quadratische Gleichung a ⋅ x 2 + b ⋅ x + c = 0 soll nach Eingabe der Koeffizienten a, b und c gelöst werden. Dabei sollen alle möglichen Sonderfälle berücksichtigt werden. Es ist guter Programmierstil, Fachklassen und GUI-Klassen zu trennen. Erstellen Sie deshalb eine Fachklasse QuadratGleichung, die in Abhängigkeit der Koeffizienten a, b und c die Lösung der Gleichung berechnet. Ein erläuternder Ausgabetext und die Lösungsmenge sollen von QuadratGleichung mit den öffentlichen Methoden getLoesungText und getLoesungMenge zur Verfügung gestellt werden. In der GUI-Klasse QuadratGleichungGUI können die Koeffizienten a, b und c mit Hilfe von Textfeldern eingegeben werden. Das Grafikfenster von QuadratGleichungGUI könnte folgendermaßen aussehen: Aufgabe 13: Erweiterter Währungs-Rechner. Erstellen Sie einen erweiterten Währungs-Rechner, der automatisch Währungen ausgewählter Länder konvertiert. Er könnte z.B. so aussehen: copyleft:munz 64 Fachschulen Lörrach 2 Grafik-Programmierung (GUI) Java Aufgabe 14: Das Programm „Figuren“ aus dem Kapitel OOP soll noch weitere Figuren, z.B. einen Stern, darstellen können. Hinweis: Ein Stern ist ein besonderes Vieleck. Außerdem wird noch eine weitere Farbe benötigt. Möglicherweise erhalten die Figuren auch einen breiten Rand. Kantenglättung verschönert die Darstellung. copyleft:munz 65 Fachschulen Lörrach 3 Threads 3 Threads Java Programme sind häufig sequenziell angelegt, d.h. Programmschritt für Programmschritt wird nacheinander abgearbeitet. Man kann sie sich also linear angeordnet vorstellen, so dass man alle Programmschritte durchzählen kann. Will man, dass zwei oder mehr Aufgaben gleichzeitig, oder wie wir auch gerne sagen, nebenläufig, abgearbeitet werden, benutzt man Prozesse oder Threads. Während zwei Prozesse vollkommen getrennt voneinander in verschieden Speicherbereichen (Adressraum) ablaufen – ein Beispiel dafür wäre etwa das Drucken aus einer Textverarbeitung heraus und das gleichzeitige Surfen im Internet mittels eines Browsers – laufen Threads im gleichen Adressraum, benutzen aber verschiedene Stapel (Stacks) um ihre Aufgaben zu erledigen. So können Threads ihre ihnen zugewiesenen Methoden aufrufen und ihre eigenen Variablen verwalten. Ohne dass wir es explizit programmieren müssen, besitzt jede Java-Anwendung mindestens einen Thread, der im Falle einer Anwendung die main-Methode und im Falle eines Applets die Methoden init(), start(), stop, und destroy() ausführt. Die Thraeds laufen dabei in der auf dem Rechner installierten VM bzw. der VM, die im Browser integriert ist. 3.1 Programme sind Threads Einen laufenden Thread kann man mit der Thread-Klassenmethode currentThread() abfragen. Genauer, currentThread() liefert das Thread-Objekt, das die Methode ausführt, aus der heraus die Methode currentThread() aufgerufen wurde. Wir wollen dies an einem einfachen Programm testen. public class MainThread{ public static void main(String[] args){ Thread laufenderThread = Thread.currentThread(); System.out.println("Name des Threads: " + laufenderThread.getName()); } } Ausgabe: Wir erkennen, dass der Thread, der die main()-Methode ausführt, selbst main heisst. Auch die Ausführung der paint()- oder paintComponent()-Methode in einem Frame- bzw. JFrame-Objekt wird von einem Thread kontrolliert, ohne dass wir es programmieren müssen. Dies gilt auch dann, wenn die paintComponent()-Methode, wie in der Klasse GrafikPanel geschehen, überschrieben ist. Um dies zu zeigen, fügen wir in der Methode paintComponent(Graphics g) von GrafikPanel die folgende Zeile ein: System.out.println(Thread.currentThread().getName()); Sie bewirkt, dass jedes Mal, wenn ein Auswahlschalter oder das Zeichne-Button gedrückt wird, also die Methode paintComponent() aufgerufen wird, im Anzeigefenster des JavaEditors der Name des Threads ausgegeben wird, der die Methode paintComponent() ausführt. copyleft:munz 66 Fachschulen Lörrach 3 Threads Java Wie die nachstehende Ausgabe belegt, ist dies das Thread-Objekt AWT-EventQueue-0. 3.2 Eine eigene Thread-Klasse Um Ausführungen eines Programms nebenläufig, also parallel zueinander ausführen zu können, bringt man sie am einfachsten in Klassen unter, die selbst einen Thread darstellen. In Java ist dies leicht zu realisieren. Man lässt dazu die Klasse von der fertigen Klasse Thread erben und überschreibt die von Thread geerbte Methode run(). In dieser Methode bringt man die Anweisungen unter, die nebenläufig abgearbeitet werden sollen. Wir betrachten dies am Beispiel der Klasse EigenerThread: public class EigenerThread extends Thread{ public void run(){ for (int i = 1; i<=10; i++){ try{ sleep((int)(Math.random()*10000)); System.out.println(getName()+": "+i); } catch(InterruptedException exp){ return; } } } } copyleft:munz 67 Fachschulen Lörrach 3 Threads Java In der run()-Methode implementieren wir eine Schleife, in der bei jedem Durchgang die Klassenmethode sleep(int millis)3 aufgerufen wird. Diese Methode hat EigenerThread von Thread geerbt. Wird sie ausgeführt, so wird der Thread, der die run()-Methode kontrolliert, für eine gewisse Zeit angehalten. Die „Schlafdauer“ wird der Methode in Millisekunden übergeben. Beim Aufruf in unserer Schleife übergeben wir der Methode sleep(…) eine Zufallszahl. Beim Ausführen der Methode kann es zu einer Ausnahme kommen, weshalb eine try-catch Konstruktion implementiert werden muss. Im try-Block implementieren wir die eigentliche Funktionalität von run(). Im Block von catch(…) bringen wir all das unter, was getan werden soll, falls sleep(…) eine Ausnahme auslöst. Eine solche Ausnahme liefert ein Objekt des Typs InterruptedException, weshalb catch ein Objekt vom Typ InterruptedException übergeben werden muss. Da es uns nur um die Demonstration geht, soll im Ausnahmefall nichts passieren, return liefert also nichts. Ebenfalls von Thread geerbt ist die Objekt-Methode getName(). Sie liefert den Namen des Threads, der beim Erzeugen standardmäßig den Namen Thread-<Nr.> erhält. Um die eigene Thread-Klasse zu testen, benutzen wir ein Demoprogramm. Zur Demonstration der Nebenläufigkeit, erzeugen wir von EigenerThread zwei Instanzen und starten sie. Zum Starten benutzen wir die Methode start(), die EigenerThread von Thread erbt. Diese Thread-Objekt-Methode start() organisiert einiges; für uns wichtig ist, dass sie die run()-Methode des Threads startet. public class EigenerThreadDemo{ public static void main(String[] args){ EigenerThread s = new EigenerThread(); EigenerThread t = new EigenerThread(); s.start(); t.start(); } } Eine mögliche Ausgabe ist: Thread-2: 1 Thread-2: 2 Thread-2: 3 Thread-1: 1 Thread-2: 4 Thread-1: 2 Thread-2: 5 Thread-1: 3 Thread-2: 6 Thread-2: 7 Thread-1: 4 Thread-2: 8 Thread-1: 5 Thread-1: 6 Thread-2: 9 Thread-2: 10 Thread-1: 7 Thread-1: 8 Thread-1: 9 Thread-1: 10 Sehr schön kann man die Nebenläufigkeit der beiden Algorithmen erkennen, da man nicht vorhersehen kann, ob zuerst Thread-1 oder Thread-2 eine Ausgabe erzeugen. 3 sleep() ist eine Klassenmethode, müsste also üblicherweise durch Thread.sleep(…) aufgerufen werden. Da unsere Klasse von Thread erbt, ist sleep(…) auch eine Klassenmethode von EigenerThread und der Aufruf kann mit this.sleep(…) oder kurz mit sleep(…) erfolgen. copyleft:munz 68 Fachschulen Lörrach 3 Threads Java Aufgabe 15: Die Klasse Thread besitzt neben dem Standardkonstruktor Thread() noch sieben weitere Konstruktoren (vgl. dazu die java-Dokumentation von SUN). Einen weiteren wollen wir kennenlernen: Thread(String name). Mit Hilfe dieses Konstruktors kann man einem Thread-Objekt statt des Standardnamens Thread-<Nr> einen eigenen Namen geben. In der Klasse EigenerThread kann man mit Hilfe von super(<Parameterliste des gewünschten Konstruktors>) auf alle Konstruktoren der Oberklasse Thread zugreifen und somit auch den Konstruktor von EigenerThread gestalten. Modifizieren Sie den Konstruktor von EigenerThread und passen Sie seinen Aufruf in EigenerThreadDemo so an, dass der Thread einen von Ihnen gewählten Namen trägt. 3.3 Threads – Runnable Wir haben gelernt, dass wir eine Klasse X, in der wir einen nebenläufigen ‚Prozess’ laufen lassen wollen, am besten von Thread erben lassen. Ein Problem haben wir, wenn unsere Klasse X bereits von einer Klasse, nennen wir sie A, erbt. Wir hätten einen Fall von Mehrfachvererbung, was in Java nicht zulässig ist. Die Abbildung zeigt die Problematik. Eine Lösung bahnt sich an, wenn wir wissen, dass die Klasse Thread das Interface4 Runnable implementiert (siehe Bild unten). 4 Ein Interface ist eine abstrakte Klasse, die keine Attribute und nur abstrakte Methoden enthält.Wenn eine Klasse von einem Interface erben soll, verwenden wir statt des reservierten Wortes extends das reservierte Wort implements. Dieses Wort ‚implements’ deutet an, was eine Klasse leisten muss, will sie von einem Interface erben: sie muss(!) alle ererbten Methoden implementieren. Mittels Interfaces kann man in Java eine Pseudo-Mehrfach-Vererbung realisieren, denn es lassen sich neben einer regulären Vererbung beliebig viele Interfaces implementieren. copyleft:munz 69 Fachschulen Lörrach 3 Threads Java Das Interface Runnable ist sehr einfach gebaut. Seine Aufgabe besteht allein darin, zu garantieren, dass in den Klassen wie X die Methode run() tatsächlich implementiert wird. Aus diesem Grund implementiert auch die Klasse Thread selbst das Interface Runnable. Es hat die Gestalt: public interface Runnable{ public abstract void run(); } Der Ausweg besteht also darin, dass unsere Klasse X das Interface Runnable und damit die Methode run() implementiert. public class X implements Runnable{ public void run(){ for (int i = 1; i<=10; i++){ try{ Thread.sleep((int)(Math.random()*10000)); System.out.println(Thread.currentThread().getName()+": "+i); } catch(InterruptedException exp){ return; } } } } Da nun X nicht mehr von Thread erbt, müssen wir die Klassen-Methode sleep() jetzt mit Thread.sleep() aufrufen. Wenn wir den Namen des Threads ausgeben wollen, der die Instanz kontrolliert, müssen wir mit der Klassen-Methode currentThread(), diesen erst ermitteln und dann seinen Namen mit getName() erfragen. Das allerdings ist noch nicht alles. Denn Runnable selbst ist kein Thread, so dass die Klasse X die Rolle nicht ganz übernehmen kann, die EigenerThread spielte. Wir zeigen zunächst Demo-Programm XDemo, das die Rolle von EigenerThreadDemo übernimmt und erläutern dann im Einzelnen, was passiert. public class XDemo{ public static void main(String[] args){ Runnable rs = new X(); Runnable rt = new X(); Thread s = new Thread(rs,"Thread: s "); Thread t = new Thread(rt,"Thread: t "); s.start(); t.start(); } } copyleft:munz 70 Fachschulen Lörrach 3 Threads Java In der main(…)-Methode werden zunächst zwei Instanzen der Klasse X erzeugt. Statt X rs = new X(); schreiben wir Runnable rs = new X(); das geht, weil X eine Unterklasse von Runnable ist. Anschließend werden zwei Threads-Objekte erzeugt, wobei wir den Konstruktor von der Klasse Thread (siehe Java-Dokumentation) benutzen, der es erlaubt, dem erzeugten Thread-Objekt ein Runnable-Objekt zu übergeben. Wir übergeben die Objekte rs und rt. Damit ist sichergestellt, dass die Objekte übergeben sind, die die run()-Methode implementiert haben. Als zweiten Parameter übergeben wir dem Konstruktor den Namen, den unser erzeugter Thread tragen soll. 3.4 Runnable als anonyme Klasse Im Beispiel XDemo2 zeigen wir, wie man auf die Klasse X verzichten kann und ihre Funktionalität als anonyme Klasse in XDemo2 integriert. Das Vorgehen ist immer dann interessant, wenn der Aufwand in der run()-Methode relativ klein ist und die Übersichtlichkeit nicht darunter leidet. public class XDemo2{ public static void main(String[] args){ Runnable rs = new Runnable(){ public void run(){ schlafen(500); } }; Runnable rt = new Runnable(){ public void run(){ schlafen(1000); } }; Thread s = new Thread(rs,"Thread: s "); Thread t = new Thread(rt,"Thread: t "); s.start(); t.start(); } private static void schlafen(int millis){ for (int i = 1; i<=10; i++){ try{ Thread.sleep((int)(Math.random()*millis)); System.out.println(Thread.currentThread().getName() +": "+i); } catch(InterruptedException exp){ return; } } } } Von Interesse sind die grau unterlegten Teile des Quelltextes: Da Runnable ein Interface und damit eine abstrakte Klasse ist, lässt sich von ihr keine Instanz direkt erzeugen. Eine Zeile wie Runnable rs = new Runnable(); copyleft:munz 71 Fachschulen Lörrach 3 Threads Java wird vom Compiler nicht übersetzt. Er meldet einen Fehler5. Der grau unterlegte Text zeigt, wie man sich helfen kann: Man erzeugt zwar ein Runnable-Objekt implementiert aber gleichzeitig die run()Methode Runnable rs = new Runnable() {"hier run() implementieren"}; Da dies nun zweimal passieren müsste, haben wir die Funktionalität der run()-Methode in einer eigene schlafen()-Methode6 implementiert, die von run() aufgerufen wurde. Sie enthält den gleichen Quelltext, wie die run()-Methode aus dem letzten Kapitel. Aufgabe 16: Erstelle eine Applikation mit einer animierten Grafik, z.B. könnte eine laufende Billardkugel an den Fensterbegrenzungen reflektiert werden. 5 Würde man Instanzen von Runnable zulassen, so könnte man nicht zusichern, dass die run()Methode implementiert ist. 6 Da schlafen() in der Klasse implementiert ist, in der auch die main()-Methode implementiert ist, muss schlafen() als static deklariert werden. copyleft:munz 72 Fachschulen Lörrach 4 Rekursionen 4 Rekursionen 4.1 Java Was sind Rekursionen? Häufig wird n! als ein Produkt erklärt, das 1 als ersten, 2 als zweiten und n als n.ten Faktor hat. Dazwischen setzt man die Reihe fort und schreibt kurz: n! = 1 2 3 … n und nimmt an, dass jeder weiß, was man zwischen den Faktoren 3 und n zu tun hat. Will man n! etwas seriöser erklären schreibt man: n! = n. (n-1)! mit 1! = 0 (und 0! =1) Eine solche Definition nennt man rekursiv und auf den ersten Blick meint man, so könne man nicht definieren, denn man erklärt etwas dadurch, dass man es selbst verwendet. Dies erscheint aber nur auf den ersten Blick. Denn n! benutzt wieder eine Fakultät aber, und das ist wesentlich, mit einem vereinfachten Parameter. Und schließlich findet man den Ausgang, und gewinnt dadurch den Boden unter den Füßen mit 1!, dessen Wert man als 1 gesetzt hat. Wichtig bei einem rekursiven Abstieg sind also die Vereinfachung (im Parameter) und die Abbruchbedingung. A B C D E F 6! 6!=6.5! 5!=5.4! . 4!=4 3! 3!=3.2! 2!=2.1! = = = = = = 1!=1 . 2!=2.1 3!=3.2 4!=4 6 5!=5.24 720 6!=6.120 Person A bekommt die Aufgabe gestellt 6! zu berechnen. A kann aber die Aufgabe lösen, wenn sie weiß was 5! ist. Also stellt A der Person B die Aufgabe 5! zu berechnen und wartet mit der Beantwortung der Frage, bis B ihm die gewünschte Zahl liefert. Nun ist B fast in der gleichen misslichen Lage wie A. Im Prinzip hat B das gleiche Problem zu lösen, allerdings mit einer etwas einfacheren Zahl. Das Spiel geht so weiter, die Rekursionstiefe nimmt zu, bis schließlich F die Frage gestellt bekommt, was 1! sei. Das weiß aber F, der Abstieg ist zu Ende und der Wiederaufstieg kann beginnen. E kann jetzt 2! berechnen und gibt sein Ergebnis an D weiter, bis schließlich A sein Frage, was denn 5! ist, beantwortet bekommt und seinem Auftraggeber sein Ergebnis 6! = 720 zurückliefern kann. In einer Programmiersprache implementiert, entsprechen den Personen A bis F Aufrufe einer Funktion. 4.2 Rekursive Methoden in Java Das folgende Demoprogramm demonstriert den rekursiven Ab- und Aufstieg: public class RekursionDemo{ private static void rekursion(int a){ a--; System.out.println(a); if (a>0)rekursion(a); System.out.println(a); } public static void main(String[] args){ rekursion(5); } } Um in unserem Bild von oben zu bleiben, scheint die Methode rekursion(…) der Person A zu entsprechen. Dann bleibt die Frage, wo sind die Personen B, C etc.? Tatsächlich entspricht nicht die Methode einer Person, sondern der Aufruf der Methode. Im Speicher des Rechners laufen tatsächlich 5 Methoden. Die erste, sie wird mit dem Parameter 5 aufgerufen, lebt am längsten. Dann folgen nacheinander die Aufrufe von rekursion(…) mit den Parametern 4, 3, 2, und 1. Beim Ab- und beim Aufsteigen wird jeweils eine Ausgabe des um 1 verminderten, aktuellen Parameters gemacht. Also müssten nacheinander 4, 3, 2, 1, 0 beim Abstieg und danach 0, 1, 2, 3, 4 beim Aufstieg ausgegeben werden, was auch die Ausgabe des Programms bestätigt. copyleft:munz 73 Fachschulen Lörrach 4 Rekursionen Java Setzt man in seinem Java-Editor Breakpoints z.B. in den Zeilen 5 und 6 und arbeitet man mit dem Debugger, so wird die Rekursion noch deutlicher. Der Debugger hält 5-mal in der Zeile 5 und erst danach 5-mal in der Zeile 7. Aufgabe 17: Programmieren Sie eine rekursive, statische Methode fakultaet(…) in FakultaetDemo.java. Der Bediener des Programms kann bestimmen, von welcher Zahl die Fakultät bestimmt werden soll. Die Lösung könnte folgendes Aussehen haben. Aufgabe 18: Es gilt fib(n) = fib(n-1)+fib(n-2) und fib(2) = fib(1) = 1. Nehmen Sie die notwendigen Namensänderungen in FakultaetDemo.java vor und ersetzen Sie die rekursive Methode fakultaet(…) durch die rekursive Methode fib(…). f(2) 1 f(3) copyleft:munz Die obige Berechnung braucht selbst bei einem Pentium 4 (1,8 GHz) Prozessor nahezu 30 Sekunden. Das liegt daran, dass viele Berechnungen vielfach vorgenommen werden, wie der Berechnungsbaum für fib(6) zeigt: Fügen Sie in der nachfolgenden Tabelle „Aufrufpfeile“ ein f(6) f(5) f(4) f(4) f(3) f(3) f(2) f(2) f(2) f(1) f(2) f(1) 1 f(1) 1 1 1 1 1 1 74 Fachschulen Lörrach 4 Rekursionen Java Fibonacci-Zahlen lassen sich wesentlich schneller auch nicht rekursiv berechnen. Es gilt nämlich: Fib( n ) = 1 5 1+ 5 2 n 1− 5 − 2 n . Dass es sich hierbei tatsächlich um Fibonacci-Zahlen handelt ist schon schwerer zu erkennen. Die rekursive Formulierung ist da deutlicher. Aufgabe 19: Der ggT zweier ganzer Zahlen lässt sich ebenfalls rekursiv berechnen. Dieses Verfahren ist leicht zu programmieren und effizient. Der Algorithmus heißt: ggT(a,a) = ggT(a,0) = a und ggT(a,b) = ggT(b,a mod b) Bemerkung: ggT(a,0) kann beim rekursiven Aufruf entstehen. Z.B. ggT(12,4), danach wird nämlich ggT(4,12 mod 4) = ggT(4,0) aufgerufen. copyleft:munz 75 Fachschulen Lörrach 4 Rekursionen 4.3 Java Rekursive Grafiken Grafiken lassen sich leicht mittels einer Turtle erstellen. Wir unterbrechen also unseren Kurs für einen kurzen Moment, um eine Turtle vorzustellen. 4.3.1 Eine einfache Turtle Für den hier beschriebenen Kurs werden nur Turtle.class, TurtleGUI.class und das Interface TGrafik.class benutzt. Die Quelltexte zu den drei Programmen finden sich im Anhang. Die Klasse TurtleGUI implementiert das Interface TGrafik, das lediglich eine Methode zeichne(Turtle t) anlegt. Da TurtleGUI diese Methode erbt, sie aber nicht implementiert, ist die Klasse TurtleGUI abstrakt und eine Klasse wie z.B. Sierpinski muss die Methode zeichne(Turtle t) implementieren. Auf diese Weise wird garantiert, dass eine Klasse, die das Zeichnen organisiert, eine syntaktisch korrekte Methode zeichne(Turtle t) implementiert hat. Zusätzlich sorgt das Verfahren, dass die „zeichnende Klasse“ von unnötigem Ballast befreit ist. Die Übersichtlichkeit wird schließlich weiter erhöht, in dem man die Klassen TurtleGUI, TGrafik und die Turtle selbst zu einem Paket turtle schnürt, das unser Programm, z.B. Sierpinski nur noch zu importieren braucht. Eine Klasse TurtleGrafikTest zeigt den prinzipiellen Aufbau eines Programms, das die Turtle zum Zeichnen einsetzt. Im Konstruktor wird die Turtle positioniert, dass dies auch in der Methode zeichne(Turtle t) auch geschehen kann, ja auch manchmal Sinn macht, werden an anderen Beispielen noch zeigen. In der main–Methode wird beim Aufruf des Konstruktors diesem die Größe des Zeichenfeldes übergeben. Dort kann man auch Veränderungen vornehmen, die die Größe des Zeichenfeldes beeinflussen. copyleft:munz 76 Fachschulen Lörrach 4 Rekursionen Java import turtle.*; public class TurtleGrafikTest extends TurtleGUI { public TurtleGrafikTest(String titel, int b, int h) { super(titel, b, h); t.setTurtle(100,200); } public void zeichne(Turtle t) { t.vor(100); t.rechts(100); } } public static void main(String[] args){ new TurtleGrafikTest("Turtle-Test", 400, 300); } Um einem Benutzer des Paketes turtle sein volle Funktionalität mitzuteilen, benutzt man am Besten eine Java-Dokumentation des Paktes7. Die unten stehende Abbildung zeigt einen Ausschnitt aus der Dokumentation, nämlich die Auflistung der Methoden der Turtle. 7 Wer den Quelltext des Paktes besitzt kann mit Hilfe des Programms javadoc.exe, das Bestandteil des Java-Pakets von Sun ist und in den JavaEditor integriert werden kann, diese Dokumentation selbst erstellen. copyleft:munz 77 Fachschulen Lörrach 4 Rekursionen 4.3.2 Java Übung mit der Turtle Die oben im Quelltext dargestellte Klasse TurtleGrafikTest erzeugt nach wiederholtem Anklicken des Buttons „Zeichne!“ die nachstehende Grafik. Aufgabe 20: a) Die Turtle soll nach viermaligen Klicken ein Quadrat gezeichnet haben. b) Die Turtle soll das gleiche Quadrat nach einmal Klicken gezeichnet haben. c) Die Turtle zeichnet ein Quadrat, das auf einer Ecke „steht“ Wenn dieses Quadrat auf einen Mausklick gezeichnet wird, was passiert dann, wenn man das Button „Zeichne!“ mehrmals anklickt? Aufgabe 21: Die Turtle beginnt mit einer Schrittlänge 100, nach jedem Drehen um 900 nach rechts wird die Schrittlänge um 10% verkürzt, bevor die Turtle weiter vorwärts schreitet. 4.4 Binärbaum Um die Grundstruktur des Binärbaumes, ein auf dem Kopf stehendes 'T', zeichnen zu lassen, verändern wir lediglich die Methode zeichne(Trutle t)neu: import turtle.*; public class BinaerBaumGS extends TurtleGUI { public BinaerBaumGS(String titel, int b, int h) { super(titel, b, h); t.setTurtle(200,100); } public void zeichne(Turtle t){ t.vor(-100); t.rechts(90); t.vor(100); t.vor(-200); } public static void main(String[] args){ new BinaerBaumGS("Binär-Baum-GrundStruktur", 400,200); } } copyleft:munz 78 Fachschulen Lörrach 4 Rekursionen Java Wir erhalten die folgende Darstellung: Wir wollen an den linken und rechten Enden des wie auf dem Kopf stehend aussehende „T“ die gleiche Figur, allerdings verkleinert anfügen. Wir hätten dann vier solche Enden. Wollen wir so fortfahren, beschreiben wir diesen Vorgang am besten rekursiv. Die Turtle zeigt zu Beginn in Richtung Norden. Wir lassen sie sich bis zum rechten Ende ziehen. Bevor wir jetzt den ganzen Vorgang rekursiv aufrufen, muss die Turtle erst wieder nach Norden zeigen. Nach dem rekursiven Aufruf wandert die Turtle zum linken Ende, wendet sich nach Norden und startet den Vorgang erneut rekursiv. Danach muss sie an den Ausgangspunkt zurückkehren und zum Schluss wieder nach Norden zeigen. Da der rekursive Aufruf mit einer halbierten Schrittlänge geschieht, und zeichne(Turtle t) lediglich die Übergabe der Turtle und nicht einer verkleinerten Länge erlaubt, muss die rekursive Methode außerhalb von zeichen(Turtle t) z.B. in zeichneBinaerBaum(Turtle t, int l) untergebracht werden. In unserem Beispiel findet der rekursive Abstieg sein Ende, wenn l kleiner 1 wird. Stellen im Quelltext, die sich vom Quelltext von BinaerBaumGS.java unterscheiden sind grau unterlegt. Quelltext: import turtle.*; public class BinaerBaum extends TurtleGUI { int l; } public BinaerBaum(String titel, int b, int h) { super(titel, b, h); l = 100; } public void zeichne(Turtle t){ t.setTurtle(200, 100); zeichneBinaerBaum(t,l); } public void zeichneBinaerBaum(Turtle t, int l){ if (l>0){ t.vor(-l); t.rechts(90); t.vor(l); t.rechts(-90); zeichneBinaerBaum(t,l/2); t.rechts(90); t.vor(-2*l); t.rechts(-90); zeichneBinaerBaum(t,l/2); t.rechts(90); t.vor(l); t.rechts(-90); t.vor(l); } } public static void main(String[] args){ new BinaerBaum("Binär-Baum", 400,400); } copyleft:munz 79 Fachschulen Lörrach 4 4.5 Rekursionen Java Kochkurve Wir dritteln eine Strecke und setzen ein Dreieck nach der folgenden Vorschrift darauf: Gehe um die neue (gedrittelte) Strecke nach vorne (F = forward), drehe um α = 60° gegen den Urzeigersinn (+), gehe wieder um die neue Strecke vorwärts, drehe zweimal um α mit dem Uhrzeigersinn (– –), gehe vorwärts, drehe um α gegen den Uhrzeigersinn und gehe ein letztes Mal vorwärts. Wir können mit der „F+/–“-Notation also kurz schreiben: L := F + F - - F + F Das L-System in Java: t.vor(l); //F t.rechts(-60); //+ t.vor(l); //F t.rechts(120); //-t.vor(l); //F t.rechts(-60); //+ t.vor(l); //F Wir erhalten dann die nachstehende Figur: Auf jeder Strecke machen wir nach einer weiteren Drittelung der Strecke das gleiche wieder. Das heißt jedes F ersetzen wir durch L mit einer Drittelung der Strecke: L + L -- L + L mit L = F + F - - F + F wir erhalten dann: Setzen wir die Rekursion fort, so hat die Kochkurve die folgende Gestalt: Der Quelltext: import turtle.*; public class Kochkurve2 extends TurtleGUI { int l; public Kochkurve2(String titel, int b, int h){ super(titel, b, h); l = 729; } copyleft:munz 80 Fachschulen Lörrach 4 Rekursionen } Java public void zeichne(Turtle t){ t.setTurtle(10,300); t.setOrientierungsWinkel(0); t.rechts(90); zeichneKoch(t,l); } public void zeichneKoch(Turtle t, int l){ if (l<9){ t.vor(l); } else{ zeichneKoch(t,l/3); //L t.rechts(-60); //+ zeichneKoch(t,l/3); //L t.rechts(120); //-zeichneKoch(t,l/3); //L t.rechts(-60); //+ zeichneKoch(t,l/3); //L } } public static void main(String[] args){ new Kochkurve2("Koch-Kurve-2",700,400); } Das Setzten der Turtle geschieht jetzt in der Methode zeichne(Turtle t) und nicht mehr im Konstruktor. Der Grund dafür ist: Klickt man erneut auf das 'Zeichne!'-Button, soll die gleiche Kochkurve gezeichnet werden. Zu diesem Zeitpunkt kann man aber nicht mehr zusichern, dass die Turtle richtig positioniert ist. Auch die Orientierung kann sich geändert haben. Es reicht also nicht nur, die Turtle auf ihren Ausgangspunkt zu positionieren wir müssen mit dem Aufruf t.setOrientierungsWinkel(0) auch die Ausrichtung auf ihren Standardwert zurücksetzen. copyleft:munz 81 Fachschulen Lörrach 4 Rekursionen Java Aufgabe 22: Programmieren Sie das L-System: L := F + F - F - F + F, mit α = 90° und der Bedingung, dass beim rekursiven Aufruf die Streckenlänge wieder gedrittelt wird. Es sollte sich die nachstehende Figur ergeben. 4.6 Sierpinski-Dreieck Ausgangsgrafik für das Sierpinski-Dreieck ist folgende Figur: Zum Sierpinski-Dreieck selbst kommen wir, wenn wir an die Enden des „Dreisterns“ rekursiv fortgesetzt wieder „Dreisterne“ mit jeweils halbierter Streckenlänge ansetzen. Wir erhalten dann die unten stehende Figur: copyleft:munz 82 Fachschulen Lörrach 4 Rekursionen Java Quelltext: public class Sierpinski extends TurtleGUI { int l; public Sierpinski(String titel, int b, int h){ super(titel, b, h); l = 128; } public void zeichne(Turtle t){ t.setTurtle(250,350); int xa = t.getTurtleX(); int ya = t.getTurtleY(); zeichneSierpinski(t,l,xa,ya); } public void zeichneSierpinski(Turtle t, int l, int xa, int ya){ if (l>1){ t.setTurtle(xa,ya) ; t.rechts(120); t.vor(l); t.setOrientierungsWinkel(0); zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY()) t.setTurtle(xa,ya); t.vor(l); zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY()); t.setTurtle(xa,ya); t.rechts(-120); t.vor(l); t.setOrientierungsWinkel(0); zeichneSierpinski(t,l/2,t.getTurtleX(),t.getTurtleY()); } } public static void main(String[] args){ new Sierpinski("Sierpinski-Dreieck",500,500); } } Wegen der Rundungsfehler bei der Berechnung der Positionen der Turtle kommen wir jetzt nicht mehr alleine mit den Methoden vor(…) und rechts(…) aus. Wir benutzen deshalb noch getTurtleX(), getTurtleY(), setTurtle(…) und setOrientierungsWinkel(…). 4.7 Pythagoras-Baum In der ersten Stufe hat der Pythagoras-Baum die folgende Gestalt: Man gibt ein Quadrat mit einer bestimmten Seitenlänge vor. Über der einen Seite zeichnet man ein 0 0 rechtwinkliges Dreieck (in unserem Beispiel hat dieses die Winkel 30 und 60 ). Über die noch freien Katheten zeichnet man die Kathetenquadrate. Es entsteht die bekannte Darstellung zum Pythagoräischen Lehrsatz. copyleft:munz 83 Fachschulen Lörrach 4 Rekursionen Java Den Pythagoras-Baum erhält man nun, wenn man die Kathetenquadrate selbst wieder als Hypothenusenquadrate für die nächste Generation auffasst. Wir erhalten dann das nächste Bild. Erhöht man die Rekursionstiefe stark genug, bekommt man eine Darstellung des Pythagoras-Baumes. Quelltext: import turtle.*; public class PythagorasBaum extends TurtleGUI { int l; public PythagorasBaum(String titel, int b, int h){ super(titel, b, h); l = 80; } public void zeichne(Turtle t){ t.setTurtle(400,400); zeichnePB(t,l); } public void zeichneQuadrat(Turtle t,int l){ for (int i = 0; i<4; i++){ t.vor(l); t.rechts(90); } } copyleft:munz 84 Fachschulen Lörrach 4 Rekursionen } Java public void zeichnePB(Turtle t, int l){ int xa = t.getTurtleX(); int ya = t.getTurtleY(); zeichneQuadrat(t,l); if (l>0){ t.vor(l); t.rechts(-30); zeichnePB(t,(int)(0.5*l*Math.sqrt(3))); t.rechts(30); t.setTurtle(xa,ya); t.vor(l); t.rechts(60); t.vor( (int)(0.5*l*Math.sqrt(3)) ); zeichnePB(t,(int)(0.5*l)); t.rechts(-60); t.setTurtle(xa,ya); } } public static void main(String[] args){ new PythagorasBaum("Pythagoras-Baum",700,500); } Zur Erläuterung des Algorithmus: Die Turtle ‚sitzt’ im Ausgangspunkt. Zuerst wird ein Quadrat gezeichnet. Danach bewegt sich die Turtle auf den Punkt P_li, dreht ihre Blickrichtung um 30° nach links (der dicke Pfeil gibt die Blickrichtung der Turtle an). Dann erfolgt der rekursive Aufruf der Methode zeichnePB(). Die Turtle bewegt sich mit neuer Blickrichtung in den Punkt P_re. Wieder der rekursive Aufruf der Methode zeichnePB(). Schließlich wird die Turtle wieder in den Ausgangspunkt gesetzt. copyleft:munz 85 Fachschulen Lörrach 4 Rekursionen Java Eine wichtige Anmerkung: Das Bewegen zu den Punkten P_li und P_re geht immer vom Ausgangspunkt aus, wohin die Turtle zunächst gesetzt werden muss. Der Grund dafür ist wieder die mangelnde Genauigkeit beim Zeichnen, wenn man die Bewegungen allein mit den Turtle-Methoden vor(..) und rechts(..) bewerkstelligt. Die Ungenauigkeit schaukelt sich mit zunehmender Rekursionstiefe immer höher, so dass die rechtwinkligen Dreiecke nicht mehr geschlossen erscheinen, die ganze Figur auseinander läuft, so dass sie als Pythagoraus-Baum kaum mehr erkennbar ist, obwohl der Algorithmus stimmt. Aufgabe 23: Programmieren Sie einen symmetrischen Pythagoras-Baum copyleft:munz 86 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets 5 Threads und Rekursionen – ein alternativer Zugang mit Applets 5.1 Java Nebenläufige Prozesse (Threads) Ein Betriebssystem wie Windows verwaltet scheinbar parallel und unabhängig verlaufende Prozesse (Multitasking) Solche eigenständigen Prozesse eignen sich besonders, um im Hintergrund laufende Vorgänge zu übernehmen. Musterbeispiele sind die zahlreichen animierten Applets, die man überall im Internet finden kann. Java gestattet den Einbau solcher scheinbar für sich eigenständig ablaufender Handlungs-Fäden („Threads“) in eigene Programme, ohne dass man ins Betriebssytem eingreifen müsste. Erstes Beispiel: import java.awt.*; import java.applet.*; public class Thread_1 extends Applet { //Globale Variablendeklarationen: EinfachThread et1,et2; //Diese Hilfsklasse muss im gleichen Verzeichnis stehen! TextArea ausgabeTA; public void init() { ausgabeTA= new TextArea("",16,50,TextArea.SCROLLBARS_BOTH); add(ausgabeTA); ausgabeTA.setBackground(Color.white); ausgabeTA.setEditable(false); et1 = new EinfachThread("JavaThread1", ausgabeTA); et2 = new EinfachThread("JavaThread2", ausgabeTA); et1.start(); et2.start(); }//Ende von init-Methode }//Ende des ganzen Applets Hier die Hilfsklasse EinfachThread: import java.awt.*; public class EinfachThread extends Thread { //Globale Variablendeklarationen: String str; TextArea tA; public EinfachThread(String name,TextArea t) { str=name; tA=t; } //Ende Konstruktor public void run() { for (int i=1; i<7; i++) { tA.append(i+": "+str+"\n"); try { sleep((int)(Math.random()*1000)); } catch (InterruptedException e){} }//Ende for tA.append("Ende von : "+str+"\n"); }//Ende von run }//Ende der Klasse EinfachThread copyleft:munz 87 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Ein mögliches Rennen der beiden Threads zeigt obige Abbildung. Beim nächsten Start kann es wieder anders aussehen! Erläuterungen zu obigem Applet: Thread ist eine eigene Java-Klasse, die man auf zwei Arten nutzen kann. Man schafft wie in obigem Beispiel durch Vererbung (extends) von Thread abgeleitete Klassen wie z.B. EinfachThread. Diese Klassen können dann in Applikationen oder Applets als Hilfsklassen benutzt werden (da Java keine Mehrfachvererbung zulässt, kann man nicht EinfachThread sowohl von Applet wie von Thread abstammen lassen.) Will man aber keine Hilfsklassen benutzen, muss das Applet die Schnittstelle (Interface) Runnable implementieren. Dies wird in späteren Beispielen gezeigt werden. Im Applet Thread_1 werden lediglich in init die zwei Threads mit ihren Namen geschaffen, das Ausgabetextfeld wird als Parameter übergeben. Dann werden beide Threads mit der Methode start() der Klasse Thread gestartet. Jeder so geschaffene EinfachThread wirft seine run()-Methode an, schreibt seinen Namen ins Textfeld und legt sich dann über seine sleep()-Methode eine zufällige Zeit schlafen. Nach 6 Schleifendurchläufen endet er jeweils. copyleft:munz 88 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Aufgabe 24: Erweitern Sie obige Klassen an geeigneter Stelle so, dass die jeweilige Verzögerungszeit im TextFeld hinter dem Thread-Namen ausgedruckt wird. Aufgabe 25: Statten Sie das obige Applet mit einem Button „Los!“ aus, sodass Sie durch Knopfdruck das Thread-Rennen erneut starten können. Im nächsten Beispiel werden keine Hilfsklassen benutzt, es wird Gebrauch von der InterfaceTechnik gemacht. import java.awt.*; import java.applet.*; public class Thread2Intflott extends Applet implements Runnable { //Globale Variablendeklarationen: Thread th1, th2; //Jetzt sind die beiden Instanzen von Thread! TextArea ausgabeTA; int verzoegerung; public void init() { ausgabeTA = new TextArea("",20,50,TextArea.SCROLLBARS_BOTH); add(ausgabeTA); ausgabeTA.setBackground(Color.white); ausgabeTA.setEditable(false); th1 = new Thread(this,"th1"); th2 = new Thread(this,"th2"); th1.start(); th2.start(); } //Ende von init-Methode public void run() { for (int i=1; i<7; i++) { try { verzoegerung = (int)(Math.random()*1000); Thread.currentThread().sleep(verzoegerung); ausgabeTA.append( i +": " + Thread.currentThread().getName()+" Verzoegerung=" + verzoegerung+"\n" ); } //Ende try catch (InterruptedException e){} } //Ende for ausgabeTA.append( "Ende von : "+Thread.currentThread().getName()+"\n" ); }//Ende von run }//Ende des ganzen Applets copyleft:munz 89 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Es entstehen ähnlich unvorhersagbare Rennergebnisse wie im Fall des ersten Applets mit den Hilfsklassen, z.B.: Will man erreichen, dass ein Thread eine Methode erst „abarbeiten“ kann bevor ein anderer sie „betritt“, muss man die Methode mit einem sog. „Monitor“ schützen. Das Java-Schlüsselwort dazu ist synchronized (diese Methode wird bei „Multitasking“ oft angewandt, um so genannte „Dead-Locks“ zu vermeiden, d.h., dass ein Prozess, der im Besitz eines Betriebsmittels ist, auf einen zweiten Prozess warten muss, dieser aber genau das von Prozess 1 besetzte Betriebsmittel braucht und auf dessen Freigabe wartet). Hier dazu die Klasse: import java.awt.*; import java.applet.*; public class Thread2IntflottSync extends Applet implements Runnable { //Globale Variablendeklarationen: //Diese Hilfsklasse muss im gleichen Verzeichnis stehen! Thread th1,th2; TextArea ausgabeTA; int verzoegerung; public void init() { ausgabeTA= new TextArea("", 20, 50, TextArea.SCROLLBARS_BOTH); add(ausgabeTA); ausgabeTA.setBackground(Color.white); ausgabeTA.setEditable(false); th1=new Thread(this,"th1"); th2=new Thread(this,"th2"); th1.start(); th2.start(); }//Ende von init-Methode copyleft:munz 90 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public synchronized void run() { for (int i=1; i<7; i++) { try { verzoegerung=(int)(Math.random()*1000); Thread.currentThread().sleep(verzoegerung); ausgabeTA.append( i+": "+Thread.currentThread().getName()+" Verzoegerung="+verzoegerung+"\n" }//Ende try catch (InterruptedException e){} }//Ende for ausgabeTA.append( "Ende von : "+Thread.currentThread().getName()+"\n" ); }//Ende von run }//Ende des ganzen Applets Da th1 zuerst gestartet wird, ergibt sich: Im letzten Beispiel dieses Abschnittes wird „ping-pong-artig“ von zwei Threads in das Ausgabetextfeld geschrieben, synchronized schützt die zwei Methoden ping und pong, wait() zwingt den Thread, der den Monitor hat, zur Abgabe desselben und zum Warten, bis ein anderer Thread über notifyAll() alle wartenden Threads benachrichtigt, dass sie nun weiterlaufen dürfen. import java.awt.*; import java.applet.*; public class Thread2Intsync2 extends Applet implements Runnable { //Globale Variablendeklarationen: Thread th1, th2; boolean flag = true; TextArea ausgabeTA; int verzoegerung; public void run() { los(); } copyleft:munz 91 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void init() { ausgabeTA= new TextArea("", 20, 50, TextArea.SCROLLBARS_BOTH); add(ausgabeTA); ausgabeTA.setBackground(Color.white); ausgabeTA.setEditable(false); th1=new Thread(this, "th1"); th2=new Thread(this, "th2"); th1.start(); th2.start(); }//Ende von init-Methode public void los() { for (int i = 0; i < 7; i++) { if (flag) ping(i); else pong(i); }//Ende for ausgabeTA.append( "Ende von : " + Thread.currentThread().getName() + "\n" ); }//Ende von los public synchronized void ping(int i) { try { flag = !flag; verzoegerung=(int)(Math.random()*1000); Thread.sleep(verzoegerung); ausgabeTA.append( "ping: " + i + ": " + Thread.currentThread().getName() + " Verzoegerung=" + verzoegerung + "\n" ); wait(); } catch (InterruptedException e){} } public synchronized void pong(int i) { try { flag =!flag; verzoegerung=(int)(Math.random()*1000); Thread.sleep(verzoegerung); ausgabeTA.append( "pong: " + i + ": " + Thread.currentThread().getName() + " Verzoegerung=" + verzoegerung + "\n" ); notifyAll(); } catch (InterruptedException e){} } }//Ende des ganzen Applets copyleft:munz 92 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Ein Ergebnis: copyleft:munz 93 Fachschulen Lörrach 5 5.2 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Graphisch animierte Applets mit Threads Wir entscheiden uns für die Interface-Methode, da in allen Beispielen ein Thread genügt, um das Wesentliche zu demonstrieren. Generell lassen wir die run-Methode des Threads am Ende repaint() aufrufen, also das Applet neu zeichnen (repaint ruft nun die Applet-Methode update auf.) Um störendes Flackern bei schneller Animation zu vermeiden, muss stets diese update-Methode des Applets überschrieben werden, da die sonst geerbte update-Methode den Bildschirm erst löscht und dann paint aufruft. Des Weiteren bedienen wir uns des sog. Double-Buffering d.h. wir stellen die Zeichnung komplett im Speicher fertig und geben sie erst dann mit drawImage auf einen Schlag auf die Appletfläche. Dieser unsichtbare Pufferbereich im Speicher heißt im folgenden Applet offImage, offGraphics der zugehörige Grafikkontext. Hinweis: Bei allen AWT-Anwendungen ist die Behandlung des Double-Bufferings vom Programmierer selbst zu leisten. Klassen, die von Swing abgeleitet sind, unterstützen Double-Buffering automatisch. import java.awt.*; import java.applet.*; public class Animacon extends Applet implements Runnable { Thread mythread=null; int dx, dy; int verzoegerung; int rot, gruen, blau; int i=0; Graphics offGraphics; Image offImage; public void init() { dx=size().width; dy=size().height; //Appletgröße holen! verzoegerung=10; //Speicherbereich für double-buffering: offImage = createImage(dx, dy); offGraphics = offImage.getGraphics(); //dazu gehöriges Graphics-Objekt offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx, dy); } public void start() { // die start-Methode des Applets wird // zum Start des Threads benutzt! if(mythread == null) { mythread = new Thread(this); mythread.start(); } } public void stop() { //die stop-Methode des Applets wird // zum Beenden des Threads benutzt! mythread = null; offImage = null; offGraphics = null; } public void run() { while (mythread != null) { try { Thread.sleep(verzoegerung); } catch (InterruptedException e) {} repaint(); //Neu zeichnen! } } copyleft:munz 94 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets } Java public void paint(Graphics g) { update(g); } public void update (Graphics g) { //dieses update überschreibt das vorhandene update, //das den Bildschirm löschen würde und Flackern erzeugte! rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); //Würfeln der neuen Farbbytes im RGB-Modell offGraphics.setColor(new Color(rot,gruen,blau)); //Daraus die neue Farbe für offGraphics bilden //4 Linien zeichnen //Danach i erhöhen, um die Punkte zu verändern! offGraphics.drawLine( i, 0, dx-i, dy); offGraphics.drawLine(dx-i, 0, i, dy); offGraphics.drawLine(0, i, dx, dy-i); offGraphics.drawLine(0, dy-i, dx, i); i += 4; if(i > dx) i = 0; if(i > dy) i = 0; if(i == 0){ offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx, dy); } //Bild immer wieder von den vielen Linien befreien, // wenn i von vorne beginnt! //i zurücksetzen, falls über Rand hinaus! g.drawImage(offImage, 0, 0, this); //Die fertige Zeichnung auf einen Schlag // im Appletfenster sichtbar machen! } Das etwas aufwendigere Animacon2-Applet gestattet,die Verzögerung während des Thread-Laufes vom Benutzer ändern zu lassen: import java.awt.*; import java.applet.*; public class Animacon2 extends Applet implements Runnable { Thread mythread=null; int dx, dy; int verzoegerung; double verzdouble; int rot, gruen, blau; int i=0; Graphics offGraphics; Image offImage; String verzkette = "10"; TextField tf=new TextField(verzkette); Label lb; copyleft:munz 95 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void init() { dx=size().width; dy=size().height; //Appletgröße holen! verzoegerung=10; setLayout(new FlowLayout()); lb = new Label("Verzögerung:"); add(lb); add(tf); offImage = createImage(dx, dy); //Speicherbereich für double-buffering offGraphics = offImage.getGraphics(); //dazu gehöriges Graphics-Objekt offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx, dy); } public void start() { // die start-Methode des Applets wird // zum Start des Threads benutzt! if(mythread == null) { mythread = new Thread(this); mythread.start(); } } public void stop() { // die stop-Methode des Applets wird // zum Beenden des Threads benutzt! mythread = null; offImage = null; offGraphics = null; } public void run() { while (mythread != null) { verzkette = tf.getText(); try { verzdouble=Double.valueOf(verzkette).doubleValue(); verzdouble = Math.abs(verzdouble); //Abfangen negativer Eingaben verzoegerung=(int)verzdouble; } catch(NumberFormatException nfe) { verzoegerung=10; tf.setText("10"); } //Abfangen unsinniger Eingabeformate! try { Thread.sleep(verzoegerung); } catch (InterruptedException e) {} repaint();//Neu zeichnen! } } public void paint(Graphics g) { update(g); } copyleft:munz 96 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets } Java public void update (Graphics g) { // dieses update überschreibt das vorhandene update, // das den Bildschirm löschen würde und Flackern erzeugte! rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); // Würfeln der neuen Farbbytes im RGB-Modell offGraphics.setColor(new Color(rot,gruen,blau)); // Daraus die neue Farbe für offGraphics bilden // 4 Linien zeichnen // Danach i erhöhen, um die Punkte zu verändern! offGraphics.drawLine( i, 0, dx-i, dy); offGraphics.drawLine(dx-i, 0, i, dy); offGraphics.drawLine(0, i, dx, dy-i); offGraphics.drawLine(0, dy-i, dx, i); i += 4; if(i > dx) i = 0; if(i > dy) i = 0; if(i == 0) { offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx, dy); } // Bild immer wieder von den vielen Linien befreien, // wenn i von vorne beginnt! // i zurücksetzen, falls über Rand hinaus! g.drawImage(offImage, 0, 0, this); // Die fertige Zeichnung auf einen Schlag // im Appletfenster sichtbar machen! } Aufgabe 26: Erweitern Sie obiges Applet so, dass das i-Inkrement vom Benutzer während des Laufes änderbar ist. (hier ist es fest auf 4 gesetzt wg. i += 4;) copyleft:munz 97 Fachschulen Lörrach 5 5.3 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Graphisch animierte Applets mit Threads und Turtlegraphik Die seit LOGO-Zeiten beliebte Turtlegraphik wird im folgenden Abschnitt und im Kapitel über Rekursion eingesetzt, um mit einfachen Algorithmen höchst komplizierte und faszinierende Graphiken interaktiv und animiert entstehen zu lassen. Die Hilfsklasse Turtle ist für Pufferbereiche also DoubleBuffering konzipiert. Durch eine kleinere Änderung im Konstruktor lässt sich ihr aber ebensogut gleich das Graphics-Objekt als Parameter übergeben, damit wäre sie dann auch ohne Benutzung eines Pufferbereiches brauchbar. Hier der Text von Turtle.java: import java.awt.*; class Turtle { protected double posX, posY; protected double winkel; protected Color farbe = Color.black; protected Image offImage; protected double urX, urY; protected boolean stiftUnten; protected Graphics g; public Turtle( Image offImage, double x, double y, double richtung) { posX=x; posY=y; winkel=richtung; urX=x; urY=y; g=offImage.getGraphics(); stiftUnten = true; } public void zumAnfang() { posX=urX; posY=urY; } public void vor(double l) g.setColor(farbe); double neuX = posX + Math.cos(bogen(winkel))*l; double neuY = posY - Math.sin(bogen(winkel))*l; //- wg. y-Achse falsch rum!! if (stiftUnten) g.drawLine((int) posX, (int) posY, (int) neuX, (int) neuY); posX = neuX; posY = neuY; } public void drehe(double grad) { winkel += grad; } public void fuelleKreis(double radius) { int r = (int) (radius+0.5); g.setColor(farbe); g.fillOval((int) (posX -radius), (int) (posY-radius), 2*r, 2*r ); } public void inRichtung(double grad) { winkel = grad; } public void geheZu(double neuX,double neuY) { g.setColor(farbe); if (stiftUnten) g.drawLine((int) posX, (int) posY, (int) neuX, (int) neuY ); posX = neuX; posY = neuY; } copyleft:munz 98 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void stiftfarbe(Color c) { farbe = c; } public boolean stiftHoch() { stiftUnten=false; return false; } public boolean stiftAb() { stiftUnten=true; return true; } public double liesRichtung () { return winkel; } public double liesX() { return posX; } public double liesY() { return posY; } public Color liesFarbe() { return (Color)farbe; } private double bogen(double winkel) { return winkel*Math.PI/180; } }//Ende von Turtle Diese Hilfsklasse wird vom folgenden Applet eingesetzt, um mit einem Objekt t vom Typ Turtle ein gleichseitiges Dreieck zu zeichnen, dessen Seitenlänge sich während der Animation ändert. Klickt man mit der Maus irgendwo auf die Appletfläche, hält die Animation an. import java.awt.*; import java.applet.*; public class MaleTurtle1 extends Applet implements Runnable { Thread thread=null; int dx, dy; int verzoegerung; int j; int rot, gruen, blau; boolean idle=false; double delay; String kette="Dreieck mit Turtle!"; String delkette="150"; TextField tf=new TextField(kette); TextField tff=new TextField(delkette); Label lb,lbb; Graphics offGraphics; Image offImage; Turtle t;//Turtleklasse copyleft:munz 99 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void init() { dx=size().width; dy=size().height; verzoegerung = 150; setLayout(new FlowLayout()); lb=new Label("Ihr Text:"); add(lb); add(tf); lbb=new Label("Verzögerung:"); add(lbb); add(tff); j=0; setBackground(Color.blue); idle=false; offImage=createImage(dx, dy); offGraphics=offImage.getGraphics(); offGraphics.setColor(Color.green); offGraphics.fillRect(0, 0, dx+1, dy+1); t=new Turtle(offImage,100,300,0); } public void start() { if(thread == null) { thread = new Thread(this); thread.start(); } } public void stop() { thread = null; offImage = null; offGraphics = null; } public void run() { while (thread != null) { kette = tf.getText(); delkette = tff.getText(); try { delay = Double.valueOf(delkette).doubleValue(); delay = Math.abs(delay); //Abfangen negativer Eingaben verzoegerung=(int)delay; } catch(NumberFormatException nfe) { verzoegerung=100;tff.setText("100"); } try { Thread.sleep(verzoegerung); } catch (InterruptedException e) {} offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx+1, dy+1); repaint(); } } public void paint(Graphics g) { update(g); } public boolean mouseDown(Event s, int x, int y) { if(idle) thread.resume(); else thread.suspend(); idle = !idle; return true; } //Veraltet, geht aber ohne MouseListener-Interface! copyleft:munz 100 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets } Java public void update (Graphics g) { rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); offGraphics.setColor(new Color(rot, gruen, blau)); offGraphics.drawString(kette, dx/2, dy/2); rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); offGraphics.setColor(new Color(rot, gruen, blau)); j++; if(j >= 8) j = 0; offGraphics.drawString( "TurtleMalen Längenwert : "+ String.valueOf(j), dx/10, dy/8 ); t.stiftfarbe(offGraphics.getColor()); for(int i=0; i<3; i++) { t.vor(50+j*12); t.drehe(120); } t.stiftHoch(); t.zumAnfang(); t.stiftAb(); rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); offGraphics.setColor(new Color(rot, gruen, blau)); offGraphics.drawString( "Klicken hält an! Nochmal klicken startet wieder!", dx/20, 9*dy/10 ); g.drawImage(offImage, 0, 0, this); } mouseDown ist zwar etwas veraltet (deprecated) verlangt aber nicht noch einen MouseMotionListener o.ä. zu implementieren. Im folgenden Applet ist eine Schaltfläche zum Anhalten des Threads eingebaut worden, die ist zwar nicht deprecated, verlangt aber die Einbindung von ActionListener als zusätzlichem Interface: import java.awt.*; import java.awt.event.*; import java.applet.*; public class MaleTurtle2 extends Applet implements Runnable, ActionListener { Button b1 = new Button("Anhalten!"); Thread meinthread=null; int dx, dy; int verzoegerung; int j; boolean gestoppt=false; double delay; String kette="Dreieck mit Turtle!"; String delkette="150"; TextField tf=new TextField(kette); TextField tff=new TextField(delkette); Label lb,lbb; Graphics offGraphics; Image offImage; Turtle t;//Turtleklasse copyleft:munz 101 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void init() { dx=size().width; dy=size().height; verzoegerung = 150; setLayout(new FlowLayout()); lb=new Label("Ihr Text:"); add(lb); add(tf); lbb=new Label("Verzögerung:"); add(lbb); add(tff); add(b1);b1.addActionListener(this); j=0; offImage=createImage(dx, dy); offGraphics=offImage.getGraphics(); offGraphics.setColor(Color.green); offGraphics.fillRect(0, 0, dx+1, dy+1); t = new Turtle(offImage,100,300,0); } public void start() { if(meinthread == null) { meinthread = new Thread(this); meinthread.start(); } } public void stop() { meinthread = null; offImage = null; offGraphics = null; } public void run() { while (meinthread != null) { kette = tf.getText(); delkette = tff.getText(); try { delay=Double.valueOf(delkette).doubleValue(); delay = Math.abs(delay); //Abfangen negativer Eingaben verzoegerung=(int)delay; } catch(NumberFormatException nfe) { verzoegerung=100;tff.setText("100"); } try { meinthread.sleep(verzoegerung); } catch (InterruptedException e) {} offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx+1, dy+1); repaint(); } } public void paint(Graphics g) { update(g); } copyleft:munz 102 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets } copyleft:munz Java public void actionPerformed(ActionEvent e) { if(gestoppt) { meinthread.resume(); b1.setLabel("Anhalten!"); } else { meinthread.suspend(); b1.setLabel("Los!"); } gestoppt =! gestoppt; } public void neueFarbe(Graphics gg) { int rot, gruen, blau; rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); gg.setColor(new Color(rot,gruen,blau)); } public void update (Graphics g) { neueFarbe(offGraphics); offGraphics.drawString(kette,dx/2,dy/2); neueFarbe(offGraphics); j++; if(j >= 8) j = 0; offGraphics.drawString( "TurtleMalen Längenwert : "+ String.valueOf(j), dx/10, dy/8 ); t.stiftfarbe(offGraphics.getColor()); for(int i = 0; i < 3; i++) { t.vor(50+j*12); t.drehe(120); } t.stiftHoch();t.zumAnfang();t.stiftAb(); neueFarbe(offGraphics); g.drawImage(offImage, 0, 0, this); } 103 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Aufgabe 27: Schreiben Sie eine eigene Methode für das Dreieck z.B. als: public void malegleichseitigesDreieck(double seitenlaenge), sie zeichnet das gleichseitige Dreieck mit seitenlaenge. Rufen Sie die Methode danach in update an geeigneter Stelle auf: malegleichseitigesDreieck(i+10); o.ä. Aufgabe 28: Schreiben Sie eine Methode public void maleregelmaessiges6Eck(double seitenlaenge), sie zeichnet das regelmäßige 6-Eck mit seitenlaenge. Bauen Sie sie geeignet in update ein. Aufgabe 29: Schreiben Sie eine Methode public void maleregelmaessigesNEck(int n, double seitenlaenge), sie zeichnet das regelmäßige n-Eck mit seitenlaenge. Bauen Sie sie geeignet in update ein. Aufgabe 30: Eine Methode zum Zeichnen einer Quadratrosette ist zu schreiben und in update einzubauen: public void maleQuadratRosette( double seitenlaenge, double winkel), sie zeichnet ein Quadrat mit seitenlaenge, dann dreht sich die Turtle um winkel zeichnet wieder ein Quadrat mit seitenlaenge, usw. bis der Gesamtdrehwinkel ein Vielfaches von 360 Grad ist (in Java: gesamtdrehwinkel % 360 ==0 abfragen!). Aufgabe 31: Eine Methode zum Zeichnen eines Polygons ist zu schreiben und in update einzubauen: public void malePolygon(double seitenlaenge, double winkel), sie zeichnet eine Strecke mit seitenlaenge, dann dreht sich die Turtle um winkel, zeichnet wieder eine Strecke mit seitenlaenge, usw. bis der Gesamtdrehwinkel ein Vielfaches von 360 Grad ist in (Java: gesamtdrehwinkel % 360 == 0 abfragen!). Aufgabe 32: Eine Polygonspirale entsteht, wenn man in obiger Aufgabe in jedem Schritt die Seitenlänge um einen festen Wert erhöht (ausprobieren!). Aufgabe 33: Spirolatertalkurven aus einem Bundeswettbewerb Informatik lassen sich ebenfalls noch gut nicht-rekursiv (also nur mit Schleifen) programmieren. Man kann sie aus mehreren Einzelhaken zusammensetzen oder sie auch in einer einzigen Methode ausprogrammieren. Man gibt ein: int anzahl, double winkel, double startLaenge. Dann geschieht folgendes: Die Turtle zeichnet eine Strecke der Länge startLaenge, dreht sich um winkel, zeichnet eine Strecke der Länge 2*startLaenge, dreht sich um winkel, zeichnet eine Strecke der Länge 3*startLaenge, dreht sich um winkel, usw. zeichnet eine Strecke der Länge anzahl*startLaenge, dreht sich um winkel. Jetzt ist der erste „Haken“ fertig! Jetzt wird der Gesamtdrehwinkel der Turtle gegenüber dem Zustand vor dem ersten Aufruf berechnet! Ergebnis zu (int) casten! Solange dieser Gesamtdrehwinkel kein Vielfaches von 360 Grad ist, wird wieder ein Haken wie oben gezeichnet und dann der Gesamtdrehwinkel der Turtle gegenüber dem Zustand vor dem ersten Aufruf erneut berechnet (immer zu (int) casten)! (Ende der „Solange-Anweisungen“!) copyleft:munz 104 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Ein Bild für winkel=120, anzahl=5: Aufgabe 34: Erweitern Sie Ihre bisherigen Applets um diese Spirolateralkurven. copyleft:munz 105 Fachschulen Lörrach 5 5.4 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Rekursion Obwohl Rekursion oft als schwer verständliche Lösungstechnik gilt ist sie dann einfach zu verstehen und in Hierarchien fast selbstverständliche Alltagstechnik, wenn man sich nicht bemüht zu verstehen, wie sie rechnerintern verwaltet wird. Hart an der Grenze der Paradoxie gelegen, eine Sache durch sich selbst zu definieren, vermeidet sie diese haarscharf dadurch, dass sie eine Sache nicht durch sich selbst sondern durch eine einfachere Version ihrer selbst und den allereinfachsten Fall, den „Basisfall“ erklärt (vollständige Induktion!). Will man also ein Problem P(n), n natürliche Zahl, rekursiv lösen, muss man zwei Dinge angeben: den Basisfall P(0) oder P(1) den „Induktionsanfang“ die Zurückführung von P(n) auf P(n-1), den „Induktionsschritt“. Das nächste Beispiel zeigt, wie die Verantwortung nach unten durchgereicht wird und anschließend die Erfolgsmeldungen von unten wieder hochgereicht werden: import java.awt.*; import java.applet.*; import java.awt.event.*; public class Arbeiten extends Applet implements ActionListener { //Globale Variablendeklarationen: Button b1=new Button("Problem lösen!"); TextField eingabefeld; TextArea ausgabeTA; //TextFläche für Ausgabestrings! Label lb; String s1; //Globaler String double azahl; int anzahl,klickzahl; public void init() { eingabefeld=new TextField(3); lb= new Label("Dienstgrad:"); add(lb); add(eingabefeld); add(b1); b1.addActionListener(this); //Button auf Appletfläche und dann Lauscher ansetzen! klickzahl=0; s1="3"; eingabefeld.setText(s1); ausgabeTA= new TextArea("",10,50,TextArea.SCROLLBARS_BOTH); add(ausgabeTA); ausgabeTA.setBackground(Color.white); ausgabeTA.setEditable(false); } //Ende von init-Methode public void erledigeArbeit (int dienstgrad){ //Weitergabe der Drecksarbeit rekursiv! if(dienstgrad == 0) { //Basisfall! Der Gemeine machts! ausgabeTA.append( "Hurra! Basisfall endlich von mir erledigt!"+"\n" ); } //Ende Basisfall else { //Rekursionsfall!Weitergabe der Drecksarbeit! ausgabeTA.append( "Von: "+dienstgrad+" an: " +(dienstgrad-1)+ " Arbeit erledigen!"+"\n" ); erledigeArbeit(dienstgrad-1); //Rekursiver Aufruf!!! ausgabeTA.append( "Von: "+(dienstgrad-1)+" an: " +dienstgrad+ "Es ist jetzt vollbracht!"+"\n" ); } //Ende else } //Ende erledigeArbeit copyleft:munz 106 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void actionPerformed(ActionEvent e) { klickzahl++; s1 = eingabefeld.getText(); azahl = Double.valueOf(s1).doubleValue(); anzahl = (int)azahl; repaint(); } //Am Ende wird neu gezeichnet!! public void paint (Graphics g) { if(klickzahl > 0) erledigeArbeit(anzahl); } //Ende von paint } //Ende des ganzen Applets Natürlich kann man auch wieder n! rekursiv berechnen usw., Rekursion zusammen mit Graphik scheint uns aber interessanter. Ein Musterbeispiel für graphische Rekursion ist die Kochkurve: Alles beginnt mit einer Linie der Länge L („Initiator“,Rekursionsstufe 0), der „Generator“ (Rekursionsstufe 1) besteht aus 4 Linien der Länge L/3 die mit der Turtle so zu zeichnen sind: Initiator mit L/3 zeichnen, Linksdrehung 60 Grad, Initiator mit L/3 zeichnen, Rechtsdrehung 120 Grad, Initiator mit L/3 zeichnen, Linksdrehung 60 Grad, Initiator mit L/3 zeichnen. Der Generator ist unten abgebildet. Damit ist die Rekursionsregel klar: koch(n-1,L/3); links(60); koch(n-1,L/3); rechts(120); koch(n-1,L/3); links(60); koch(n-1,L/3); Der Basisfall ist der Generator: vorwärts(L); copyleft:munz 107 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Im folgend abgedruckten Beispiel wird die Kochkurve mittels Threads und mit Hilfe unserer Hilfsklasse Turtle aus dem Thread-Kapitel animiert und farbig angezeigt. import import import public java.awt.*; java.awt.event.*; java.applet.*; class KochKurve extends Applet implements Runnable, ActionListener { Button b1=new Button("Anhalten!"); Thread meinthread=null; int dx, dy; int verzoegerung; int j,grad; boolean gestoppt=false; double delay,startlaenge; String kette="Kochkurven mit Turtle!"; String delkette="150",startlaengekette="243"; TextField tf=new TextField(kette); TextField tff=new TextField(delkette); TextField tf5=new TextField(startlaengekette); Label lb,lbb,lb5; Graphics offGraphics; Image offImage; Turtle t; //Turtleklasse copyleft:munz 108 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void init() { dx=size().width; dy=size().height; verzoegerung = 150; setLayout(new FlowLayout()); lb=new Label("Ihr Text:"); add(lb); add(tf); lbb=new Label("Verzögerung:"); add(lbb); add(tff); add(b1);b1.addActionListener(this); lb5=new Label("Startlänge:"); add(lb5); add(tf5); j=0;grad=0; offImage=createImage(dx, dy); offGraphics=offImage.getGraphics(); offGraphics.setColor(Color.green); offGraphics.fillRect(0, 0, dx+1, dy+1); t = new Turtle(offImage,100,200,0); } public void start() { if(meinthread == null) { meinthread = new Thread(this); meinthread.start(); } } public void stop() { meinthread = null; offImage = null; offGraphics = null; } copyleft:munz 109 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void run() { while (meinthread != null) { kette = tf.getText(); delkette = tff.getText(); try { delay = Double.valueOf(delkette).doubleValue(); delay = Math.abs(delay); //Abfangen negativer Eingaben verzoegerung=(int)delay; } catch(NumberFormatException nfe) { verzoegerung=100;tff.setText("100"); } startlaengekette = tf5.getText(); try { startlaenge = Double.valueOf(startlaengekette).doubleValue(); } catch(NumberFormatException nfe) { startlaenge=25;tf5.setText("25"); } try { meinthread.sleep(verzoegerung); } catch (InterruptedException e) {} offGraphics.setColor(Color.black); offGraphics.fillRect(0, 0, dx+1, dy+1); repaint(); } //Ende while } //Ende run public void paint(Graphics g) { update(g); } public void actionPerformed(ActionEvent e) { if(gestoppt) { meinthread.resume(); b1.setLabel("Anhalten!"); } else { meinthread.suspend(); b1.setLabel("Los!"); } gestoppt=!gestoppt; } public void neueFarbe(Graphics gg) { int rot, gruen, blau; rot = (int)(Math.random()*256); gruen = (int)(Math.random()*256); blau = (int)(Math.random()*256); gg.setColor(new Color(rot,gruen,blau)); } copyleft:munz 110 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java public void maleKoch(int stufe,double laenge) { if (stufe == 0){ //Basisfall! t.vor(laenge); } else { //Rekursionsfall,von n auf n-1 //den schwarzen Peter weitergeben! maleKoch(stufe-1,laenge/3); t.drehe(60); maleKoch(stufe-1,laenge/3); t.drehe(-120); maleKoch(stufe-1,laenge/3); t.drehe(60); maleKoch(stufe-1,laenge/3); } //Ende else } // Ende maleKoch public void update (Graphics g) { neueFarbe(offGraphics); offGraphics.drawString(kette,dx/2,dy/2); neueFarbe(offGraphics); j++; if(j >= 8) j = 0; offGraphics.drawString( "KochKurve malen Längenwert : "+ String.valueOf(j), dx/10, dy/7 ); t.stiftfarbe(offGraphics.getColor()); maleKoch(grad,startlaenge+j*2); grad++; if(grad > 5) grad = 0; t.stiftHoch(); t.zumAnfang(); t.stiftAb(); neueFarbe(offGraphics); g.drawImage(offImage, 0, 0, this); } //Ende update } //Ende Applet copyleft:munz 111 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Aufgabe 35: Ändern Sie das Applet so ab, dass die Kreuzstichkurve gezeichnet wird. Diese hat den gleichen Initiator wie die Kochkurve, den Generator entnehmen Sie der Abbildung: copyleft:munz 112 Fachschulen Lörrach 5 Threads und Rekursionen – ein alternativer Zugang mit Applets Java Aufgabe 36: Bilden Sie aus der Kochkurve die „Schneeflockenkurve“, die aus drei Kochkurven besteht, die zu einem „gleichseitigen Dreieck“ hintereinander gelegt werden. Die folgende Abb. zeigt eine höhere Rekursionsstufe. Es gibt nicht nur rekursiv definierte Methoden sondern auch ebenso definierte Klassen. Dies benötigt man für die Implementierung von Listen, Stacks, Bäumen u.ä. (in Sprachen wie Pascal hieß dies „abstrakte Datentypen“ oder „dynamische Datentypen“, in Java gibt es keine Datentypen mehr nur noch Klassen). Diese Fragestellungen werden in einem eigenen Kapitel eingehender untersucht werden. copyleft:munz 113 Fachschulen Lörrach 6 Abstrakte Datentypen 6 Abstrakte Datentypen Java Jedem der sich mit Informatik beschäftigt, werden irgendwann Begriffe wie Liste, Stack oder Baum begegnen. Bei diesen Begriffen handelt es sich um sog. abstrakte Datentypen. Der Stack, oder auch Keller genannt, ist so wichtig, dass er in Java unter dem Namen Stack als Klasse standardmäßig implementiert ist. Wir wollen uns den Datentyp Liste genauer anschauen und auch implementieren und dabei Listentypen wie LIFO und FIFO kennen lernen. 6.1 Was ist eine Liste? Eine erste Vorstellung von Listen kann man sich machen, wenn man sie von den Feldern abgrenzt. int n = (int)(100*Math.random()); Object[] feld = new Object[n]; Die beiden Zeilen zeigen, wie vielfältig Felder in Java verwendet werden können. Sie können alle möglichen Daten aufnehmen und ihre Größe ist insofern flexibel, dass erst zur Laufzeit bestimmt wird, wie viele Eintragungen in das Feld vorgenommen werden können. Ist die Länge aber mal gesetzt, wird das „Gebilde“ eher starr. Man kann das Feld nicht weiter wachsen lassen, wenn man erkennt, dass noch einige Plätze nötig sind und man kann es nicht schrumpfen lassen, wenn man zu großzügig angelegt hat. Von Listen als abstrakte Datentypen verlangt man aber, dass sie mit dem Bedarf 8 wachsen und schrumpfen . Ein weiterer Unterschied zu den Feldern, bei denen man auf die einzelnen Plätze mittels Platznummern zugreifen kann, besteht darin, dass Elemente einer Liste anders angeordnet sind. Es sind Gebilde, die zum einen einen Wert tragen und zum anderen auf das nächste Listenelement verweisen. Ein wahlfreier Zugriff auf einzelne Elemente ist nicht mehr möglich. Das unten stehende Bild vermittelt einen Eindruck, wie man sich eine Liste aufgebaut vorstellen kann. Wir modellieren zunächst ein Listenelement in Java; seinen Bauplan legen wir konsequenterweise in der Klasse Element an. An der Modellierung erkennt man, dass die Verkettung eines Elementes in der Liste mit seinem Nachfolger dadurch geschieht, dass es ein Attribut hat, dessen Typ selbst wieder ein Element ist und sein Wert nichts anderes ist, als eine Referenz auf ein neues ElementObjekt. Erinnern wir uns: Die Werte von Java-Variablen sind, die primitiven Datentypen ausgenommen, immer Referenzen. Ein Verzeigern, wie wir es von anderen Sprachen gewohnt sind, entfällt, ja ist nicht einmal möglich; aus Sicherheitsgründen haben die Entwickler von Java ein explizites Referenzieren von Beginn an ausgeschlossen. 8 Der in Java implementierte Datentyp Vector bietet hier größere Flexibilität copyleft:munz 114 Fachschulen Lörrach 6 Abstrakte Datentypen Java Das Implementieren der Klasse Element in Java ist nicht sehr schwierig, die Bezeichner von Attributen und Methoden sind sprechend und sagen das Wesentliche aus. Die Klasse fügen wir dem Paket abstraktDT hinzu. package abstraktDT; public class Element { protected Object wert; protected Element naechstes; public Element() { wert = null; naechstes = null; } public Element(Object wert) { setWert(wert); naechstes = null; } public Object getWert() { return wert; } public void setWert(Object wert) { this.wert = wert; } public Element getNaechstes() { return naechstes; } public void setNaechstes(Element naechstes) { this.naechstes = naechstes; } } Die Klasse hat zwei Konstruktoren. Mit Hilfe des ersten Konstruktors lassen sich Objekte der Klasse Element erzeugen, die weder Werte noch Referenzen auf Nachfolger haben. Der zweite Konstruktor dagegen erlaubt es, Element-Objekte zu erzeugen, die Werte haben. Referenzen auf Nachfolger gibt es auch hier nicht. In beiden Fällen sind die Attributs-Werte von naechstes die Referenz null, „zeigen“ demnach auf kein konkretes Objekt9. 6.2 Die abstrakte Liste Über eine Liste hat man solange Kontrolle, solange man den Kopf, sprich das erste Element kennt. Ein Element, über das man einen direkten Zugriff auf die Liste hat, könnte man Anker nennen. Anker werden auch benötigt, um die zwei wesentlichen Operationen auf Listen auszuführen, nämlich das Anhängen und das Ausgeben der Listenelemente. Wir modellieren eine neue Klasse Liste, in der wir allerdings noch offen lassen wollen, wie wir anhängen. Das Anhängen kann nämlich auf zwei Arten geschehen. Wie wir noch sehen werden, erhalten wir nämlich, je nachdem wie wir das Anhängen realisieren, eine LIFO – Liste oder eine FIFO – Liste. Deshalb legen wir das Anhängen in einer abstrakten Methode an und machen damit Liste zu einer abstrakten Klasse. Auch sie kommt in das Paket abstraktDT. 9 Da die Werte von noch nicht referenziierten Objekten standardmäßig null sind, hätte man auf die Zeile naechstes = null; verzichten können; lediglich didaktische Gründe veranlassen uns, sie stehen zu lassen. copyleft:munz 115 Fachschulen Lörrach 6 Abstrakte Datentypen Java Der Quelltext: package abstraktDT; public abstract class Liste { protected Element kopf = null; public void gibAus() { Element lauf = kopf; while (lauf != null) { System.out.print(lauf.getWert()+ ", "); lauf = lauf.getNaechstes(); } System.out.println(); } public abstract void haengeAn(Object wert); } Kommentare zum Quelltext: Die Klasse Liste enthält, da kein anderer Konstruktor implementiert ist, den Standardkonstruktor Liste(). In der Methode gibAus() wird lauf vom Typ Element deklariert und mit kopf initialisiert. Das Element-Objekt kopf spielt also die Rolle des Ankers. In der while-Schleife werden die Werte der Elemente in der Liste solange ausgegeben, bis das letzte Listenelement erreicht ist, d.h. bis naechstes auf null zeigt. Das ‚Durchhangeln’ durch die Liste geschieht in der Zeile: lauf = lauf.getNaechstes(); Die Situation vor dem Abarbeiten der Zeile: Die Situation nach dem Abarbeiten der Zeile: copyleft:munz 116 Fachschulen Lörrach 6 Abstrakte Datentypen Java Die Methode haengeAn() ist, wie schon angekündigt, abstrakt und muss von einer Klasse, die von Liste erbt, implementiert werden. 6.3 ListeLIFO LIFO steht für Last In First Out. Bei einer LIFO-Liste wird bei der Ausgabe immer das zuletzt eingegebene Element als erstes wieder ausgegeben. Dabei kann man Ausgaben sich ruhig verallgemeinert als irgendwelche Jobs vorstellen. Eine LIFO-Liste lässt sich dann mit einem Stapel auf einem Schreibtisch vergleichen. Neue Jobs werden oben aufgelegt und von oben wird auch abgearbeitet. Der letzte Auftrag ist also der, der als erster erledigt wird. Wollte man diese Liste mit einem Motto beschreiben, so könnte dieses „Die Ersten werden die Letzten sein“ heißen. Quelltext: package abstraktDT; public class ListeLIFO extends Liste { public void haengeAn(Object wert) { Element neu = new Element(wert); if (kopf != null) neu.setNaechstes(kopf); kopf = neu; } } Das beim Aufruf der Methode haengeAn(..) übergebene Objekt wird im erzeugten Element-Objekt neu abgelegt. Ist die Bedingung der if-Anweisung kopf !=null falsch, zeigt also der von Liste geerbte kopf auf null, und somit die Liste leer, so wird kopf auf neu gesetzt (kopf = neu;), die Liste erhält ihren ersten Eintrag. Anders die Situation, wenn die Liste nicht mehr leer ist, dann werden folgende Anweisungen durchgeführt: neu.setNaechstes(kopf); kopf = neu; copyleft:munz 117 Fachschulen Lörrach 6 Abstrakte Datentypen Java Schauen wir uns den Vorgang Schritt für Schritt an. Nach dem Erzeugen des Element-Objektes neu haben wir die folgende Situation. neu.naechstes zeigt noch auf null. Diese Referenz soll nun auf das Element kopf zeigen. Dies geschieht mit neu.setNaechstes(kopf); Schließlich soll kopf wieder das erste Element sein. Also benötigen wir auch hier die Programmzeile kopf = neu; copyleft:munz 118 Fachschulen Lörrach 6 6.4 Abstrakte Datentypen Java ListeFIFO FIFO steht für First In First Out. Bei einer FIFO-Liste geschieht die Ausgabe in der gleichen Reihenfolge, wie die Eingabe. Ihr Motto könnte heißen: „Wer zuerst kommt, mahlt zuerst“. Eine FIFO-Liste verwaltet man am besten mit zwei Ankern. Der erste ist wie gehabt der kopf, wir benötigen ihn zur Ausgabe und mit ihm halten wir die Liste im „Griff“ und der zweite ist fuss, er ist das letzte Element, sein Nachfolger ist immer null. Er markiert die Stelle, wo neue Elemente eingefügt werden sollen. Wieder betrachten wir zuerst den Quelltext: Package abstraktDT; public class ListeFIFO extends Liste { private Element fuss = null; public void haengeAn(Object wert) { Element neu = new Element(wert); if (kopf == null) kopf = neu; else fuss.setNaechstes(neu); fuss = neu; } } Wie schon bei ListeLIFO wird der übergebene Wert in ein neu erzeugtes Element-Objekt mit dem Namen neu eingetragen. War die Liste zu diesem Moment noch leer, so bekommen kopf und fuss die gleiche Referenz wie neu. Ist die Liste nicht leer, so haben wir vor dem Anhängen die folgende Situation. copyleft:munz 119 Fachschulen Lörrach 6 Abstrakte Datentypen Java mit fuss.setNaechstes(neu); wird neu zum letzten Glied in der Liste: Jetzt muss nur noch fuss zum letzten Eintrag werden. fuss = neu; copyleft:munz 120 Fachschulen Lörrach 6 6.5 Abstrakte Datentypen Java Listen-Demo Mit einem einfachen Programm wollen wir die beiden Listen testen: import abstraktDT.*; public class ListenDemo { public ListenDemo() { ListeLIFO listeLIFO = new ListeLIFO(); ListeFIFO listeFIFO = new ListeFIFO(); for (int k = 0; k < 50; k++) { Integer i = new Integer(k); listeLIFO.haengeAn(i); listeFIFO.haengeAn(i); } System.out.println("Ausgabe LIFO:"); listeLIFO.gibAus(); System.out.println(); System.out.println("Ausgabe FIFO:"); listeFIFO.gibAus(); } public static void main (String args[]) { new ListenDemo(); } } Bemerkungen: Die ganzen Zahlen, die in die Listen eingetragen werden sollen, müssen als Integer-Objekte angelegt werden, denn die Klasse Integer erbt von Object und ihre Instanzen können somit in die Listen eingetragen werden. Werte vom Typ int können nicht in die Listen aufgenommen werden, da int keine Klasse ist und damit nicht von Object erben kann. Aufgabe 37: Implementieren Sie eine Klasse Person, mit den Attributen Name und Vorname, deren Werte beim Aufruf des Konstruktors gesetzt werden. Weiter werde in dieser Klasse die Methode toString() so überschrieben, dass System.out.println(einePerson) Vor- und Zuname des Objektes einePerson liefert. Passen Sie ListenDemo.java so an, dass Personen in die FIFO- und in die LIFO-Listen eingetragen und wieder ausgegeben werden. copyleft:munz 121 Fachschulen Lörrach 6 Abstrakte Datentypen Java Das Bild zeigt einen Keller in UML-Notation: Der Konstruktor erzeugt einen leeren Keller (vergleichbar einer Liste). Die Methode isEmpty() sagt, ob der Keller leer ist. top() gibt das oberste Element aus (vgl. dem kopf in ListeLIFO). push() legt das übergebene Objekt auf den Keller. pop() nimmt das letzte Element vom Keller. Implementieren Sie die Klasse Keller und fügen sie die Klasse in das Paket abstraktDT ein. Zum Testen dieser Fachklasse verwenden Sie das einfache Testprogramm KellerDemo.java. import info1.*; import abstraktDT.*; public class KellerDemo{ static Keller k = new Keller(); public KellerDemo(){ boolean fertig = false; int wahl; Integer i; while (!fertig){ System.out.println("isEmpty : 1"); System.out.println("top : 2"); System.out.println("push : 3"); System.out.println("pop : 4"); System.out.println("fertig : 5"); System.out.println(); wahl = Console.in.readInt(); switch(wahl){ case 1 : System.out.println(k.isEmpty()); break; case 2 : System.out.println( k.isEmpty()?"Keller leer":k.top()); break; case 3 : System.out.print("Zahl: "); i = new Integer(Console.in.readInt()); k.push(i); break; case 4 : if (!k.isEmpty()){ k.pop(); } break; case 5 : fertig = true; break; default: System.out.println("Falsche Eingabe"); } } } public static void main(String[] args){ new KellerDemo(); } } Aufgabe 38: Schreiben Sie eine GUI-Klasse (mit Swing) für den Keller. copyleft:munz 122 Fachschulen Lörrach 6 Abstrakte Datentypen 6.6 Java Der abstrakte Datentyp Baum In vielen Algorithmen, speziell Such- und Sortieralgorithmen, spielen solche Datentypen eine große Rolle, die wir Bäume nennen. Bei unseren Betrachtungen beschränken wir uns auf binäre Bäume, spezieller auf binäre Suchbäume. Wir werden sehen, dass diese geeignet sind, die in einem binären Suchbaum untergebrachten Daten so auszugeben, dass die Daten anschließend sortiert sind. 6.6.1 Was ist ein Baum? Ein binärer Baum ist entweder leer, oder hat folgende Gestalt: Wurzel B1 B2 Dabei sind B1 und B2 wieder binäre Bäume. Wir erkennen die Rekursion in der Definition. Ein vollständiger Baum hat Knoten, in denen Werte stehen, und Kanten, die zwei Knoten miteinander verbinden. Bei den Knoten unterscheiden wir 3 Arten: Die Wurzel, sie ist quasi der Start eines Baumes (rot markiert), die inneren Knoten (blau markiert) – von ihnen zeigt jeweils eine Kante nach „oben“ und mindestens eine Kante nach „unten“ – und schließlich Blätter, das sind Knoten, von denen nach unten keine Kanten zeigen. Zwei weitere Namen stehen für die Beziehung der Knoten untereinander. So nennen wir einen Knoten, von dem eine Kante nach unten zu einem weiteren Knoten zeigt, Vater des nachrangigen Knotens, der seinerseits Sohn genant wird. Wir geben uns eine 12-elementige Liste vor: [7, 12, 0, 5, 9, 2, 8, 1, 13, 10, 15, 3] und erzeugen einen binären Baum mit 12 Knoten (die Knoten werden „in der Breite“, d. h. „zeilenweise von links nach rechts“ aufgefüllt. Der Baum hat dann die folgende Gestalt: 7 1 4 12 0 2 3 5 5 6 9 1 13 10 15 3 8 9 10 11 12 2 7 8 Die Nummern neben bzw. unter den Knoten kennzeichnen die Reihenfolge der Erzeugung der Knoten und ihre Belegung mit Werten. copyleft:munz 123 Fachschulen Lörrach 6 Abstrakte Datentypen 6.6.2 Java Binärer Suchbaum Wir können die Zahlen aus der Liste [7, 12, 0, 5, 9, 2, 8, 1, 13, 10, 15, 3] auch nach folgender Regel in einen Baum eintragen: Trage zunächst das erste Element in die Wurzel ein. Das nächste Element kommt, wenn sein Wert kleiner ist als der Wert der Wurzel, in den Knoten, den man erreicht, wenn man die linke Kante weiter geht, andernfalls kommt es in den rechten Knoten. So werden nacheinander alle Elemente der Liste abgearbeitet. Ist ein Knoten belegt, geht man eine Stufe tiefer, also in die Knoten der „nächsten Generation“. Vor jedem Abstieg wird der Wert des einzutragenden Elements mit dem Wert des Knotens auf die gleiche Weise verglichen und somit der Abstieg über die rechte bzw. linke Kante gewählt. Das Bild unten zeigt den fertigen Baum. 7 1 0 12 3 2 4 6 5 5 2 1 3 8 12 9 13 9 8 10 15 7 10 11 Einen Baum, den wir auf die beschriebene Weise erstellt haben, nennen wir einen binären Suchbaum. Die Definition eines binären Suchbaums lehnt sich sehr eng an die Definition eines binären Baums an: • B ist leer oder • Wenn B nicht leer ist gilt o der rechte und der linke Unterbaum von B sind ebenfalls Binäre-Such-Bäume. o Ist w der Wert in der Wurzel, so sind alle Werte in den Knoten des linken Unterbaums kleiner als w und die Werte aller Knoten des rechten Unterbaums sind größer als w. (Insbesondere kommt also ein Wert nur einmal in einem Binären-Such-Baum vor, es sei denn man ersetzt größer durch größer gleich. Der Grund, warum wir einen solchen Baum Suchbaum nennen, zeigt sich darin, dass das Auffinden eines Wertes in einem Suchbaum sehr effizient ist. Um einen Wert in unserem Baum aufzufinden, benötigen wir maximal 4 Vergleiche (= Höhe des Baumes = Anzahl der Kanten im längsten Pfad). In der Ausgangsliste waren es maximal 12. Noch effizienter gestaltet sich das Suchen, wenn der Baum ausgeglichen ist. Er ist ausgeglichen (AVL-Baum; Adel’son-Vel’skii and Landis [1962]) wenn für jeden Knoten gilt, dass sich der linke und der rechte Teilbaum in ihren Höhen um maximal eins unterscheiden. Wäre der Baum in unserem Beispiel ausgeglichen, so hätte er eine Tiefe von drei. Das Suchen in diesem Baum wäre somit auf drei Vergleiche beschränkt. Ob ein Baum, nachdem er aufgebaut wurde, ausgeglichen ist oder nicht, hängt davon ab, in welcher Reihenfolge die Werte in den Baum eingefügt werden. Dies soll an der Eingabe der drei Zahlen 1, 2 und 3 zeigen. Man kann sie auf mehrere Arten eingeben. Eingabe: 1-2-3 Eingabe: 2-1-3 Eingabe: 3-1-2 2 1 1 2 3 3 1 2 3 Wie das erste Beispiel zeigt, erzeugt das Eingeben einer sortierten Liste einen extrem unausgeglichenen Baum, er hat dann nur linke oder nur rechte Kanten, der Baum wird im Extremfall zu einer Liste. copyleft:munz 124 Fachschulen Lörrach 6 Abstrakte Datentypen Java Es gibt nun Algorithmen, auf die wir nicht näher eingehen wollen, die es erlauben, aus einem nicht ausgeglichenen Baum einen ausgeglichenen zu machen. Der Baum aus unserem Beispiel könnte nach einem solchen Verfahren z. B. so aussehen: 7 2 1 0 12 3 9 8 5 13 10 15 Selbstverständlich lassen sich viele Arten von Daten in einen binären Suchbaum speichern, Voraussetzung allerdings ist, dass es in der Menge der einzutragenden Elemente eine Ordnungsrelation gibt, denn ohne Vergleich lässt sich nicht entscheiden, ob man beim Eintrag eines Wertes in einem linken oder einem rechten Teilbaum absteigen muss. Bei der Ausgabe der Werte, die in einem Baum gespeichert sind, unterscheidet man drei Arten. Pre-Order, In-Order und Post-Order. 6.6.3 Pre-Order-Strategie bei der Ausgabe eines binären Suchbaumes Die Ausgabe Pre-Order lässt sich, wie auch die anderen beiden am besten rekursiv formulieren. Bei der Strategie der Pre-Order-Ausgabe wird zunächst die Wurzel besucht und ihr Wert ausgegeben. Dann wird der linke Teilbaum mit der Strategie Pre-Order ausgegeben, danach der rechte Teilbaum, ebenfalls mit der Strategie Pre-Order. Pre-Order = Ausgabe Knoten Pre-Order (linker Teilbaum) Pre-Order (rechter Teilbaum) Die Pre-Order-Strategie wird z.B. bei Termauswertung benutzt. Aufgabe 39: Wie heißt die Liste der Zahlen, wenn der binäre Suchbaum nach Pre-Order ausgegeben wird? 6.6.4 In-Order-Strategie bei der Ausgabe eines binären Suchbaumes In diesem Fall wird die Wurzel erst ausgegeben, wenn der linke Teilbaum mit der Strategie In-Order ausgeben ist. Nach der Ausgabe des Wertes der Wurzel, erfolgt die Ausgabe des rechten Teilbaums mit der Strategie In-Order. In-Order = In-Order (linker Teilbaum) Ausgabe Knoten In-Order (rechter Teilbaum) Die In-Order-Strategie wird zum effizienten Sortieren benutzt. Aufgabe 40: Wie heißt die Liste der Zahlen, wenn der binäre Suchbaum nach der In-Order-Strategie ausgegeben wird? Es sollte sich zeigen, dass die Werte sortiert ausgegeben werden, übrigens unabhängig davon, wie der Baum aufgebaut wurde. 6.6.5 Post-Order-Strategie bei der Ausgabe eines binären Suchbaumes Bei diesem Verfahren wird zunächst der linke Teilbaum mit der Strategie Post-Order ausgegeben, und daran anschließend der rechte Teilbaum mit der gleichen Strategie. Danach erfolgt erst die Ausgabe des Wertes der Wurzel. copyleft:munz 125 Fachschulen Lörrach 6 Abstrakte Datentypen Java Post-Order = Post-Order (linker Teilbaum) Post-Order (rechter Teilbaum) Ausgabe Knoten Das Post-Order-Verfahren wird z.B. benutzt bei der Eingabe von Termen bei einigen Taschenrechnern (Post-Fix-Notation). 6.6.6 Ein binärer Suchbaum wird in Java implementiert Die Implementierung geht analog zur Implementierung der Liste LIFO bzw. Liste FIFO. Was in einer Liste ein Element war, ist in einem Baum ein Knoten, wir implementieren also zuerst eine Klasse Knoten. Quelltext von Knoten: package abstraktDT.baum; public class Knoten { protected Object wert; private Knoten links, rechts; } copyleft:munz public Knoten() { Object wert; links = rechts = null; } public Knoten(Object wert) { setWert(wert); links = rechts = null; } public void setWert(Object wert) { this.wert = wert; } public Object getWert() { return wert; } public void setLinks(Knoten links) { this.links = links; } public Knoten getLinks() { return links; } public void setRechts(Knoten rechts) { this.rechts = rechts; } public Knoten getRechts() { return rechts; } 126 Fachschulen Lörrach 6 Abstrakte Datentypen Java Die Klasse die man aus diesem Quelltext erzeugt kommt, wie die erste Zeile des Textes zeigt in ein Paket . Syntax und Funktionalität der implementierten Konstruktoren und Methoden kann man der Dokumentation entnehmen (Anmerkung: die Java-Dokumentationskommentare sind, um die Übersichtlichkeit zu gewährleisten aus dem obigen Quelltext entfernt worden. Der Quelltext der abstrakten Klasse BinBaum: package abstraktDT.baum; public abstract class BinBaum { public Knoten wurzel; public void inOrder() { inOrder(wurzel); } private void inOrder(Knoten knoten){ if (knoten!= null){ inOrder(knoten.getLinks()); System.out.print(knoten.getWert()+“, inOrder(knoten.getRechts()); } } public void preOrder() { preOrder(wurzel); } private void preOrder(Knoten knoten){ if (knoten!= null){ System.out.print(knoten.getWert()+“, preOrder(knoten.getLinks()); preOrder(knoten.getRechts()); } } public void postOrder() { postOrder(wurzel); } copyleft:munz 127 „); „); Fachschulen Lörrach 6 Abstrakte Datentypen } Java private void postOrder(Knoten knoten){ if (knoten!= null){ postOrder(knoten.getLinks()); postOrder(knoten.getRechts()); System.out.print(knoten.getWert()+“, „); } } public abstract void fuegeInBaum(Object wert); Die abstrakte Klasse BinBaum ist der abstrakten Klasse Liste nachempfunden. Sie implementiert die drei besprochenen rekursiven Ausgabemöglichkeiten des Baumes, während das Eintragen eines Wertes in den Baum abstrakt und damit einem „Erben“ vorbehalten bleibt. Der Quelltext von BinSuchBaum: package abstraktDT.baum; public class BinSuchBaum extends BinBaum { public void fuegeInBaum(Object wert){ wurzel = fuegeInBaum(wurzel, wert); } private Knoten fuegeInBaum(Knoten aktuell, Object wert){ if(aktuell == null) aktuell = new Knoten(wert); else{ if (wert.toString().compareTo (aktuell.getWert().toString())<0){ aktuell.setLinks(fuegeInBaum(aktuell.getLinks(),wert)); } else aktuell.setRechts(fuegeInBaum( aktuell.getRechts(),wert)); } return aktuell; } } copyleft:munz 128 Fachschulen Lörrach 6 Abstrakte Datentypen Java Aufgabe 41: Erstellen Sie eine Liste von zehn Namen, fügen Sie die Werte in einem „Schreibtischtest“ in einen binären Suchbaum ein. Notieren Sie die Ausgaben wenn sie die Strategien Pre-Order, In-Order und Post-Order verwenden. Aufgabe 42: Implementieren Sie ein Testprogramm, das die Ausgaben der vorangehenden Aufgabe überprüft. copyleft:munz 129 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7 Java-Server im Intranet und Internet 7.1 Java Direkte Kommunikation zweier Rechner über Sockets (TCP/IP) Damit die Verständigung zwischen verschiedenen Rechnern klappt, müssen bestimmte Regeln eingehalten werden. Für die Verständigung im Internet sind solche Regeln in den Protokollen TCP und IP festgehalten. Auch Rechner in lokalen Netzen können damit kommunizieren. Dazu muss man die IPAdresse der beiden Rechner kennen und einen Port, der für diese Art der Kommunikation benutzt wird. Einige Portnummern sind für bestimmte Anwendungen reserviert, wie zum Beispiel die Nummer 21 für FTP, 23 für Telnet, 80 für http, 110 für POP. Wir wählen für unsere Zwecke vorsichtshalber den Port mit einer Nummer über 1024. Mit Rechneradresse und Rechnerport haben wir gewissermaßen eine „Steckdose“ für unsere Netzwerkverbindung, den Socket. Zunächst stellen wir die Netzadresse unseres Rechners und die des Nachbarrechners fest. Dafür gibt es das Programm ipconfig das wir auf der Betriebssystemebene DOS aufrufen können. Unter Windows gibt es die Möglichkeit, mit Start - Ausführen – WINIPCFG anzuklicken, oder Start Einstellungen - Systemsteuerung - Netzwerk - Identifikation - Computername. Man kann aber auch auf einem Rechner mit sich selbst kommunizieren. Man muss die Anwendung oder die Entwicklungsumgebung mit der Anwendung dann einfach zweimal starten. Als Adresse verwendet man dann: „127.0.0.1“ oder „localhost“. Der folgende Java- Kommunikationsserver besteht aus den Klassen KServer1, Empfaenger und Sender. Im Konstruktor von Kserver1 wird die Benutzeroberfläche gestaltet. Sie hat einen Texteinund einen Textausgabebereich, sowie ein Textfeld für die Eingabe einer Rechneradresse. Bei Anklicken des Absendebuttons wird der in das Textausgabefeld geschriebene Text von einem Objekt der Klasse Sender an den Rechner mit der angegebenen Adresse gesandt. Ein Objekt der Klasse Empfaenger lauscht in einer endlosen While-Schleife an Port 6000 und zeigt den Absender und die ankommende Textzeile im Textfenster an. Zwei Rechner im lokalen Netzwerk können so kommunizieren, wenn die richtige Rechneradresse jeweils eingetragen wird. import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; public class Kserver1 extends Frame implements ActionListener { TextArea textEin = new TextArea(); TextArea textAus = new TextArea(); TextField rechnerAdresse= new TextField("127.0.0.1"); Empfaenger meinEmpfaenger=new Empfaenger(); Sender meinSender=new Sender(); public Kserver1() { super("Kommunikation über Sockets"); setLayout(new FlowLayout()); setSize(500,550); copyleft:munz 130 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Label e = new Label("Eingang:"); add(e); add(textEin); textEin.setEditable(false); Label a = new Label("Ausgang:"); add(a); add(textAus); add(rechnerAdresse); Button b = new Button("Absenden"); add(b); b.addActionListener(this); setVisible(true); addWindowListener(new FensterSchliesser()); meinEmpfaenger.lausche(); } public void actionPerformed(ActionEvent e) { meinSender.sende(); } public static void main (String[] args) { new Kserver1(); } class Empfaenger { public void lausche() { String absender, sEin; try { ServerSocket verbindung=new ServerSocket(6000); Socket lauschen; while (true) { lauschen=verbindung.accept(); absender=lauschen.getInetAddress().getHostAddress(); InputStreamReader portLeser; portLeser = new InputStreamReader(lauschen.getInputStream()); BufferedReader eingabe=new BufferedReader(portLeser); sEin=eingabe.readLine(); textEin.append(absender + ": " + sEin + "\n"); } } catch (Exception e) {} } } class Sender { public void sende() { try { Socket zumNachbar = new Socket(rechnerAdresse.getText(),6000); PrintWriter ausg = new PrintWriter(zumNachbar.getOutputStream(),true); ausg.println(textAus.getText()); ausg.close(); textAus.setText(""); } catch (Exception e) {} } } class FensterSchliesser extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); }} } Will man sich mit einem Partner länger unterhalten, oder viele Daten übertragen, so hat obiges Programm den Nachteil, dass die Verbindung über den Port längere Zeit belegt ist. Um mehrere Partner nahezu gleichzeitig bedienen zu können, kann man jede neu eingehende Verbindung in einen eigenständigen, nebenläufigen Prozess (Thread) auslagern. copyleft:munz 131 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Das nächste Programm lauscht also wieder in einer Endlosschleife an Port 6000. Wenn eine Verbindung erlauscht wurde, wird das Socket-Objekt, das die Daten der Verbindung (Rechneradresse und Port) enthält, sofort an einen unabhängigen Thread weitergegeben. Es können dann neue Anfragen akzeptiert werden. In obigem Programm wird vom Konstruktor anstatt meinEmpfaenger.lausche die Methode verbinde aufgerufen: public void verbinde() { ServerSocket verbindung; try { verbindung=new ServerSocket(6000); while (true) { Socket lauschen=verbindung.accept(); Empfaenger meinEmpfaenger=new Empfaenger(lauschen); meinEmpfaenger.start(); } } catch (Exception e) { }; } Der Empfänger kann dann so aussehen: class Empfaenger extends Thread{ private Socket erlauscht; public Empfaenger(Socket lauschen){ erlauscht=lauschen; } public void run() { String sEin=""; try { String absender=erlauscht.getInetAddress().getHostAddress(); InputStreamReader portLeser; portLeser=new InputStreamReader(erlauscht.getInputStream()); BufferedReader eingabe=new BufferedReader(portLeser); sEin=eingabe.readLine(); while (sEin!=null) { textEin.append(absender+": "+sEin+"\n"); sEin=eingabe.readLine(); } eingabe.close(); } catch (Exception e) {} } } Das vollständige Programm Kserver2 befindet sich im Anhang und auf der CD. copyleft:munz 132 Fachschulen Lörrach 7 7.2 Java-Server im Intranet und Internet Java Client-Server-Kommunikation über das Common Gateway Interface (CGI) Das Common Gateway Interface (CGI) regelt die Kommunikation zwischen einem Web-Browser und einem Server-Programm. Zunächst eine Webseite, die außer einem Absende-Button noch keine weiteren Eigabemöglichkeiten enthält: <HTML> <HEAD> <TITLE> WebClient.html </TITLE> </HEAD> <BODY> Hallo Server! <BR> <FORM METHOD=GET ACTION="http://localhost:5000"> <INPUT TYPE="SUBMIT"> </FORM> </BODY> </HTML> Der Java-Server auf dem selben Rechner lauscht an Port 5000 und schickt eine Antwort zurück. Dabei muss das Protokoll HTTP und der Inhaltstyp HTML angegeben werden, gefolgt von einer Leerzeile ( \n bedeutet new line ): import java.net.*; import java.io.*; public class CGIServer0{ public CGIServer0() { try { ServerSocket anschluss = new ServerSocket(5000); while (true) { Socket lausch=anschluss.accept(); PrintWriter aus=newPrintWriter(lausch.getOutputStream(),true); aus.println("HTTP/1.0 200 OK\nContent-type: text/html\n\n"); aus.println("<HTML><BODY><FONT SIZE=8>Hallo Client!<BR>"); aus.println("<FONT COLOR=RED>Hier CGIServer0.</HTML>"); System.out.println("OK!"); aus.close(); } } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) { new CGIServer0(); } } Man kann auf einer HTML-Seite auch Daten eingeben und an den Server schicken. Hier eine Webseite mit Eingabeformular für eine E-Mail-Adressliste: <HTML> <HEAD><TITLE> WebClient1.html </TITLE></HEAD> <BODY> <H3> Adressliste</H3> Bitte beantworten Sie folgende Fragen zu Ihrer Person: <BR> <FORM METHOD=GET ACTION="http://localhost:6000"> Vorname: <INPUT TYPE="TEXT" NAME="vorname"> <BR> Nachname: <INPUT TYPE="TEXT" NAME="nachname"> <BR> E- Mail- Adresse: <INPUT TYPE=text NAME=eMail SIZE=60> <P> <INPUT TYPE=reset> <INPUT TYPE=submit> </FORM> </BODY> </HTML> copyleft:munz 133 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Wir nehmen unser Server-Programm Kserver2 vom letzten Kapitel, um zu untersuchen, in welcher Form die Daten beim Server ankommen: Das Java-CGI-Programm empfängt die Daten vom Client. Sie sind, wie man sieht, in einer Textzeile enthalten und bestehen jeweils aus dem Paar: Name und Inhalt der Variablen vorname, nachname und eMail. Wir ordnen sie einem Objekt der Klasse Kunde zu: Kunde meinKunde = new Kunde(); Unsere Klasse Kunde enthält eine Methode entziffern zum Entnehmen der Daten aus dem eingehenden String sEin, eine Methode benachrichtigen zum Benachrichtigen des Kunden, dass seine Daten angekommen sind und später eine Methode speichern zum Speichern der Daten in einer verfügbaren Datenbank. Diese Methoden werden in der Run-Methode des Empfängers aufgerufen: meinKunde.entziffern(); meinKunde.benachrichtigen(); copyleft:munz 134 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java class Kunde{ String vorname, nachname, eMail; void entziffern() { vorname=""; nachname=""; eMail=""; int i=0; char ch=sEin.charAt(i); while (ch != '=') { i=i+1; ch=sEin.charAt(i); } while (ch != '&') { vorname=vorname+ch; i=i+1; ch=sEin.charAt(i); } while (ch != '=') { i=i+1; ch=sEin.charAt(i); } while (ch != '&') { nachname=nachname+ch; i=i+1; ch=sEin.charAt(i); } while (ch != '=') { i=i+1; ch=sEin.charAt(i); } while (ch != 'H') { eMail=eMail+ch; i=i+1; ch=sEin.charAt(i); } } void benachrichtigen() { meinSender.sende( "HTTP/1.0 200 OK\nContent-type: text/html\n\n" ); meinSender.sende("Vorname" + vorname + "<BR>"); meinSender.sende("Nachname" + nachname + "<BR>"); meinSender.sende("Ihre E-Mail-Adresse" + eMail+ "<BR>\n"); } Die Methode entziffern ist hier ganz auf die obige HTML-Seite des Client abgestimmt. In dem Programm CGIServer ist die Methode entziffern allgemeiner verwendbar. Sie liest die Namen der ankommenden Variablen und ihre Werte in ein Array. Das Programm CGIServer befindet sich im Anhang. Aufgabe 43: Ergänzen Sie die HTML-Seite (Client) durch Radio-Buttons und Auswahlmenüs und untersuchen Sie, in welcher Form die Daten dann an den Server übermittelt werden. copyleft:munz 135 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3 Java Datenbanken Wir wollen nun die ankommenden Daten in einer Datenbank-Tabelle speichern. Wir müssen also zuerst eine (leere) Datenbank erstellen: Eine Datenbank besteht, vereinfacht ausgedrückt, aus Tabellen, die zueinander in Beziehung stehen können. Auf eine Datenbank wollen in der Regel mehrere Nutzer zugreifen. Das geschieht im lokalen Netzwerk oder im Internet. Wir beschreiben hier den Zugriff eines Java-Programms auf eine lokale Microsoft-Access-Datenbank im Netz oder auf dem eigenen Rechner, sowie den Zugriff auf eine MySQL-Datenbank im Internet. Auf Internet-Servern hat das Datenbank-Management-System MySQL weite Verbreitung gefunden. Ein Hauptgrund ist seine Schnelligkeit bei gleichzeitigem Zugriff vieler Nutzer. MySQL ist kostenlos erhältlich, es wird bei der Installation von Linux gleich mit installiert. Anleitungen und Tools sind im Internet verfügbar. Es gibt inzwischen MySQL auch kostenlos für Windows, so dass man damit auch auf dem eigenen Rechner oder im Schulnetz arbeiten kann. Zum Erstellen einer neuen leeren MySQLDatenbank kann man ein Tool (z. B. MySQLWinAdmn.exe) benutzen, oder im Fenster der MSDOSEingabeaufforderung mysql aufrufen und direkt Kommandozeilen ergeben. 7.3.1 Erstellen einer lokalen Access-Datenbank Nach dem Starten von Access geben wir der Datenbank zunächst einen Namen, die Endung *.mdb wird automatisch hinzugefügt. Das erscheinende Datenbankfenster zeigt die Datenbank-Objekte wie zum Beispiel die Tabellen. Hier eine Tabelle in der Datenblattansicht: copyleft:munz 136 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Zum Erstellen einer neuen Tabelle können wir die Entwurfsansicht wählen: Mit Hilfe des Tabellenassistenten geben wir die Überschriften der einzelnen Tabellenspalten (Feldnamen) und ihren Typ (Felddatentyp) ein. Für den Felddatentyp wird eine Auswahl vorgeschlagen. Wir wählen den Typ Text und geben die maximale Länge an. Man kann Tabellen auch aus dem Tabellenkalkulationsprogramm Excel importieren: Einfügen Tabelle Tabelle importieren. copyleft:munz 137 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.2 Java Zugriff auf eine Access-Datenbank ermöglichen Wir wollen nun den Zugriff von außen auf eine existierende Microsoft-Access-Datenbank (als Datenquelle) ermöglichen. Den Zugang zu verschiedenen Datenquellen regelt unter Windows die Open Database Connectivity (ODBC). Wir benutzen den Datenquellenadministrator, um diesen Zugang einzurichten. Er findet sich in der Windows-Systemsteuerung. Mit ihm fügen wir die Datenquelle mit System-Datenquellen-Namen (DSN) und den passenden Access-Treiber hinzu: Start - Einstellungen - Systemsteuerung (Verwaltung) ODBC-Datenquelle 32 Bit (Datenquellenadministrator) System-DNS - Hinzufügen Microsoft Access-Treiber - Fertig stellen Datenquellenname eingeben Datenbank - Auswählen copyleft:munz 138 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.3 Java Erstellen einer MySQL- Datenbank MySQL kopiert man am Besten in ein Verzeichnis auf der Festplatte. Im MySQL-Unterverzeichnis Docs finden sich Anleitungen. Unter Windows NT odere Windows 2000 lässt sich der MySQL-Dämon beispielsweise so starten: Man öffnet ein MS-DOS-Fenster, wechselt in das Unterverzeichnis bin und gibt ein: mysqld-nt --standalone Der MySQL-Server läuft nun im Hintergrund. In einem weiteren DOS-Fenster ruft man mysql auf. Eine neue Datenbank test2 erzeugt man mit create database test2; Die bestehenden Datenbanken werden angezeigt mit: show databases; Die Datenbank test ist schon vorgegeben. Im folgenden Bild wird gezeigt, wie man eine Tabelle in der Datenbank test erzeugt: Es gibt auch Tools zum Administrieren von MySQL-Datenbanken unter Windows, wie z. B. MySQLWinAdmn.exe. (Unter Linux wird häufig das Programm phpMyAdmin benutzt, für das zuvor PHP installiert sein muss.) Das folgende Bild zeigt die Verwaltung der Tabelle kursliste in der Datenbank test mit Hilfe von MySQLWinAdmn: copyleft:munz 139 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.4 Java Datenbank-Abfragesprache SQL SQL (Structured Query Language) ist die standardisierte Abfragesprache, die man benutzt, um Informationen aus einer Datenbank abzufragen. Sie wurde vereinbart, um unabhängig vom Hersteller der Datenbank zu sein. Wir verwenden als Beispiele nur wenige Vokabeln von SQL, wie insert und select und verweisen auf die Literatur, bzw. das Internet. select bewirkt eine Selektion (Auswahl) von Zeilen (Datensätzen) einer Tabelle: SELECT * FROM weine; Hier wird alles aus der Tabelle der Weine ausgewählt. Das Sternchen * ist ein Joker für alles. SELECT * FROM weine WHERE nummer = 8; Hier wird die Zeile ausgewählt, deren Nummer gleich 8 ist. Mit insert fügen wir Datensätze in die Tabelle ein: INSERT INTO kursliste (vorname, nachname, email) VALUES ("Reinhold", "Ley", "[email protected]"); Unsere Tabelle heißt hier Klassenliste und hat Spalten mit den Namen Vorname, Nachname und Email. copyleft:munz 140 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.5 Java Zugriff auf eine Datenbank mit Java - Die Java Database Conectivity (JDBC) JDBC ist die Schnittstelle zur Anbindung von Datenbanken an Java-Programme. Wir beschreiben den Zugriff in fünf Schritten: Als Erstes ist ein Treiber für die Datenbank anzugeben: 1 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Hier ist ein Treiber für Access-Datenbanken angegeben. Die Open Database Connectivity (ODBC) regelt die Kommunikation von MSWindows mit verschiedenen Datenbanksystemen. Eine JDBC:ODBC-Brücke ermöglicht die Verbindung der Java- mit der MSWindows- Welt. Im zweiten Schritt rufen wir die Methode getConnection des DriverManagers auf und geben die Brücke, die URL zur Datenbank und den Datenbanknamen an. Hierbei ist auch eventuell ein LoginName und ein Passwort erforderlich. Im folgenden Beispiel befindet sich eine Access-Datenbank mit dem Namen Test1 auf dem lokalen Rechner, es ist kein Login-Name und kein Passwort erforderlich: String urlDB="jdbc:odbc:Test1"; 2 Connection con=java.sql.DriverManager.getConnection(urlDB,"",""); Diese Zeilen sind zu ändern, wenn auf eine andere Datenbank zugegriffen werden soll. Ein Treiber für MySQL-Datenbanken wäre: org.gjt.mm.mysql.Driver Der Zugriff auf eine MySQL-Datenbank geht dann so: String treiber="org.gjt.mm.mysql.Driver"; String urlDB="jdbc:mysql://localhost:3306/test"; String user="nobody"; String psst=null; Connection con=DriverManager.getConnection(urlDB,user,psst); Im dritten Schritt wird die Methode createStatement unserer Verbindung aufgerufen. 3 Statement s = con.createStatement(); Das Statement-Objekt erhält als Eingabe den Abfragetext. Der Abfragetext entstammt dem Vokabular der Abfragesprache SQL: INSERT INTO kursliste (vorname) VALUES ("Claus"); Mit der Methode execute wird im vierten Schritt dieser SQL-Befehl des Statements ausgeführt: s.execute(eintrag); 4 Bei einer Abfrage befindet sich das Ergebnis dann im ResultSet-Objekt, es ist in der Regel eine Tabelle. 5 ResultSet resultat=s.getResultSet(); Das ResultSet-Objekt verfügt über die Methode next mit der wir immer zur nächsten Tabellenzeile gelangen. Mit getString, bzw. getDouble erhalten wir die einzelnen Zellen, die durch den Spaltennamen bestimmt sind: resultat.getString("vorname") copyleft:munz 141 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.6 Java Datensatz in eine Datenbanktabelle einfügen Server- Daten- Programm bank Eine erste Version unseres Java-Programms zum Datenbank-Eintrag schreibt einen explizit angegebenen Namen in die Tabelle Klassenliste der Access-Datenbank Adressen: import java.sql.*; public class Eintrag1{ String treiber="sun.jdbc.odbc.JdbcOdbcDriver"; String urlDB="jdbc:odbc:Adressen"; String user=""; String psst=""; public Eintrag1() { try { Class.forName(treiber); Connection con=java.sql.DriverManager.getConnection(urlDB,"",""); Statement s=con.createStatement(); String eintrag="INSERT INTO Klassenliste (Vorname, Nachname) VALUES ('Alexander','Popp');"; s.execute(eintrag); con.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) { new Eintrag1(); } } Man beachte, dass im String eintrag noch Strings enthalten sind. Mit unseren gewonnenen Kenntnissen können wir nun das Programm CGIServer vom vorherigen Kapitel ergänzen und die im HTML-Formular eingegebenen Daten der Kunden in der AccessDatenbank Adressen speichern lassen. Client: Server- Daten- HTML-Seite Programm bank Dazu fügen wir der Klasse Kunde die folgende Methode speichern hinzu. copyleft:munz 142 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java void speichern() { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); String urlDB="jdbc:odbc:Adressen"; Connection con = java.sql.DriverManager.getConnection(urlDB,"",""); Statement s = con.createStatement(); String eintrag; eintrag="insert into Klassenliste("+varName[0]; for (int i=1; i<n; i=i+1) eintrag=eintrag+","+varName[i]; eintrag=eintrag+")values('"+varWert[0]+"'"; for (int i=1; i<n; i=i+1) eintrag=eintrag+",'"+varWert[i]+"'"; eintrag=eintrag+")"; s.execute(eintrag); con.close(); meinSender.sende("alles klar!\n"); } catch (Exception e) { System.out.println(""+e.getMessage()); } } Das vollständige Programm mit dem Namen AccessServer befindet sich im Anhang. 7.3.7 Der umgekehrte Weg: Holen von Daten aus einer Datenbanktabelle Das folgende Java-Programm nimmt Verbindung zu einer lokalen Datenbank mit dem Namen „db1“ auf. In der Tabelle mit dem Namen „rotweine“ sind Informationen über gelagerte Rotweine gespeichert. Eine Tabellenspalte enthält die Namen und eine Tabellenspalte den Preis des Weins. Es werden die Weine ausgewählt, deren Preis unter 20 liegt. Die Ausgabe erfolgt hier über die Standardausgabe: import java.sql.*; public class Anfrage { public static void main(String[] args) { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 1 String urlDB="jdbc:odbc:db1"; 2 Connection con = java.sql.DriverManager.getConnection(urlDB,"",""); 3 Statement s = con.createStatement(); String frage = "SELECT * FROM rotweine WHERE Preis<20"; s.execute(frage); 4 ResultSet resultat=s.getResultSet(); 5 while (resultat.next()) { System.out.print(resultat.getString("Name") + "\t"); System.out.println(resultat.getDouble("Preis")); } con.close(); } catch (Exception e) { System.out.println(""+e.getMessage()); } } } copyleft:munz 143 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.3.8 Java Ausgabe der Daten auf einer grafischen Oberfläche import java.awt.*; import java.awt.event.*; import java.sql.*; public class Anfrage3 extends Frame implements ActionListener{ Label l = new Label("Suchen in db1"); TextArea textAus = new TextArea("",10,60); Button b = new Button("Los!"); public Anfrage3() { setLayout(new FlowLayout()); add(l); add(b); b.addActionListener(this); add(textAus); setSize(500,300); setVisible(true); addWindowListener(new FensterLauscher()); } public void actionPerformed(ActionEvent e) { Connection con = null; try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); String urlDB="jdbc:odbc:db1"; con = java.sql.DriverManager.getConnection(urlDB,"",""); Statement s=con.createStatement(); String frage = "SELECT * FROM rotweine WHERE Preis<20"; s.execute(frage); ResultSet resultat=s.getResultSet(); String n; double p; while (resultat.next()) { n=resultat.getString("Name"); textAus.append(n+"\t"); p=resultat.getDouble("Preis");textAus.append(""+p+"\n"); } con.close(); } catch (Exception ex) { textAus.append(""+ex.getMessage());} } class FensterLauscher extends WindowAdapter { public void windowClosing(WindowEvent e) {System.exit(0);}} public static void main(String[] args) { new Anfrage3(); } }} 7.3.9 Ausgabe im Browser des Client, der Java-Server als Middleware Auf eine Datenbank greifen unterschiedliche Nutzer zu. Sie haben auch unterschiedliche Zugriffsrechte. Der Administrator hat Zugriffsrechte, die ihm erlauben, eine Datenbank neu zu schaffen, oder zu erweitern. Der Besitzer der Daten fügt neue Daten hinzu, z. B. über ein Warenlager. Der Kunde will sich über die Schätze in der Datenbank informieren und darin nach bestimmten Kriterien suchen. Die Datenbankverbindung ist eventuell lange geöffnet und störungsanfällig. Man schaltet deshalb noch eine Ebene auf dem Server dazwischen. Zwischen Datenbank und Client befindet sich die sogenannte Middleware. Die Middleware reicht die Kundenanfrage mit Hilfe eines geeigneten Treiber-Zwischenstücks an die Datenbank weiter und bereitet die Antwort von dort in eine kundengefälligere Form auf. Client: Server- Daten- HTML-Seite Programm bank copyleft:munz 144 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Unser HTML-Client sieht in einer einfachen Form so aus: <HTML> <HEAD> <TITLE> WebClient2.html </TITLE> </HEAD> <BODY> Sie erhalten die Liste der Weine aus unserem Lagers, die mehr als 20 Euro kosten: <BR> <FORM METHOD=GET ACTION="http://localhost:5000"> <INPUT TYPE="SUBMIT"> </FORM> </BODY> </HTML> Das folgende Java-Programm sendet die Liste der in der Datenbank gefundenen Weine an den Browser des Client. Dabei muss zur Verständigung das HTTP-Protokoll beachtet werden. Das heißt, es wird eine Zeile vorangestellt, in der die Version des Protokolls und der Inhaltstyp angegeben werden, gefolgt von einer Leerzeile. Erst danach kommen die Daten: import java.net.*; import java.io.*; import java.sql.*; public class AccessWeinServer{ String treiber="sun.jdbc.odbc.JdbcOdbcDriver"; String urlDB="jdbc:odbc:WeinDB"; String user=""; String psst=""; public AccessWeinServer () { try { ServerSocket anschluss = new ServerSocket(5000); Socket lausch=anschluss.accept(); PrintWriter ausgabe = new PrintWriter(lausch.getOutputStream(),true); ausgabe.println( "HTTP/1.0 200 OK\nContent-type:text/html\n\n"); Class.forName(treiber); Connection con; con=java.sql.DriverManager.getConnection(urlDB,user,psst); Statement s= con.createStatement(); String frage = "SELECT Name,Preis FROM rotweine WHERE Preis>20"; s.execute(frage); ResultSet resultat=s.getResultSet(); while (resultat.next()) { String name=resultat.getString("Name"); String preis=resultat.getString("Preis"); ausgabe.println(name + " " + preis + "<br>"); } ausgabe.close(); } catch (Exception e) { e.printStackTrace(); } } public static void main (String[] args) { new AccessWeinServer(); } } Aufgabe 44: Erstellen Sie im Schulnetz eine Datenbank mit einer Tabelle der Adressen und Telefonnummern der Schülerinnen und Schüler deiner Klasse. Gestalten Sie eine grafische Oberfläche für die Eingabe der Daten. Schreiben Sie ein Java-Programm für den Eintrag in die Datenbank. Aufgabe 45: Gestalten Sie eine Oberfläche für die Abfrage der Adressdatenbank, so dass unter unterschiedlichen Gesichtspunkten gesucht werden kann. Schreiben Sie ein Java-Programm für die Suche in der Datenbank. Das Java-Programm soll die Ergebnisse in einer übersichtlichen Form wieder im Browser präsentieren. copyleft:munz 145 Fachschulen Lörrach 7 7.4 Java-Server im Intranet und Internet Java Servlets Servlet-Umgebung Tomcat einrichten: Ein Java-Servlet entspricht etwa einem Java-Applet, aber es befindet sich auf dem Server und hat keine eigene grafische Benutzerschnittstelle. Es kommuniziert mit einem Internet-Browser, dem Client. Im Arbeitsspeicher des Webservers können viele Servletinstanzen nebeneinander leben. Servlets sind also ideal, wenn gleichzeitig mehrere Anfragen aus dem WWW eintreffen. Man kann Webspace mit der Servlet- Engine Tomcat auch mieten. Zum Entwickeln und Testen von Servlets auf dem PC eignet sich die gleiche Servlet-Umgebung, die auch auf Webservern benutzt wird: Tomcat. Tomcat gibt es auch für Windows. Nach dem Download entsteht beim Auspacken folgende Verzeichnisstruktur: Im abgebildete Beispiel wurde das Installationsverzeichnis Tomcat genannt. Zu den Unterverzeichnissen: bin\ Hier befinden sich die Programme zum Starten und beenden von Tomcat: startup.bat und shutdown.bat. In doc\ Hier stehen Anleitungen bereit. In lib\ sind Klassen untergebracht, die benötigt werden. webapps\examples\servlets\ Hier sind HTML-Seiten, mit denen man Beispiel- Servlets aufrufen kann. WEB-INF\classes\ Hier wurden Servlets untergebracht, und zwar fertig kompilierte Servlets, also die ausführbaren class- Dateien. Um den Java- Quellcode von selbstgeschriebenen Servlets zu kompilieren benötigt man das Java Developers Kit, das früher schon installiert wurde und Servlet- Klassen des Java- Archivs servlet.jar, das sich im Unterverzeichnis lib\common befindet. Bei der Installation des JDK wurde eventuell früher ein Pfad auf das Unterverzeichnis bin und ein Klassenpfad auf das Unterverzeichnis lib gesetzt, beispielsweise durch einen Eintrag in der Datei Autoexec.bat: SET PATH=C:\jdk\bin; SET CLASSPATH=C:\jdk\lib; Nun ist dies zu ergänzen durch SET CLASSPATH=C:\Tomcat\lib\common; Man kann stattdessen auch die Umgebungsvariablen JAVA_HOME und TOMCAT_HOME in der Windows- Systemsteuerung eintragen. Das geht mit copyleft:munz 146 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Start - Einstellungen - Systemsteuerung - System - Erweitert - Umgebungsvariablen - Systemvariable bearbeiten - CLASSPATH Auch bei Benutzung einer Java- Entwicklungsumgebung trägt man unter Optionen den Klassenpfad ein. Im Java-Editor ist dies der Menupunkt Fenster - Konfiguration: copyleft:munz 147 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java Bevor die Servlets getestet werden können, muss zuerst Tomcat mit startup.bat gestartet werden. Man prüft, ob die Installation korrekt war, indem man die mitgelieferten Beispiel- Servlets laufen lässt. Dazu kann man im Browser direkt eingeben: http://localhost:8080/ Es erscheint die Startseite von Tomcat mit einer Auswahl von Servlets, die man anklicken kann. Einzelne Servlets ruft man von einer HTML- Seite aus auf, oder direkt im Browser, indem man z. B. eingibt: http://localhost:8080/examples/servlet/HelloWorldExample Hier ist also der Rechnername anzugeben, der Port, der Aliasname servlet und der Servletname. Fehlerfreie Servlets überträgt man dann mit einem FTP- Programm wie z. B. WS_FTP auf einen Internet-Host. Auch dort sind sie im Verzeichnis WEB-INF/classes unterzubringen. Ein Java- Treiber für MySQL heißt mm.mysql-2.0.2-bin.jar und wird in der Klassen-Library untergebracht. HTML-Client: Der Aufruf eines Servlets aus einer HTML-Seite sieht etwa so aus: <html> <body> <form method=GET action=http://localhost:8080/servlet/ServletEins> <input type=submit> </form> </body> </html> Obiges HTML-Formular enthält noch keine Eingaben. Servlet Eins: Das Servlet ServletEins antwortet mit Text. Der Inhaltstyp der Antwort ist text/html, deshalb gibt unser PrintWriter Hypertext mit Tags zur Formatierung aus: import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ServletEins extends HttpServlet { public void doGet( HttpServletRequest anfrage, HttpServletResponse antwort) throws ServletException, IOException { antwort.setContentType("text/html"); PrintWriter ausgabe = antwort.getWriter(); ausgabe.println("<BODY> <FONT SIZE=7>"); ausgabe.println("Hallo Client, hier spricht ServletEins!"); ausgabe.println("</FONT> </BODY>"); ausgabe.close(); } } Bei der Methode GET werden die Daten als Parameter an die übermittelte URL mit Fragezeichen angehängt und jeder kann sie lesen. Auch ist die Anzahl der Zeichen beschränkt auf 255. Bei geheimen Daten oder wenn die Datenmenge größer ist, sollte man die Methode POST verwenden. 7.4.1 HTML-Client mit Eingabe <HTML> <BODY> <FORM METHOD=POST ACTION="http://localhost:8080/servlet/ServletEins"> Name:<br> <input type="Text" name="Name" size="30"><br> Vorname:<br> <input type="Text" name="Vorname" size="30"><br> EMail:<br> <input type="Text" name="EMail" size="50"><br> <INPUT TYPE = SUBMIT VALUE = "Absenden"> </FORM> </BODY> </HTML> Wenn der Submit- Button gedrückt wird, werden die Namen und Inhalte der Variablen Vorname und Email an ServletZwei übermittelt. copyleft:munz 148 Fachschulen Lörrach 7 Java-Server im Intranet und Internet 7.4.2 Java Request und Response Das vom HTML- Formular angesprochene Servlet erhält die Namen und die Werte der eingegebenen Daten (Parameter) mit Hilfe eines HttpServletRequest-Objekts, das wir anfrage nennen: anfrage.getParameterNames(); Auf den Wert der Daten mit dem Namen "Vorname" greifen wir zu mit: String Wert = anfrage.getParameterValues("Vorname"); Wir speichern alle Daten als Aufzählung (Enumeration). Enumeration aus dem Paket java.util besitzt Methoden, die uns hier nützlich sind (siehe auch Dokumentation): hasMoreElements() liefert true, wenn die Aufzählung noch weitere Elemente hat. nextElement() liefert das nächste Objekt in der Aufzählung. Wir wandeln es in einen String um. Solange es noch ein nächstes Element gibt, werden im folgenden Servlet die Werte der Daten eingelesen mit getParameterValues(Name); antwort ist ein HttpServletResponse-Objekt. Es soll auf den Eingang der Daten antworten. Servlet Zwei: import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class ServletZwei extends HttpServlet { public void doPost( HttpServletRequest anfrage,HttpServletResponse antwort) throws ServletException, IOException { antwort.setContentType("text/html"); PrintWriter ausgabe = antwort.getWriter(); ausgabe.println("<HTML><BODY>"); ausgabe.println("<FONT SIZE = 6> Hallo Client."); Enumeration Namen=anfrage.getParameterNames(); while (Namen.hasMoreElements()) { String Name=(String)Namen.nextElement(); String Wert=anfrage.getParameterValues(Name)[0]; ausgabe.println(Name+" "+Wert); } ausgabe.println("</FONT></BODY></HTML>"); ausgabe.close(); } } Nach dem Test mit Tomcat übertragen wir die Servlets jeweils auf einen „Servlet-fähigen“ InternetHost. Im Verzeichnisbaum des Servers ist dafür eine spezielle Servlet-Zone vorgesehen. 7.4.3 Servlet mit MySQL-Datenbankanbindung Es müssen 3 Dinge geprüft werden: • Angabe des Datenbanktreiber: "org.gjt.mm.mysql.Driver" • URL zu Datenbank, gegebenenfalls einschließlich Pfad und Portnummer, z. B.: "jdbc:mysql://localhost:3306/test" • Username und Passwort Außerdem muss auf der HTML-Seite der Servlet-Aufruf angepasst werden. Lokal lautet er z. B.: "http://localhost:8080/examples/servlet/ServletDreiA" oder auf einem Internet-Host möglicherweise "http://softley.de/servlet/ServletDreiA" Servlet Drei: ServletDreiA empfängt die eingegebenen Daten vom Browser des Client: Enumeration Namen=request.getParameterNames(); und speichert sie mit String Eintrag="insert into kursliste ... s.execute(Eintrag); in die Tabelle kursliste der Datenbank test: copyleft:munz 149 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class ServletDreiA extends HttpServlet { String treiber = "org.gjt.mm.mysql.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String user = "nobody"; String psst = null; public void doPost(HttpServletRequest anfrage, HttpServletResponse antwort) throws ServletException, IOException{ Connection con = null; Statement s; String vorname=""; String nachname=""; String email=""; String ergebnis="OK"; vorname = anfrage.getParameter("vorname"); nachname = anfrage.getParameter("nachname"); email = anfrage.getParameter("email"); antwort.setContentType("text/html"); PrintWriter ausgabe = antwort.getWriter(); ausgabe.println("<BODY> <H1> E-Mail-Liste speichern:</H1>"); ausgabe.println(vorname+"<BR>"+nachname+"<BR>"+email); String eintrag="insert into kursliste (vorname, nachname, email) values('"+vorname+"','"+nachname+"','"+email+"')"; try { Class.forName("org.gjt.mm.mysql.Driver"); con=DriverManager.getConnection(url,user,psst); s=con.createStatement(); s.execute(eintrag); } catch (Exception e) {ergebnis = e.getMessage();} ausgabe.println("<BR>"+ergebnis+"</BODY> </HTML>"); ausgabe.close(); } } ServletDreiB holt alle Datensätze der Tabelle kursliste aus der Datenbank test: import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.sql.*; public class ServletDreiB extends HttpServlet { String url = "jdbc:mysql://localhost:3306/test"; String treiber = "org.gjt.mm.mysql.Driver"; String user = "nobody"; String psst = null; public void doGet (HttpServletRequest anfrage, HttpServletResponse antwort) throws ServletException, IOException { antwort.setContentType( "text/html" ); PrintWriter ausgabe = antwort.getWriter( ); // Ausgabe an Browser String titel = "kursliste"; ausgabe.println("<html><head><title>"+titel+"</title></head>" ); ausgabe.println("<body><H1>" + titel + "</H1>" ); ausgabe.println("<table border>"); String frageDB = "SELECT * FROM kursliste"; // SQL try { Class.forName(treiber); Connection con = DriverManager.getConnection(url, user, psst); Statement s = con.createStatement(); ResultSet resultat = s.executeQuery(frageDB); ResultSetMetaData resMeta = resultat.getMetaData(); int spalten = resMeta.getColumnCount(); ausgabe.println("<tr>"); for (int i=1; i<=spalten; i=i+1) { String spaltenname = resMeta.getColumnLabel(i); ausgabe.println("<th> " + spaltenname + " </th>"); } copyleft:munz 150 Fachschulen Lörrach 7 Java-Server im Intranet und Internet Java ausgabe.println("</tr>"); while (resultat.next()) { ausgabe.println("<tr>"); for (int i=1; i<=spalten; i=i+1) { String inhalt = resultat.getString(i); ausgabe.println("<td> " + inhalt + " </td>"); } ausgabe.println("</tr>"); } con.close(); } catch (Exception e) {ausgabe.println (e.getMessage()); } ausgabe.println("</table>"); ausgabe.println("</body></html>" ); ausgabe.close(); } } Aufgabe 46: Schreiben Sie ein Abfrage-Servlet, das bei Eingabe eines Namens die Adresse, Telefonnummer und E-Mail-Adresse deiner Mitschüler ausgibt. 7.4.4 Anfänge eines Online-Shops Java-Servlets sind für E-Commerce-Software-Lösungen ideal. Für Servlets muss nicht jedes mal bei einer Anfrage ein neues Programm gestartet werden. Wenn mehrere Kunden aus dem Internet gleichzeitig das Servlet aufrufen, werden im Hauptspeicher mehrere Objekte nebeneinander und unabhängig voneinander ins Leben gerufen. Ihr Platz-und Zeitbedarf ist der jeweiligen transportierten Datenmenge angepasst. Aufgabe 47: Programmieren Sie für einen einfachen Laden - ein Kunden-Servlet, - ein Waren-Servlet, - ein Such-Servlet, - ein Korb-Servlet. Das Kunden-Servlet: Ein Kunde besitzt Daten und diese sollten in der Datenbank gespeichert werden. Der Höflichkeit halber wird er davon auch benachrichtigt. Damit ist es naheliegend, eine Klasse Kunde zu schaffen. Das Speichern der Daten in der Datenbank geschieht genau wie früher beschrieben. Natürlich werden noch mehr Daten als Vorname und Nachname aufgenommen. Es ist praktisch, diese Daten mit den gleichen Variablennamen zu versehen, wie die Spalten in der entsprechenden Tabelle der Datenbank. Das Waren-Servlet: Beim Anlegen der Tabelle für die Waren sollte man sich auch überlegen, nach welchen Kriterien ein Kunde im Warenbestand suchen möchte. Das Suchergebnis trägt zur Kaufentscheidung bei. Es sollte deshalb übersichtlich in einer Tabelle dargestellt werden. Das Korb-Servlet: Das Korb-Servlet soll an einen Warenkorb erinnern. Man kann etwas hinzutun, man kann den Preis addieren, die Bestellung wird aufgenommen und gespeichert. copyleft:munz 151 Fachschulen Lörrach 8 Anhang 8 Anhang 8.1 Java Quelltexte zur den einfachen Grafik-Programmen Grundgerüst: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JGrafik extends JFrame { private GrafikPanel zeichenFlaeche; public JGrafik(String title) { super (title); addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); zeichenFlaeche = new GrafikPanel(300, 200); cp.add(zeichenFlaeche); pack(); setVisible(true); } public static void main (String[] args) { new JGrafik("Grafik-Beispiel"); } } Die Zeichenfläche wird von JPanel abgeleitet. Hier wird ein Rechteck gezeichnet: import java.awt.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); } public void paintComponent(Graphics g) { super.paintComponent(g); int dx = breite*2/3; int dy = hoehe*2/3; int x = (breite - dx)/2; int y = (hoehe - dy)/2; g.setColor(Color.WHITE); g.fillRect(x, y, dx, dy); g.setColor(Color.BLACK); g.drawRect(x, y, dx, dy); } copyleft:munz } 152 Fachschulen Lörrach 8 Anhang Java Die Zeichnung wird auf Knopfdruck verändert: import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JGrafik extends JFrame implements ActionListener { private GrafikPanel zeichenFlaeche; private JButton jbNeu; private Random rand; public JGrafik(String title) { // Frame-Initialisierung super (title); addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); rand = new Random(); jbNeu = new JButton("neu zeichnen"); jbNeu.addActionListener(this); cp.add(jbNeu, BorderLayout.NORTH); zeichenFlaeche = new GrafikPanel(300, 200); cp.add(zeichenFlaeche); pack(); setVisible(true); } } public void actionPerformed(ActionEvent e) { if (jbNeu == e.getSource()) { zeichenFlaeche.setFarbe( new Color(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)) ); zeichenFlaeche.zeichneGrafik(); } } public static void main (String[] args) { new JGrafik("Grafik Beispiel"); } Die Grafik wird in einer Image-Komponente zwischengespeichert: import java.awt.*; import java.awt.image.*; import java.awt.event.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; private int x, y; private Color farbe; Die Image-Komponente: private BufferedImage image; copyleft:munz 153 Fachschulen Lörrach 8 Anhang Java Der Grafik-Kontext der Image-Komponente: private Graphics2D g2D; Der Konstruktor: public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); farbe = Color.WHITE; erstelleGrafik(); zeichneGrafik(); } Das Image erstellen, diese Methode wird nur lokal benötigt: private void erstelleGrafik() { image = new BufferedImage( breite*8/10, hoehe*8/10, BufferedImage.TYPE_INT_RGB ); g2D = image.createGraphics(); } Die Methode zeichneGrafik wird von außen aufgerufen, sie ersetzt das alte Rechteck: public void zeichneGrafik() { g2D.setColor(farbe); g2D.fillRect(0, 0, image.getWidth(), image.getHeight()); g2D G.setColor(Color.BLACK); g2D drawRect(0, 0, image.getWidth(), image.getHeight()); repaint(); } public void setFarbe(Color farbe) { this.farbe = farbe; } paintComponent stellt das Image beim Programmstart dar und erneuert die Darstellung bei Bedarf ohne es zu verändern. public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(image, (breite - image.getWidth())/2, (hoehe - image.getHeight())/2, null); } } copyleft:munz 154 Fachschulen Lörrach 8 8.2 Anhang Java Quelltexte zum Grafikprogramm mit BufferedImage Das Grundgerüst: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JGrafik extends JFrame implements ActionListener { private GrafikPanel zeichenFlaeche; private JButton bnNeu; public JGrafik(String title) { super (title); addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); bnNeu = new JButton("Neue Zeichnung"); bnNeu.addActionListener(this); cp.add(bnNeu, BorderLayout.NORTH); zeichenFlaeche = new GrafikPanel(300, 200); cp.add(zeichenFlaeche); pack(); setVisible(true); } } public void actionPerformed(ActionEvent event) { zeichenFlaeche.zeichneMuster(); repaint(); } public static void main (String[] args) { new JGrafik("Grafik-Beispiel"); } Die Zeichenfläche mit einer Image-Komponente: import java.awt.*; import java.awt.image.*; import javax.swing.*; public class GrafikPanel extends JPanel { private int breite, hoehe; private BufferedImage image; private Graphics2D g2D; public GrafikPanel(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; setPreferredSize(new Dimension(breite, hoehe)); erstelleGrafik(); } private void erstelleGrafik() { int b = breite - 80, h = hoehe - 80; image = new BufferedImage(b, h, BufferedImage.TYPE_INT_RGB); g2D = image.createGraphics(); g2D.setColor(getBackground()); g2D.fillRect(0, 0, b, h); } copyleft:munz 155 Fachschulen Lörrach 8 Anhang } copyleft:munz Java public void zeichneGrafik() { int b = image.getWidth(), h = image.getHeight(); g2D.setColor(getBackground()); g2D.fillRect(0, 0, b, h); g2D.setColor(Color.BLACK); for (int x = 0; x < b; x++) { if (Math.random() < 0.5) g2D.drawLine(x, 0, x, h); } } public void paintComponent(Graphics g) { super.paintComponent(g); int x = (breite - image.getWidth())/2; int y = (hoehe - image.getHeight())/2; g.drawImage(image, x, y, this); } 156 Fachschulen Lörrach 8 8.3 Anhang Java Quelltexte zum Pakel turtle Die Klasse turtle.java package turtle; import java.awt.*; import java.awt.image.*; import javax.swing.*; public class Turtle extends JPanel { private int b, h, xa = 0, ya = 0; private int orientierungsWinkel = 0; private double bogen=0; private BufferedImage image; private Graphics g; public Turtle(){ this(200, 200); } public Turtle(int b, int h){ this.b = b; this.h = h; setPreferredSize(new Dimension(b, h)); image = new BufferedImage(b,h, BufferedImage.TYPE_INT_RGB); g = image.getGraphics(); g.setColor(Color.white); g.fillRect(0,0,b,h); g.setColor(Color.BLACK); } public int getTurtleX(){ return xa; } public int getTurtleY(){ return ya; } public void setTurtle(int xa, int ya){ this.xa = xa; this.ya = ya; } public void vor(int schritt){ int dx = (int) (schritt* Math.sin(bogen)); int dy = (int) (schritt* Math.cos(bogen)); int xe = xa + dx; int ye = ya - dy; g.drawLine(xa,ya,xe,ye); xa=xe; ya=ye; } public void rechts(int drehWinkel){ setOrientierungsWinkel(getOrientierungsWinkel()+drehWinkel); bogen = winkelInBogen(getOrientierungsWinkel()); } public int getOrientierungsWinkel(){ return orientierungsWinkel; } public void setOrientierungsWinkel(int orientierungsWinkel){ this.orientierungsWinkel = orientierungsWinkel; bogen = winkelInBogen(getOrientierungsWinkel()); } copyleft:munz 157 Fachschulen Lörrach 8 Anhang } Java private double winkelInBogen(int winkel){ return 2 * Math.PI*winkel/360; } public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(image,0,0,this); } Das Interface TGrafik.java: package turtle; public interface TGrafik{ public abstract void zeichne(Turtle t); } Quelltext TurtleGUI: package turtle; import import import public java.awt.*; javax.swing.*; java.awt.event.*; abstract class TurtleGUI extends JFrame implements TGrafik, ActionListener { protected Turtle t; private JButton zeichneButton; public TurtleGUI(String titel) { super(titel); t = new Turtle(); init(); } public TurtleGUI(String titel, int b, int h) { super(titel); t = new Turtle(b, h); init(); } private void init() { addWindowListener(new WindowAdapter() { public void windowClosing (WindowEvent evt) { System.exit(0);}} ); Container cp = getContentPane(); cp.setLayout(new BorderLayout()); cp.add(t, BorderLayout.CENTER); zeichneButton = new JButton("Zeichne!"); zeichneButton.addActionListener(this); cp.add(zeichneButton, BorderLayout.SOUTH); setLocation(10, 10); pack(); setVisible(true); setResizable(false); } public void actionPerformed(ActionEvent e){ zeichne(t); repaint(); } } copyleft:munz 158 Fachschulen Lörrach 8 8.4 Anhang Java Quelltexte zum Kommunikationsserver import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; public class Kserver2 extends Frame implements ActionListener { TextArea textEin = new TextArea(); TextArea textAus = new TextArea(); TextField rechnerAdresse= new TextField("127.0.0.1"); Sender meinSender=new Sender(); public Kserver2() { super("Gib mir die Dose"); setLayout(new FlowLayout()); setSize(500,550); Label e = new Label("Eingang:"); add(e); add(textEin); textEin.setEditable(false); Label a = new Label("Ausgang:"); add(a); add(textAus); add(rechnerAdresse); Button b = new Button("Absenden"); add(b); b.addActionListener(this); setVisible(true); addWindowListener(new FensterLauscher()); verbinde(); } public void verbinde() { ServerSocket verbindung; try { verbindung=new ServerSocket(6000); while (true) { Socket lauschen=verbindung.accept(); Empfaenger meinEmpfaenger=new Empfaenger(lauschen); meinEmpfaenger.start(); } } catch (Exception e) { }; } public void actionPerformed(ActionEvent e) { meinSender.sende(); } public static void main (String[] args) { new Kserver2(); } copyleft:munz 159 Fachschulen Lörrach 8 Anhang Java class Empfaenger extends Thread { Socket erlauscht; public Empfaenger(Socket lauschen) { erlauscht=lauschen; } public void run() { String sEin=""; try { String absender=erlauscht.getInetAddress().getHostAddress(); InputStreamReader portLeser; portLeser=new InputStreamReader(erlauscht.getInputStream()); BufferedReader eingabe=new BufferedReader(portLeser); sEin=eingabe.readLine(); while (sEin!=null) { textEin.append(absender+": "+sEin+"\n"); sEin=eingabe.readLine(); } eingabe.close(); } catch (Exception e) {} } } class Sender{ public void sende() { try { Socket zumNachbar = new Socket(rechnerAdresse.getText(),6000); PrintWriter ausgabe; ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true); ausgabe.println(textAus.getText()); ausgabe.close(); textAus.setText(""); } catch (Exception e) {} } } class FensterLauscher extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); }} } copyleft:munz 160 Fachschulen Lörrach 8 8.5 Anhang Java Quelltexte zum CGI-Server import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; public class CGIServer extends Frame implements ActionListener { TextArea textEin = new TextArea(); TextArea textAus = new TextArea(); TextField rechnerAdresse= new TextField("127.0.0.1"); Sender meinSender=new Sender(); String sEin=""; Kunde meinKunde = new Kunde(); public CGIServer() { super("Gib mir die Dose"); setLayout(new FlowLayout()); setSize(500,550); Label e = new Label("Eingang:"); add(e); add(textEin); textEin.setEditable(false); Label a = new Label("Ausgang:"); add(a); add(textAus); add(rechnerAdresse); Button b = new Button("Absenden"); add(b); b.addActionListener(this); setVisible(true); addWindowListener(new FensterLauscher()); verbinde(); } public void verbinde() { ServerSocket verbindung; try { verbindung=new ServerSocket(6000); while (true) { Socket lauschen=verbindung.accept(); Empfaenger meinEmpfaenger=new Empfaenger(lauschen); meinEmpfaenger.start(); } } catch (Exception e) { }; } public void actionPerformed(ActionEvent e) { meinSender.sende(textAus.getText()); textAus.setText(""); } public static void main (String[] args) { new CGIServer(); } copyleft:munz 161 Fachschulen Lörrach 8 Anhang Java class Empfaenger extends Thread { Socket erlauscht; public Empfaenger(Socket lauschen){ erlauscht=lauschen; } public void run() { try { String absender=erlauscht.getInetAddress().getHostAddress(); InputStreamReader portLeser; portLeser=new InputStreamReader(erlauscht.getInputStream()); BufferedReader eingabe=new BufferedReader(portLeser); sEin=eingabe.readLine(); textEin.append(absender+": "+sEin+"\n"); eingabe.close(); meinKunde.entziffern(); meinKunde.benachrichtigen(); } catch (Exception e) {} } } class Sender{ public void sende(String s) { try { Socket zumNachbar = new Socket("localhost",6000); PrintWriter ausgabe; ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true); ausgabe.println(s); } catch (Exception e) {} } } copyleft:munz 162 Fachschulen Lörrach 8 Anhang Java class Kunde{ String[] varName = new String[10]; String[] varWert = new String[10]; void entziffern() { int i=0; int n=0; char ch=sEin.charAt(i); while (ch != '?') { i=i+1; ch=sEin.charAt(i); } while (ch != ' ') { varName[n]=""; varWert[n]=""; i=i+1; ch=sEin.charAt(i); while (ch != '=') { varName[n]=varName[n]+ch; i=i+1; ch=sEin.charAt(i); } i=i+1; ch=sEin.charAt(i); while ((ch != '&') && (ch != ' ')) { varWert[n]=varWert[n]+ch; i=i+1; ch=sEin.charAt(i); } n=n+1; } } void benachrichtigen() { meinSender.sende("HTTP/1.0 200 OK\n“ + Content-type: text/html\n\n"); for (int n=0; n<3; n=n+1) meinSender.sende(varName[n]+" : "+ varWert[n]+ "<BR>\n"); } } class FensterLauscher extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } } copyleft:munz 163 Fachschulen Lörrach 8 8.6 Anhang Java Quelltexte zum Server mit Datenbankzugang import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; import java.sql.*; public class AccessServer3 extends Frame implements ActionListener { TextArea textEin = new TextArea(); TextArea textAus = new TextArea(); TextField rechnerAdresse= new TextField("127.0.0.1"); Sender meinSender=new Sender(); String sEin=""; Kunde meinKunde = new Kunde(); public AccessServer3() { super("Gib mir die Dose"); setLayout(new FlowLayout()); setSize(500,550); Label e = new Label("Eingang:"); add(e); add(textEin); textEin.setEditable(false); Label a = new Label("Ausgang:"); add(a); add(textAus); add(rechnerAdresse); Button b = new Button("Absenden"); add(b); b.addActionListener(this); setVisible(true); addWindowListener(new FensterLauscher()); verbinde(); } public void verbinde() { ServerSocket verbindung; try { verbindung=new ServerSocket(6000); while (true) { Socket lauschen=verbindung.accept(); Empfaenger meinEmpfaenger=new Empfaenger(lauschen); meinEmpfaenger.start(); } } catch (Exception e) { }; } public void actionPerformed(ActionEvent e) { meinSender.sende(textAus.getText()); textAus.setText(""); } public static void main (String[] args) { new AccessServer3(); } copyleft:munz 164 Fachschulen Lörrach 8 Anhang Java class Empfaenger extends Thread{ Socket erlauscht; public Empfaenger(Socket lauschen){ erlauscht=lauschen; } public void run() { try { String absender=erlauscht.getInetAddress().getHostAddress(); InputStreamReader portLeser; portLeser=new InputStreamReader(erlauscht.getInputStream()); BufferedReader eingabe=new BufferedReader(portLeser); sEin=eingabe.readLine(); textEin.append(absender+": "+sEin+"\n"); eingabe.close(); meinKunde.entziffern(); meinKunde.speichern(); meinKunde.benachrichtigen(); } catch (Exception e) {} } } class Sender{ public void sende(String s) { try { Socket zumNachbar = new Socket("localhost",6000); PrintWriter ausgabe; ausgabe=new PrintWriter(zumNachbar.getOutputStream(),true); ausgabe.println(s); } catch (Exception e) {} } } copyleft:munz 165 Fachschulen Lörrach 8 Anhang Java class Kunde{ String[] varName = new String[10]; String[] varWert = new String[10]; void entziffern() { int i=0; int n=0; char ch=sEin.charAt(i); while (ch != '?') { i=i+1; ch=sEin.charAt(i); } while (ch != ' ') { varName[n]=""; varWert[n]=""; i=i+1; ch=sEin.charAt(i); while (ch != '=') { varName[n]=varName[n]+ch; i=i+1; ch=sEin.charAt(i); } i=i+1; ch=sEin.charAt(i); while ((ch != '&') && (ch != ' ')) { varWert[n]=varWert[n]+ch; i=i+1; ch=sEin.charAt(i); } n=n+1; } } void benachrichtigen() { meinSender.sende( "HTTP/1.0 200 OK\nContent-type: text/html\n\n" ); for (int n=0; n<3; n=n+1) meinSender.sende(varName[n]+" : "+ varWert[n]+ "<BR>\n"); } void speichern() { try { String treiber="sun.jdbc.odbc.JdbcOdbcDriver"; Class.forName(treiber); String db="jdbc:odbc:Adressen"; Connection con = java.sql.DriverManager.getConnection(db,"",""); Statement s = con.createStatement(); String eintrag; eintrag="insert into Klassenliste("+varName[0]; for (int i=1; i<n; i=i+1) eintrag=eintrag+","+varName[i]; eintrag=eintrag+")values('"+varWert[0]+"'"; for (int i=1; i<n; i=i+1) eintrag = eintrag+",'"+varWert[i]+"'"; eintrag=eintrag+")"; s.execute(eintrag); con.close(); meinSender.sende("alles klar!\n"); } catch (Exception e) { System.out.println(""+e.getMessage());} } } class FensterLauscher extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } } copyleft:munz 166 Fachschulen Lörrach 9 Java im Internet 9 Java im Internet Java JavaEditor: http://www.bildung.hessen.de/abreich/inform/skii/material/java/editor.htm JCreator: http://www.jcreator.com/ Sun: Java™ 2 Platform, Standard Edition (J2SE™) http://java.sun.com/j2sdk/ Sun: Java™ 2 Platform, Enterprise Edition (J2EE™) http://java.sun.com/j2see/ Sun: The Java-Tutorial http://java.sun.com/docs/books/tutorial/ → Informationen zu Servlets: http://java.sun.com/docs/books/tutorial/servlets/TOC.html Sun: Tutorials & Short Courses http://developer.java.sun.com/developer/onlineTraining/ Sun: Java Foundation Classes (JFC), Cross Platform GUIs & Graphics http://java.sun.com/products/jfc/ → The Swing Connection http://java.sun.com/products/jfc/tsc/ → Swing Connection, Acticle Index http://java.sun.com/products/jfc/tsc/articles → Getting Started with Swing http://java.sun.com/productsjfc/tsc/articles/getting_started → A Swing Architecture Overview http://java.sun.com/productsjfc/tsc/articles/architecture → Painting in AWT and Swing http://java.sun.com/productsjfc/tsc/articles/painting/ Das Java-Buch von Guido Krüger: http://www.javabuch.de/ Tomcat (Servlet Container): http://jakarta.apache.org/ MySQL-Treiber für Java: http://www.worldserver.com/mm.mysql/ Datenbank-Management-System MySQL: http://www.mysql.com/downloads/index.html Landesinstitut für Erziehung und Unterricht Stuttgart, Musterlösungen für Novell, Windows, Linux: http://www.lehrerfortbildung-bw.de/netz/muster/ Java-Seiten der Autoren: http://www.lehrer.uni-karlsruhe.de/~za220/htm/kurse/informat/inform.html http://www.pohlig.de http://ww2.lehrer.uni-karlsruhe.de/~taulien/info/java/java.html copyleft:munz 167 (Reinhold Ley) (Michael Pohlig) (Matthias Taulien) Fachschulen Lörrach 10 Index Java 10 Index abstract .........................................................7 Abstract Windowing Toolkit ..........................12 AbstractAction .............................................58 AbstractButton .............................................55 abstrakte Klasse ............................................4 Access-Treiber .......................................... 140 ActionListener.................................. 30, 34, 41 actionPerformed ..........................................30 Adapter-Klasse ............................................37 addActionListener ........................................31 addWindowListener .....................................37 AFC.............................................................13 anonyme Klasse .............................. 35, 37, 74 Antialiasing ............................................ 20, 51 API ..............................................................19 Application Foundation Classes ...................13 Attribut...........................................................6 AVL-Baum ................................................. 127 AWT ............................................................12 BasicStroke ........................................... 19, 52 Baum......................................... 116, 117, 126 binärer Baum ............................................. 126 binärer Suchbaum ............................. 126, 127 Blätter........................................................ 126 BOLD ..........................................................21 BorderFactory..............................................29 BorderLayout ................................... 25, 27, 41 Box........................................................ 28, 41 BoxLayout ............................................. 25, 27 BufferedImage ....................................... 16, 18 Canvas ........................................................14 charWidth ....................................................22 clearRect .....................................................17 ColorChooser ..............................................51 Component............................................ 15, 24 Container......................................... 14, 24, 25 ContentPane................................................25 copyArea .....................................................18 createBevelBorder .......................................29 createCompoundBorder...............................29 createEmptyBorder......................................29 createEtchedBorder.....................................30 createGlue...................................................28 createGraphics ............................................16 createHorizontalGlue ...................................28 createHorizontalStrut ...................................28 createLineBorder .........................................30 createLoweredBevelBorder..........................29 createMatteBorder .......................................30 createRaisedBevelBorder ............................29 createTitledBorder .......................................30 CreateVerticalGlue ......................................28 createVerticalStrut .......................................28 Dialog.................................................... 21, 25 DNS .......................................................... 140 Double-Buffering............................ 14, 97, 101 drawArc .......................................................17 drawImage............................................. 17, 18 drawLine......................................................17 drawOval .....................................................17 copyleft:munz drawPolygon................................................17 drawPolyline ................................................17 drawRect .....................................................17 drawRoundRect ...........................................17 drawString ...................................................18 E-Commerce ............................................. 151 Event...........................................................30 Event Delegation Model ...............................30 EventListener......................................... 30, 35 Fachklasse ....................................................4 Fachklassen ................................................30 FIFO.................................................. 117, 122 fillArc ...........................................................17 fillOval .........................................................17 fillPolygon ....................................................17 fillRect .........................................................17 fillRoundRect ...............................................17 First In First Out......................................... 122 FlowLayout ............................................ 25, 26 FocusListener ..............................................34 Font.............................................................21 Frame..........................................................25 FTP ........................................................... 133 getAscent ....................................................22 getContentPane...........................................25 getDescent ..................................................22 getFamily.....................................................21 getFont ........................................................21 getFontMetrics.............................................21 getGraphics ........................................... 16, 18 getHeight .....................................................22 getKeyCode.................................................61 getLeading...................................................22 getSize ........................................................21 getSource ....................................................32 getStyle .......................................................21 getWindowListeners() ..................................45 GlassPane...................................................25 Graphical User Interface .......................... 5, 12 Graphics................................................ 16, 19 Graphics2D .................................................19 GridBagLayout....................................... 25, 27 GridLayout............................................. 25, 27 GUI.......................................................... 5, 12 GUI-Klasse ....................................................4 GUI-Klassen ................................................30 Hostadresse .............................................. 133 HTML-Client .............................................. 149 HTMLConverter ...........................................13 http ............................................................ 133 IFC ..............................................................13 init() .............................................................45 innere Klasse...............................................35 In-Order..................................................... 128 Interface .......................................... 30, 37, 72 Internet Foundation Classes ........................13 IP-Adresse................................................. 133 ipconfig...................................................... 133 isLeftMouseButton ................................. 62, 63 ITALIC.........................................................21 168 Fachschulen Lörrach 10 Index Java JApplet .................................................. 40, 45 jar-Datei.......................................................56 Java Foundation Classes....................... 12, 13 java.util ........................................................54 Java2D ........................................................51 Java-Plug-In ................................................13 Java-Virtual-Machine ...................................12 JComboBox........................................... 51, 64 JComponent .......................................... 15, 24 JDialog ........................................................25 JFC ....................................................... 12, 13 JFrame ........................................................25 JLayeredPane .............................................25 JMenuBar ....................................................51 JPanel .........................................................25 JRadioButton...............................................41 JRootPane...................................................25 JWindow......................................................25 Kante......................................................... 126 Keller......................................................... 117 KeyAdapter..................................................61 KeyEvent.VK_SHIFT ...................................61 keyPressed..................................................61 Klasse ...........................................................4 Knoten....................................................... 126 Last In First Out ......................................... 120 LayoutManager............................................25 leichtgewichtig .............................................12 LIFO .................................................. 117, 120 Liste .................................................. 116, 117 localhost .................................................... 133 logischer......................................................21 Mehrfachvererbung......................................72 MenüLeiste..................................................25 Methode ........................................................6 Middleware ................................................ 144 model-view-controller...................................13 Monospaced ................................................21 MouseAdapter ....................................... 38, 62 mouseClicked ..............................................37 mouseDragged ...................................... 37, 63 mouseEntered .............................................37 mouseExited................................................37 MouseListener .............................................37 MouseMotionAdapter............................. 38, 63 MouseMotionListener...................................37 mouseMoved...............................................37 mousePressed.............................................37 mouseReleased..................................... 37, 63 MVC ............................................................13 Netzadresse .............................................. 133 notifyAll() .....................................................94 Objekt............................................................4 Objektvariable..............................................10 ODBC........................................................ 140 OOP ..............................................................4 package.........................................................7 paint ............................................................15 paintComponent .............................. 15, 57, 61 PL&F ...........................................................12 copyleft:munz PLAIN..........................................................21 pluggable look and feel .......................... 12, 13 point ............................................................21 Polymorphie ..................................................4 POP .......................................................... 133 Port ........................................................... 133 Post-Fix-Notation ....................................... 129 Post-Order................................................. 128 Pre-Order .................................................. 128 privat .............................................................6 protected .......................................................6 Rekursion .......................................... 109, 126 RenderingHints...................................... 20, 59 repaint .........................................................16 RepaintManager ..........................................16 Request ..................................................... 149 requestFocus...............................................62 Response .................................................. 149 Runnable .....................................................72 SansSerif.....................................................21 schwergewichtig ..........................................12 Serif.............................................................21 Server................................................ 133, 146 Servlet ....................................................... 146 setBorder.....................................................29 setBounds ...................................................26 setContentPane ...........................................25 setFont ........................................................21 setStroke .....................................................19 Shape..........................................................52 Socket ....................................................... 133 SQL........................................................... 141 Stack ........................................... 69, 116, 117 stringWidth ..................................................22 Swing ..........................................................12 SwingUtilities ......................................... 62, 63 synchronized ......................................... 93, 94 System-DNS.............................................. 140 TCP/IP....................................................... 133 Telnet ........................................................ 133 Thread.........................................................69 Turtle...........................................................79 UML ..............................................................6 UML-Notation ................................................6 Unified Modelling Language...........................6 Unified Ressource Locator...........................56 URL............................................. 56, 148, 149 Vector.................................................... 54, 59 Vererbung......................................................4 VM......................................................... 12, 69 wait() ...........................................................94 Warenkorb................................................. 151 Window .......................................................25 WindowAdapter ..................................... 37, 51 windowClosing.............................................37 WindowListener ............................... 37, 40, 45 WINIPCFG ................................................ 133 Wurzel ....................................................... 126 X_AXIS........................................................27 Y_AXIS........................................................27 169 Fachschulen Lörrach 11 Inhaltsverzeichnis Java 11 Inhaltsverzeichnis 1 2 3 4 5 6 7 8 Einführung in die objektorientierte Programmierung (OOP) .......................................................... 4 1.1 Was ist eine Klasse, was ist ein Objekt?................................................................................ 4 1.2 Das Miniprojekt „Figuren“...................................................................................................... 4 1.3 Trenne Fachklassen von den GUI-Klassen! .......................................................................... 5 1.4 Die Fachklassen Quadrat und Kreis ...................................................................................... 5 1.5 Die abstrakte Klasse Figur .................................................................................................... 6 1.6 Pakete .................................................................................................................................. 7 1.7 Die Quelltexte ....................................................................................................................... 9 Grafik-Programmierung (GUI) .................................................................................................... 12 2.1 Das Abstract Windowing Toolkit (AWT)............................................................................... 12 2.2 Swing ................................................................................................................................. 12 2.3 Andere Java Foundation Classes (JFC) .............................................................................. 13 2.4 Grafik-Programmierung mit Swing....................................................................................... 14 2.5 Die wichtigsten Methoden zum Zeichnen grafischer Objekte ............................................... 16 2.6 Grafiken mit der Klasse BufferedImage........................................................................... 18 2.7 Java 2D Grafik API ............................................................................................................. 19 2.8 Font–Objekte ...................................................................................................................... 21 2.9 Aufgaben ............................................................................................................................ 23 2.10 Die Swing-Komponenten.................................................................................................... 24 2.11 Die LayoutManager............................................................................................................ 25 2.12 Rahmen............................................................................................................................. 29 2.13 Ereignisse.......................................................................................................................... 30 2.14 Die GUI-Klassen zum Miniprojekt „Figuren“........................................................................ 40 2.15 Einige GUI-Beispielprogramme .......................................................................................... 46 2.16 Aufgaben ........................................................................................................................... 67 Threads ..................................................................................................................................... 69 3.1 Programme sind Threads.................................................................................................... 69 3.2 Eine eigene Thread-Klasse ................................................................................................. 70 3.3 Threads – Runnable ........................................................................................................... 72 3.4 Runnable als anonyme Klasse ............................................................................................ 74 Rekursionen .............................................................................................................................. 76 4.1 Was sind Rekursionen? ...................................................................................................... 76 4.2 Rekursive Methoden in Java ............................................................................................... 76 4.3 Rekursive Grafiken ............................................................................................................. 79 4.4 Binärbaum .......................................................................................................................... 81 4.5 Kochkurve .......................................................................................................................... 83 4.6 Sierpinski-Dreieck............................................................................................................... 85 4.7 Pythagoras-Baum ............................................................................................................... 86 Threads und Rekursionen – ein alternativer Zugang mit Applets ................................................ 90 5.1 Nebenläufige Prozesse (Threads) ....................................................................................... 90 5.2 Graphisch animierte Applets mit Threads ............................................................................ 97 5.3 Graphisch animierte Applets mit Threads und Turtlegraphik .............................................. 101 5.4 Rekursion ......................................................................................................................... 109 Abstrakte Datentypen .............................................................................................................. 117 6.1 Was ist eine Liste?............................................................................................................ 117 6.2 Die abstrakte Liste ............................................................................................................ 118 6.3 ListeLIFO.......................................................................................................................... 120 6.4 ListeFIFO.......................................................................................................................... 122 6.5 Listen-Demo ..................................................................................................................... 124 6.6 Der abstrakte Datentyp Baum ........................................................................................... 126 Java-Server im Intranet und Internet ........................................................................................ 133 7.1 Direkte Kommunikation zweier Rechner über Sockets (TCP/IP) ........................................ 133 7.2 Client-Server-Kommunikation über das Common Gateway Interface (CGI) ....................... 136 7.3 Datenbanken .................................................................................................................... 139 7.4 Servlets ............................................................................................................................ 149 Anhang .................................................................................................................................... 155 copyleft:munz 170 Fachschulen Lörrach 11 Inhaltsverzeichnis Java 8.1 Quelltexte zur den einfachen Grafik-Programmen ............................................................. 155 8.2 Quelltexte zum Grafikprogramm mit BufferedImage ...................................................... 158 8.3 Quelltexte zum Pakel turtle ............................................................................................... 160 8.4 Quelltexte zum Kommunikationsserver ............................................................................. 162 8.5 Quelltexte zum CGI-Server ............................................................................................... 164 8.6 Quelltexte zum Server mit Datenbankzugang.................................................................... 167 9 Java im Internet ....................................................................................................................... 170 10 Index....................................................................................................................................... 171 11 Inhaltsverzeichnis.................................................................................................................... 173 copyleft:munz 171 Fachschulen Lörrach