Die Präsentation wird geladen. Bitte warten

Die Präsentation wird geladen. Bitte warten

Benutzeroberflächen mit Java

Ähnliche Präsentationen


Präsentation zum Thema: "Benutzeroberflächen mit Java"—  Präsentation transkript:

1 Benutzeroberflächen mit Java
Michael Weiss

2 Ausgangspunkt JFrame import javax.swing.*; /** * @author Michael Weiss
*/ public class Uhr { private JFrame hatJFrame; public Uhr() hatJFrame = new JFrame("Fenster"); hatJFrame.setVisible(true); // <- vorher sieht man gar nichts! } public static void main(String args[]) { try { // 5 Zeilen, damit sich das Programm ins OS integriert UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { System.err.println("no system look and feel available"); }; new Uhr(); Michael Weiss

3 Ausgangspunkt JFrame Das Resultat überzeugt noch nicht vollständig:
Verbesserung: import javax.swing.*; import java.awt.*; public Uhr() { hatJFrame = new JFrame("Fenster"); hatJFrame.setMinimumSize(new_Dimension(400,300)); hatJFrame.setVisible(true); } Ergebnis: Michael Weiss

4 Fenster aufteilen Das Fenster soll am Schluss etwa so aussehen:
Wenn man das Fenster skaliert, soll der Bereich oben rechts nicht breiter werden. Michael Weiss

5 JPanel Die einzelnen Fensterteile sind Objekte vom Typ JPanel. Wir fügen nun gemäss dem gewünschten Layout 3 JPanels in das JFrame ein. public Uhr() { hatJFrame = new JFrame("Fenster"); hatJFrame.setMinimumSize(new Dimension(400,300)); hatJFrame.setLayout(new BorderLayout()); hatZeichenJPanel_=_new_JPanel(); hatJFrame.add(hatZeichenJPanel,_BorderLayout.CENTER); hatSliderJPanel_=_new_JPanel(); hatJFrame.add(hatSliderJPanel,_BorderLayout.SOUTH); hatButtonJPanel_=_new_JPanel(); hatJFrame.add(hatButtonJPanel,_BorderLayout.EAST); hatJFrame.setVisible(true); } Der sichtbare Effekt ist gleich null, daher fügen wir 2 Buttons ein. Michael Weiss

6 GridLayout und JButton
Wir fügen 2 Buttons in das hatButtonJPanel ein. Dazu soll der Platz in diesem Panel in gleich grosse Rechteckzellen eingeteilt werden, was durch Zuweisung eines GridLayouts geschieht. hatButtonJPanel.setLayout(new GridLayout(2,1)); // Zeilen, Spalten hatPauseJButton = new JButton("Pause"); hatButtonJPanel.add(hatPauseJButton); hatStellenJButton = new JButton("stellen"); hatButtonJPanel.add(hatStellenJButton); Wieder überzeugt das Resultat nur teilweise: Der "Pause"-Knopf blinkt blau, wie wenn er aktiv wäre (man sagt: wie wenn er den Focus hätte). Die beiden Knöpfe füllen den gesamten "EAST"-Raum aus. Michael Weiss

7 Keine Buttons mit Focus...
hatButtonJPanel.setLayout(new GridLayout(2,1)); // Zeilen, Spalten hatPauseJButton = new JButton("Pause"); hatPauseJButton.setFocusable(false); hatButtonJPanel.add(hatPauseJButton); hatStellenJButton = new JButton("stellen"); hatStellenJButton.setFocusable(false); hatButtonJPanel.add(hatStellenJButton); ...und bitte nicht so riesig! Eigentlich sollen die Buttons nur den oberen Teil des EAST-Gebiets füllen: Dazu unterteilen wir das EAST-Gebiet erneut mit einem BorderLayout und stecken das ButtonJPanel in das NORTH-Gebiet dieser Unterteilung. Michael Weiss

8 Geschachtelte Layouts
hatEastJPanel_=_new_JPanel(); hatJFrame.add(hatEastJPanel,_BorderLayout.EAST); hatEastJPanel.setLayout(new_BorderLayout()); hatButtonJPanel = new JPanel(); hatEastJPanel.add(hatButtonJPanel,_BorderLayout.NORTH); hatButtonJPanel.setLayout(new GridLayout(2,1)); // Zeilen, Spalten hatPauseJButton = new JButton("Pause"); hatPauseJButton.setFocusable(false); hatButtonJPanel.add(hatPauseJButton); hatStellenJButton = new JButton("stellen"); hatStellenJButton.setFocusable(false); hatButtonJPanel.add(hatStellenJButton); schon besser.... aber ein wenig freien Rand rund um die Buttons würde man sich schon noch wünschen! Michael Weiss

9 Ränder einrichten nicht schlecht! hatEastJPanel = new JPanel();
hatJFrame.add(hatEastJPanel, BorderLayout.EAST); hatEastJPanel.setLayout(new BorderLayout()); hatButtonJPanel = new JPanel(); // unsichtbaren 10 Pixel breiten Rand um das hatButtonJPanel hatButtonJPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); hatEastJPanel.add(hatButtonJPanel, BorderLayout.NORTH); // Das GridLayout_auch_noch_mit_Abständen_zwischen_den_Zellen_versehen hatButtonJPanel.setLayout(new GridLayout(2,1,10,10)); // Zeilen, Spalten, horiz. & vert. Abstand hatPauseJButton = new JButton("Pause"); hatPauseJButton.setFocusable(false); hatButtonJPanel.add(hatPauseJButton); hatStellenJButton = new JButton("stellen"); hatStellenJButton.setFocusable(false); hatButtonJPanel.add(hatStellenJButton); nicht schlecht! Michael Weiss

10 Slider Nun noch ein Schiebebalken zur Einstellung der Geschwindigkeit der Uhr. hatJSlider = new JSlider(JSlider.HORIZONTAL, -10, 10, 1); hatJSlider.setMajorTickSpacing(1); hatJSlider.setPaintTicks(true); hatJSlider.setPaintLabels(true); hatSliderJPanel.add(hatJSlider); Leider hat Java seine eigenen Vorstellungen davon, was eine optimale Slider-Breite ist. Michael Weiss

11 Slider, verschönert Einige Versuche führen schliesslich auf die folgende Lösung: hatJSlider = new JSlider(JSlider.HORIZONTAL, -10, 10, 1); hatJSlider.setMajorTickSpacing(1); hatJSlider.setPaintTicks(true); hatJSlider.setPaintLabels(true); hatJSlider.setMinimumSize(new_Dimension(hatJFrame.getWidth()-20,50)); hatJSlider.setPreferredSize(new_Dimension(hatJFrame.getWidth()-20,50)); hatSliderJPanel.add(hatJSlider); Das kommt dem erwünschten Aussehen schon recht nahe. Nur die Uhr fehlt noch! Michael Weiss

12 In ein JPanel hineinzeichnen
Im Gegensatz zur SuM-Bibliothek stellen uns die Java-Standardbibliotheken kein Fenster zur Verfügung, in das wir einfach jederzeit zeichnen können. Dafür weiss Java, wann ein JPanel neu gezeichnet werden muss und ruft dann die Methode paintComponent(Graphics pGraphics) auf. Standardmässig zeichnet diese Methode aber nur die in ihr enthaltenen GUI-Komponenten (Buttons, Slider, Labels etc.) neu. Formen wie Kreise, Linien usw. müssen wir selbst zeichnen. Zu diesem Zweck entwickeln wir eine Unterklasse von JPanel, welche paintComponent(Graphics pGraphics)überschreibt: import javax.swing.*; import java.awt.*; /** * Ein JPanel um drin zu zeichnen. * Michael Weiss */ public class ZeichenJPanel extends JPanel { // überschreibe die entsprechende Methode der Überklasse protected void paintComponent(Graphics pGraphics) // Damit die Buttons, Slider, Labels etc. weiterhin gezeichnet werden, // rufen wir als erstes paintComponent() der Überklasse auf. super.paintComponent(pGraphics); // Ab hier zeichnen wir selbst. } Michael Weiss

13 In ein JPanel hineinzeichnen
Das erfordert auch kleine Änderungen an der Klasse Uhr: hatUhrJPanel = new JPanel(); hatJFrame.add(hatUhrJPanel, BorderLayout.CENTER); hatUhrZeichenJPanel_=_new_ZeichenJPanel(); hatJFrame.add(hatUhrZeichenJPanel,_BorderLayout.CENTER); Bis jetzt haben wir aber noch gar nichts gezeichnet! Michael Weiss

14 Wie zeichnet man eine Uhr?
Die Uhr sollte ein möglichst grosses Quadrat ausfüllen. Wir bestimmen die Koordinaten dieses Quadrats und beginnen dann mit einem Kreis. // ab hier zeichnen wir selbst. pGraphics.setColor(Color.BLACK); int xmin = 0, width = 0, ymin = 0, height = 0; if(this.getWidth() > this.getHeight()) { height = this.getHeight() - 10; ymin = 5; xmin = (this.getWidth() - height) / 2; width = height; } else { width = this.getWidth() - 10; xmin = 5; ymin = (this.getHeight() - width) / 2; height = width; } pGraphics.drawOval(xmin, ymin, width, height); Das Graphics-Objekt wird uns von Java mitgegeben, damit wir seine Methoden zum zeichnen benutzen können. Manchmal braucht man mehr Methoden, als sie das Graphics-Objekt zur Verfügung stellt. pGraphics zeigt aber in Wirklichkeit gar nicht auf ein reines Graphics-Objekt, sondern auf ein Graphics2D-Objekt, welches weitere Zeichnungsmehtoden zur Verfügung stellt. Um pGraphics als Zeiger auf ein Graphics2D-Objekt zu verwenden, müssen wir casten: Graphics2D kenntGraphics = (Graphics2D)pGraphics; // danach mit kenntGraphics arbeiten. Michael Weiss

15 Die Uhrzeiger Es macht Sinn, eine Klassenhierarchie einzuführen:
Die Standard-Bibliothek von Java erlaubt es, Formen als Kombinationen von Linienzügen, Ellipsen, Kreisbogenstücken und anderen Grundformen auszudrücken und diese Formen später als Ganzes zu transformieren. Wir beschreiben daher die Zeiger in einer bequemen Lage (12-Uhr-Stellung) und in einem handlichen Koordinatensystem (Ursprung im Mittelpunkt der Uhr, Radius der Uhr gleich 50 Einheiten). Die anschliessende Transformationsarbeit (Drehen der Zeiger entsprechend der gewünschten Zeit, Einpassen der Zeiger in ein Fenster, dessen Grösse sich verändern kann) ist für alle Uhrzeiger gleich und wird daher in der abstrakten Überklasse erledigt. Michael Weiss

16 Der Minutenzeiger y import java.awt.*; import java.awt.geom.*; /** Michael Weiss */ public class Minutenzeiger extends Uhrzeiger { public Minutenzeiger() super(Color.BLACK); super.hatForm.moveTo(0,-15); super.hatForm.lineTo(2,0); super.hatForm.lineTo(0,45); super.hatForm.lineTo(-2,0); super.hatForm.closePath(); } x hatForm ist vom Typ Path2D.float und kann mehrere Grafikobjekte aufnehmen. Es wird in der Überklasse erzeugt. Der Stundenzeiger sieht praktisch gleich aus. Michael Weiss

17 Der Sekundenzeiger y x Michael Weiss 4.1.2011 import java.awt.*;
import java.awt.geom.*; /** Michael Weiss */ public class Sekundenzeiger extends Uhrzeiger { public Sekundenzeiger() super(Color.RED); super.hatForm.moveTo(-0.5,-16); super.hatForm.lineTo(0.5,-16); super.hatForm.lineTo(0.5,48); super.hatForm.lineTo(-0.5,48); super.hatForm.closePath(); super.hatForm.append(new Ellipse2D.Float(-3,-3,6,6), false); } x Michael Weiss

18 Affine Transformation
x x y affine Transformation Michael Weiss y

19 Erster Schritt: Drehung
y y Drehung x x Da die Drehung im Uhrzeigersinn erfolgt, ist der Drehwinkel negativ. Java stellt die benötigte Koordinatenumrechnung (ein Spezialfall einer affinen Transformation) direkt zur Verfügung. Michael Weiss

20 Drehung in Java import java.awt.*; import java.awt.geom.*; /** Michael Weiss */ public abstract class Uhrzeiger { protected Path2D.Float hatForm; private Color zColor; // Jeder Uhrzeiger wird durch einen Pfad repräsentiert, der in ein Quadrat von 100 Einheiten Seitenlänge // passt, und dessen Koordinatenursprung sich in der Quadratmitte befindet. public Uhrzeiger(Color pColor) { zColor = pColor; hatForm = new Path2D.Float(); } public void zeichne(Graphics2D pGraphics2D, int pH, int pV, int pBreite, int pHoehe, double pWinkel) { AffineTransform_lRotation_=_AffineTransform.getRotateInstance(-pWinkel_/_180.0_*_Math.PI); AffineTransform_lAffineTransform_=_new_AffineTransform(0.01*pBreite,0,0,-0.01*pHoehe,pH+0.5*pBreite,pV+_0.5*pHoehe); lAffineTransform.concatenate(lRotation); Path2D.Float lForm_=_ (Path2D.Float)(hatForm.clone()); lForm.transform(lAffineTransform); pGraphics2D.setColor(zColor); pGraphics2D.fill(lForm); Der Drehwinkel muss im Bogenmass angegeben werden, daher / 180 * . Michael Weiss

21 Zweiter Schritt: Transformation auf JPanelkoordinaten
y x0 x0+w C(0|50) x' y0 C'(x0+w/2|y0) Transfor-mation x A'(x0+w/2|y0+h/2) A(0|0) B(50|0) B'(x0+w|y0+h/2) y0+h y' Michael Weiss

22 Berechnung der Transformation
Wenn man berechnen will, welche Koordinaten (x'|y') ein Punkt im neuen Koordinatensystem haben wird, wenn er im alten Koordinatensystem die Koordinaten (x|y) hat, kann man von folgenden Gleichungen ausgehen: x' = ax + cy + e y' = bx + dy + f Anhand dreier vorgegebener Punkte A(xA, yA), B(xB, yB) und C(xC, yC) sowie ihrer Bildpunkte A'(xA', yA'), B'(xB', yB') und C'(xC', yC') lassen sich die Parameter a, b, c, d, e und f berechnen. In unserem Fall sind die Punkte A(0|0), B(50|0) und C(0|50) sowie deren Bildpunkte A'(x0+w/2|y0+h/2), B'(x0+w|y0+h/2) und C'(x0+w/2|y0) gegeben. Setzen wir A und A' ein, erhalten wir x0+w/2 = e y0+h/2 = f Setzen wir B und B' ein und verwenden die bereits ausgerechneten Werte für e und f, erhalten wir nach kurzer Rechnung a = w/100 und b = 0. Mit C und C' finden wir schliesslich c = 0 und d = -h / 100. Michael Weiss

23 Die Transformation in Java
Die Parameter a bis f werden in dieser Reihenfolge dem Konstruktor eines Objekts der Klasse AffineTransform übergeben. import java.awt.*; import java.awt.geom.*; /** Michael Weiss */ public abstract class Uhrzeiger { protected Path2D.Float hatForm; private Color zColor; // Jeder Uhrzeiger wird durch einen Pfad repräsentiert, der in ein Quadrat von 100 Einheiten Seitenlänge // passt, und dessen Koordinatenursprung sich in der Quadratmitte befindet. public Uhrzeiger(Color pColor) { zColor = pColor; hatForm = new Path2D.Float(); } public void zeichne(Graphics2D pGraphics2D, int pH, int pV, int pBreite, int pHoehe, double pWinkel) { AffineTransform lRotation = AffineTransform.getRotateInstance(-pWinkel / * Math.PI); AffineTransform_lAffineTransform_=_new_AffineTransform(0.01*pBreite,0,0,-0.01*pHoehe,pH+0.5*pBreite,pV+_0.5*pHoehe); lAffineTransform.concatenate(lRotation); Path2D.Float lForm_=_ (Path2D.Float)(hatForm.clone()); lForm.transform(lAffineTransform); pGraphics2D.setColor(zColor); pGraphics2D.fill(lForm); w h x y0 Michael Weiss

24 Hintereinanderausführung von Drehung und Koordinatentransformation
import java.awt.*; import java.awt.geom.*; /** Michael Weiss */ public abstract class Uhrzeiger { protected Path2D.Float hatForm; private Color zColor; // Jeder Uhrzeiger wird durch einen Pfad repräsentiert, der in ein Quadrat von 100 Einheiten Seitenlänge // passt, und dessen Koordinatenursprung sich in der Quadratmitte befindet. public Uhrzeiger(Color pColor) { zColor = pColor; hatForm = new Path2D.Float(); } public void zeichne(Graphics2D pGraphics2D, int pH, int pV, int pBreite, int pHoehe, double pWinkel) { AffineTransform lRotation = AffineTransform.getRotateInstance(-pWinkel / * Math.PI); AffineTransform lAffineTransform = new AffineTransform(0.01*pBreite,0,0,-0.01*pHoehe,pH+0.5*pBreite,pV+ 0.5*pHoehe); lAffineTransform.concatenate(lRotation); Path2D.Float lForm_=_ (Path2D.Float)(hatForm.clone()); lForm.transform(lAffineTransform); pGraphics2D.setColor(zColor); pGraphics2D.fill(lForm); Michael Weiss

25 Ausführung der gesamten Transformation
lAffineTransform.concatenate(lRotation); Path2D.Float lForm = (Path2D.Float)(hatForm.clone()); lForm.transform(lAffineTransform); pGraphics2D.setColor(zColor); pGraphics2D.fill(lForm); Klonen des Zeigers (erzeugt eine echte Kopie) Transformieren der Kopie Farbe setzen transformierte Kopie zeichnen (fill: ausmalen) Michael Weiss

26 Zifferblatt Mit der selben Technik lässt sich auch ein Zifferblatt zeichnen. Dazu wird das ursprüngliche Bild eines senkrechten Ziffernstrichs 11- resp. 48-mal gedreht und jeweils in ein- und dasselbe Path2D.float-Objekt aufgenommen. import java.awt.*; import java.awt.geom.*; /** * Klasse Ziffernblatt: Zeichnet 12 grosse und 48 kleine Uhrstriche. * Gezeichnet wird ein Ziffernblatt, das in ein Quadrat der Seitenlänge * 100 und dem Koordinatenursprung in der Mitte passt. * Michael Weiss */ public class Ziffernblatt { private Path2D.Float hatForm, hatStundenstrich, hatMinutenstrich; public Ziffernblatt() hatStundenstrich = new Path2D.Float(); hatStundenstrich.moveTo(-0.5,40); hatStundenstrich.lineTo(0.5,40); hatStundenstrich.lineTo(0.5,48); hatStundenstrich.lineTo(-0.5,48); hatStundenstrich.closePath(); hatMinutenstrich = new Path2D.Float(); hatMinutenstrich.moveTo(-0.25,45); hatMinutenstrich.lineTo(0.25,45); hatMinutenstrich.lineTo(0.25,48); hatMinutenstrich.lineTo(-0.25,48); hatMinutenstrich.closePath(); hatForm = new Path2D.Float(); Michael Weiss

27 Zifferblatt (Forts.) int h = 0; while(h < 12) {
Path2D.Float lStundenstrich = (Path2D.Float)(hatStundenstrich.clone()); AffineTransform lRotation = AffineTransform.getRotateInstance(-h * 30.0 / * Math.PI); lStundenstrich.transform(lRotation); hatForm.append(lStundenstrich, false); h++; } int m = 1; while(m < 60) { if(m % 5 != 0) { Path2D.Float lMinutenstrich = (Path2D.Float)(hatMinutenstrich.clone()); AffineTransform lRotation = AffineTransform.getRotateInstance(-m * 6.0 / 180.0* Math.PI); lMinutenstrich.transform(lRotation); hatForm.append(lMinutenstrich, false); m++; public void zeichne(Graphics2D pGraphics2D, int pH, int pV, int pBreite, int pHoehe) { AffineTransform lAffineTransform = new AffineTransform(0.01 * pBreite, 0, 0, * pHoehe, pH + 0.5*pBreite, pV + 0.5*pHoehe); Path2D.Float lForm = (Path2D.Float)(hatForm.clone()); lForm.transform(lAffineTransform); pGraphics2D.setColor(Color.BLACK); pGraphics2D.fill(lForm); Michael Weiss

28 Dargestellte Zeit einstellen
Wir benutzen eine Klasse, welche sich eine Zeit merken kann und diese in die Winkeleinstellungen der drei Uhrzeiger umrechnen kann. /** * Speichere die Zeit in Stunden, Minuten * und Sekunden und * berechne die Winkel der drei Uhrzeiger. * Michael Weiss */ public class Zeit { private int zStd, zMin; private double zSek; public Zeit(int pStd, int pMin, double pSek) zStd = pStd; zMin = pMin; zSek = pSek; } public double winkelStundenzeiger() { return zStd * 30 + zMin * zSek * 0.5 / 60.0; } public double winkelMinutenzeiger() return zMin * 6 + zSek * 0.1; public double winkelSekundenzeiger() return zSek * 6; Michael Weiss

29 Dargestellte Zeit einstellen
Wir benutzen eine Klasse, welche sich eine Zeit merken kann und diese in die Winkeleinstellungen der drei Uhrzeiger umrechnen kann. /** * Speichere die Zeit in Stunden, Minuten * und Sekunden und * berechne die Winkel der drei Uhrzeiger. * Michael Weiss */ public class Zeit { private int zStd, zMin; private double zSek; public Zeit(int pStd, int pMin, double pSek) zStd = pStd; zMin = pMin; zSek = pSek; } public double winkelStundenzeiger() { return zStd * 30 + zMin * zSek * 0.5 / 60.0; } public double winkelMinutenzeiger() return zMin * 6 + zSek * 0.1; public double winkelSekundenzeiger() return zSek * 6; Erzeugt wird sie innerhalb der Klasse Uhr: hatZeit = new Zeit(13,50,22); Michael Weiss

30 Dargestellte Zeit einstellen (2)
Damit das ZeichenJPanel die Uhrzeit nutzen kann, erzeugen wir eine Getter-Methode: public Zeit zeit() { return hatZeit; } Zudem übergeben wir dem ZeichenJPanel im Konstruktor einen Verweis auf die Klasse Uhr, so dass das ZeichenJPanel später die Methode zeit() der Klasse Uhr aufrufen kann: hatUhrZeichenJPanel = new ZeichenJPanel(this); Entsprechend bekommt die Klasse ZeichenJPanel einen geänderten Konstruktor: public class ZeichenJPanel extends JPanel { private Uhrzeiger hatSekundenzeiger, hatMinutenzeiger, hatStundenzeiger; private Ziffernblatt hatZiffernblatt; private Uhr kenntUhr; public ZeichenJPanel(Uhr pUhr) { hatZiffernblatt = new Ziffernblatt(); hatSekundenzeiger = new Sekundenzeiger(); hatMinutenzeiger = new Minutenzeiger(); hatStundenzeiger = new Stundenzeiger(); kenntUhr = pUhr; } Michael Weiss

31 Dargestellte Zeit einstellen (3)
Und auch paintComponent() ändert sich noch ein wenig: protected void paintComponent(Graphics pGraphics) { // damit die Buttons, Slider, Labels etc. weiterhin gezeichnet werden, // rufen wir als erstes paintComponent() der Überklasse auf. super.paintComponent(pGraphics); Graphics2D kenntGraphics2D = (Graphics2D)pGraphics; // ab hier zeichnen wir selbst. int xmin = 0, width = 0, ymin = 0, height = 0; if(this.getWidth() > this.getHeight()) { height = this.getHeight() - 10; ymin = 5; xmin = (this.getWidth() - height) / 2; width = height; } else { width = this.getWidth() - 10; xmin = 5; ymin = (this.getHeight() - width) / 2; height = width; } kenntGraphics2D.setColor(Color.BLACK); kenntGraphics2D.setStroke(new BasicStroke(width / 100)); kenntGraphics2D.drawOval(xmin, ymin, width, height); hatZiffernblatt.zeichne(kenntGraphics2D, xmin, ymin, width, height); Zeit lZeit = kenntUhr.zeit(); hatStundenzeiger.zeichne(kenntGraphics2D, xmin, ymin, width, height, lZeit.winkelStundenzeiger()); hatMinutenzeiger.zeichne(kenntGraphics2D, xmin, ymin, width, height, lZeit.winkelMinutenzeiger()); hatSekundenzeiger.zeichne(kenntGraphics2D, xmin, ymin, width, height, lZeit.winkelSekundenzeiger()); Michael Weiss

32 Fertig? Die Benutzeroberfläche ist nun fertig.
Das Programm reagiert jedoch noch auf keine Benutzereingaben! Diese so genannte Ereignisverarbeitung behandeln wir später. Michael Weiss


Herunterladen ppt "Benutzeroberflächen mit Java"

Ähnliche Präsentationen


Google-Anzeigen