GUIs in Java und ihre Programmierung Einführung ● GUI = Graphical User Interface – ● Java Tutorial – ● ● graphische Benutzerschnittstelle java.sun.com/docs/books/tutorial/index.html Java Documentation (in Version 1.4.2 oder 1.5) – java.sun.com/j2se/1.4.2/docs/api/index.html – java.sun.com/j2se/1.5/docs/api/index.html Oder als Link auf der Java-Homepage – java.sun.com Java-Documentation AWT ● AWT = Abstract Window Toolkit ● import java.awt.*; ● ● ● alle wichtigen Komponenten enthalten (Frames, Buttons, ...) Benutzen des Event-Handling-Konzepts, wie es der Window-Manager anbietet aber: viele Bugs, Zusammenarbeit mit WindowManager mangelhaft, langsam JFC/Swing ● JFC = Java Foundation Classes ● wird hier ausschließlich benutzt ● import javax.swing.*; ● Bietet mehr Komponenten als AWT, z.B. PopUps, ToolTips, Tables ● Bietet zusätzliche Konzepte ● – Look-And-Feel – Drag-And-Drop – Internationalization unterstützt Computertechnik für behinderte Menschen (BrailleDisplays, ...) Der Einstieg – das erste Fenster import javax.swing.*; class FirstWindow { public static void main( String args[] ) { JFrame frame = new JFrame("OurFirstWindow"); frame.pack(); frame.setVisible(true); } } Aussehen und Verbesserungen ● ● Jedes Fenster hat i.A. einen Rahmen (Border), eine Titelleiste und eine Zeichenfläche (Pane) Verbesserungen: – Look-and-feel – Java-Programm beim Schließen des Fensters auch beenden Erweiterung/Verbesserung ● Das Aussehen aller Fenster auf das javainterne Lookand-Feel setzen – ● JFrame.setDefaultLookAndFeelDecorated(true); Beim Schließen des Fensters auch das JavaProgramm beenden – frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Das komplette Programm import javax.swing.*; class FirstWindow { public static void main( String args[] ) { JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame("OurFirstWindow"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ... } } frame.pack(); frame.setVisible(true); Design von awt und swing ● Container vs. Components ● 3 top-level Container existieren: ● – Frame – Dialog – Applet Alle anderen Window-Elemente sind low-level und heißen Components – Componenten sind nicht stand-alone, sondern werden in die Container eingefügt und angezeigt – Panel (kann andere Components aufnehmen) – Label, Button, Checkbox, ... – TextArea, TextField, ... Beispiele für Components ● Label JLabel label = new JLabel("Hello World"); frame.getContentPane().add(label); ● Button JButton button = new JButton("Press Me"); frame.getContentPane().add(button); ● CheckBox JCheckBox cb = new JCheckBox("Choose Me"); frame.getContentPane().add(cb); ● TextField JTextField tf = new JTextField("Write Me"); frame.getContentPane().add(tf); Problem ● ● Nachfolgende Componenten überschreiben schon vorhandene 2 Lösungen möglich: – Layout-Manager – Panel ● ● Ist eine besondere Componente, weil sie andere Components aufnehmen kann Dieses Panel kann dann dem Frame hinzugefügt werden, weil es selbst eine Componente ist. Layout-Manager ● ● ● ● ● Jedes Pane (und damit das Frame, welches diese Pane enthält) hat ein eigenes Aussehen, über das der implementierte Layout-Manager entscheidet. BorderLayout default für alle neuen Frames; 5 Felder, in denen die Components angezeigt werden FlowLayout die Components werden in einer Reihe angeordnet (default für alle neuen Panel) GridLayout alle Components bekommen dieselbe Größe und liegen in Reihen und Spalten Weitere Layouts: BoxLayout, CardLayout, GridBagLayout, SpringLayout (siehe Java-Tutorial) Verwendung BorderLayout ● Importiere awt: ● Die definierten Components ... import java.awt.*; JLabel JCheckBox JRadioButton JTextField JTextArea ● lab cb rb tf ta = = = = = new new new new new JLabel("Message"); JCheckBox("Choose Me"); JRadioButton("Choose Radio"); JTextField("Einzeilig"); JTextArea("Return erlaubt"); ... mit der überladenen add-methode einfügen frame.getContentPane().add(lab, frame.getContentPane().add(cb, frame.getContentPane().add(rb, frame.getContentPane().add(tf, frame.getContentPane().add(ta, ● BorderLayout.CENTER); BorderLayout.LINE_START); BorderLayout.LINE_END); BorderLayout.PAGE_START); BorderLayout.PAGE_END); Zum Ausprobieren (setzt Abstände zwischen Components) void setHgap(int) void setVgap(int) Weiteres Problem ● ● Wie kann ich mehr als 5 Components in das BorderLayout einfügen? Lösung: – Layout verändern, z.B. durch frame.getContentPane().setLayout( new GridLayout(0, 1, 10, 10) ); bzw. Container pane = frame.getContentPane(); pane.setLayout( new BoxLayout( pane, BoxLayout.Y_AXIS ) ); – Panel Arbeiten mit Panels ● Panel erstellen (hat als Default ein FlowLayout) JPanel panel = new JPanel(); ● In das Panel die Components einfügen panel.add(new JLabel(“Read me”)); panel.add(new JButton(“Click me”)); panel.add(new JRadioButton(“Mark me”)); ● Das Panel dem Frame übergeben frame.getContentPane().add(panel); ● Note: Panels können zu Panels hinzugefügt werden JLabel JTextField JLabel JTextArea JScrollPane JCheckBox JCheckBox JButton JButton JButton lab1 tf lab2 ta sp cb1 cb2 but1 but2 but3 = = = = = = = = = = new new new new new new new new new new JLabel("Insert one line of text:"); JTextField(); JLabel("Insert some lines of text"); JTextArea(); JScrollPane(ta); JCheckBox("Use sinus"); JCheckBox("Use square root"); JButton("Cancel"); JButton("Compute"); JButton("Exit"); frame.getContentPane().setLayout( new GridLayout( 0, 1 )); JPanel panel1 = new JPanel( new FlowLayout() ); JPanel panel2 = new JPanel( new FlowLayout() ); panel1.add(cb1); panel1.add(cb2); panel2.add(but1); panel2.add(but2); panel2.add(but3); frame.getContentPane().add(lab1); frame.getContentPane().add(tf); frame.getContentPane().add(lab2); frame.getContentPane().add(sp); frame.getContentPane().add(panel1); frame.getContentPane().add(panel2); GUI-Events ● ● ● Problem: wie bemerke ich, dass der User einen Button betätigt, oder eine bestimmte Taste drückt, oder dass die Mouse sich über einem Icon befindet, dass ich daraufhin ändern möchte? Lösung 1: unser Programm fragt ständig alle möglichen Eingabequellen (Maus, Tastatur, ...) ab, und sucht sich die Ereignisse heraus, auf die es reagieren will – Zeitaufwendig – Codeaufwendig Lösung 2: im Hintergrund läuft das Betriebssystem und der Window-Manager, die sich sowieso schon um alle Eingabegeräte kümmern, sowie die GUI verwalten, also sollen die uns doch sagen, was wir wissen wollen – Einfach, praktikabel – Wir registrieren uns bei der GUI für bestimmte Events Was für Events gibt es? Act that Results in the Event User clicks a button, presses Enter while typing in a text field, or chooses a menu item User closes a frame (main window) User presses a mouse button while the cursor is over a component User moves the mouse over a component Component becomes visible Component gets the keyboard focus Table or list selection changes Any property in a component changes such as the text on a label Listener Type ActionListener WindowListener MouseListener MouseMotionListener ComponentListener FocusListener ListSelectionListener PropertyChangeListener Wie benutze ich Events? ● ● ● ● Alle “Listener” sind Interfaces. Ein Listener kann mehrere Events enthalten, mit je einer Methode. Bsp: MouseListener enthält – MouseClicked über einer Component wurde eine Maustaste betätigt – MouseEntered der User bewegt den Mauscursor über eine Componente – MouseExited der Mauscursor verläßt die Componente – MousePressed ein Mausbutton wurde über einer Componente niedergedrückt – MouseReleased ein Mausbutton wurde über einer Componente losgelassen Ich brauche also eine “Class”, die das Interface (und die Methoden daraus) implementiert Dann kann diese Class sich bei der Componente registrieren, welche die entsprechenden Methoden aufruft Event-Beispiel ● Ich will auf einen Button-click reagieren, also brauche ich den ActionListener in meiner “Class”: import java.awt.event.*; class FirstWindow implements ActionListener ... ● Zum Interface ActionListener gehört nur eine Methode: public void actionPerformed(ActionEvent e) { System.out.println(“Button clicked!”); } ● Und ich muss die Class beim Button registrieren, damit der Button auch die entsprechende Methode aufruft: z.B. button.addActionListener( new FirstWindow() ); button.addActionListener( this ); button.addActionListener( someClassVariable ); import javax.swing.*; import java.awt.*; import java.awt.event.*; class FirstWindow implements ActionListener { JButton button1; JButton button2; public void actionPerformed(ActionEvent e) { JButton button = (JButton)e.getSource(); if (button == button1) button = button2; else button = button1; int i = 1+Integer.parseInt(button.getText()); button.setText( ""+i ); } public void createGUI() { JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame("OurFirstWindow"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); button1 = new JButton("1"); button2 = new JButton("10"); button1.addActionListener( this ); button2.addActionListener( this ); frame.getContentPane().add( button1, BorderLayout.PAGE_START ); frame.getContentPane().add( button2, BorderLayout.PAGE_END ); } } frame.pack(); frame.setVisible(true); public static void main( String args[] ) { FirstWindow fw = new FirstWindow(); fw.createGUI(); } Minimale Registrierung ● Wenn ich in meiner Class kein Interface implementieren will, kann ich auch beim Button eine minimale Class, die ich adhoc erstelle, registrieren: button.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { ... button.setText(“You have clicked me”); ... } } ); ● ● Sollte nur bei wenig Programmcode für den ActionListener benutzt werden, weil schnell unübersichtlich Vorteil: jede Component ruft ihren eigenen ActionListener auf (kann man auch erreichen, indem man bei jeder Component eine eigene Class registriert) Selbst zeichnen leichgemacht ● ● In einem bestimmten Ausschnitt unseres Panel wollen wir selbst zeichnen In Panel's können nur Components eingefügt werden, also definieren wir unsere eigene Component: class MyComp extends JComponent { } ● Dann überschreiben wir die Methode paintComponent, über die alle Components gezeichnet werden: protected void paintComponent(Graphics g) { } ● Jetzt können wir alle Methoden von JComponent und Graphics nutzen, um unser Object zu zeichnen Dimensionen ● Unser Object braucht noch eine bevorzugte Breite (height) und Höhe (width), damit es gleich von Anfang an dargestellt wird: public Dimension getPreferredSize() { return new Dimension(400,200); } ● Koordinatensystem zum Zeichnen Wichtige Methoden zum Zeichnen ● ● Aus JComponent: getInsets() getWidth() getHeight() liefert die Breiten der Rahmenseiten (Border) liefert die zeichenbare Breite inkl. Border liefert die zeichenbare Höhe inkl. Border Aus Graphics: drawLine(x1,y1,x2,y2) drawRect(x,y,w,h) draw... fillRect(x,y,w,h) fill... setColor(color) zeichnet eine Linie zeichnet eine Rechteck Methoden zum Malen (auch Text) füllt das Rechteck Methoden zum Füllen setzt die Farbe für das nächste zu zeichnende Element import javax.swing.*; import java.awt.*; class FirstWindow { class MyComp extends JComponent { public Dimension getPreferredSize() { return new Dimension(400,200); } } protected void paintComponent(Graphics g) { g.setColor( Color.RED ); g.drawLine( 0, 0, getWidth(), getHeight() ); g.setColor( Color.WHITE ); g.drawLine( 0, getHeight(), getWidth(), 0 ); } public void createGUI() { JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame("OurFirstWindow"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add( new MyComp() ); } } frame.pack(); frame.setVisible(true); public static void main( String args[] ) { FirstWindow fw = new FirstWindow(); fw.createGUI(); } Threads ● ● ● Threads sind Prozesse, die zeitgleich (konkurrierend) neben dem main-Programm ausgeführt werden Sie sind nicht stand-alone, sondern immer vom Mutterprozess abhängig, der sie starten und kontrollieren muss. Wichtig: Swing-event- und Component-paintingcode laufen in einem Thread ab, dem Event-dispatching thread Thread-Safety ● Beispiel für einen Konflikt: eine Componente wurde noch nicht gezeichnet, wird aber zur selben Zeit von Außerhalb (alles was nicht der eventdispatching thread ist) so verändert, dass ein Update fällig wäre, obwohl doch noch nichts geupdated werden kann ● Probleme zwar sehr selten, sind trotzdem zu vermeiden ● Programmcode, wie die Probleme vermieden werden können: public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater( new Runnable() { public void run() { createAndShowGUI(); } } ); }