GUI-Workshop: Let's Swing again...

Grundlegende GUI-Struktur(en)

Dem Fenster mit JFrame einen Rahmen geben

Ein Programmfenster kann in Java mittels der Klasse JFrame erzeugt werden. Um sie verwenden zu können, müssen zunächst die AWT- (Abstract Window Toolkit) und Swing-Bibliotheken importiert werden, die diese Klasse beinhalten.

In folgendem Beispiel werden zwei Fenster unterschiedlicher Größe an unterschiedlicher Position erzeugt. Als Konstruktorparameter werden die gewünschten Fensterüberschriften angegeben. Ein Fenster kann über seinen Dienst setVisible(false) bei Bedarf auch temporär ausgeblendet werden.

 
import java.awt.*;    // awt-Bibliotheken importieren
import javax.swing.*; // swing-Bibliotheken importieren

public class DoppelFenster
{
    public static void main (String[] args)
    {
       JFrame frame1 = new JFrame("JFrame 1");
       frame1.setLocation(200, 200);
       frame1.setSize(300,200);
       frame1.setVisible(true);
    
       JFrame frame2 = new JFrame("JFrame 2");
       frame2.setLocation(550, 200);
       frame2.setSize(300,400);
       frame2.setVisible(true);        
    }
}
      
Erstelle im aktuellen BlueJ-Projekt eine neue Klasse wie oben beschrieben und teste diese durch Variation der verschiedenen Parameter.

Die Fenster sind bislang noch völlig leer und alle internen Strukturen müssten bei einem Vorgehen wie oben gezeigt in der jeweiligen Methode (hier main) erzeugt werden, was unpraktisch und fehleranfällig ist.
Es ist daher besser (und üblich), für jedes Fenster, das man benötigt, eine eigene Klasse aus JFrame abzuleiten, die alle fenster­spezifischen Dinge beinhaltet.
Handelt es sich dabei um das Hauptfenster einer Applikation, kann in dieser Klasse aus Gründen der Einfachheit auch die main-Methode untergebracht werden, in der dann das Fenster in einer Variablen des entsprechenden Typs instanziiert wird. Siehe dazu das folgende Beispiel:

 
import java.awt.*;     // awt-Bibliotheken importieren
import javax.swing.*;  // swing-Bibliotheken importieren

public class BasisFenster extends JFrame
{
  public BasisFenster(String title)
  {
    super(title); // Oberklassenkonstruktor aufrufen
      
    this.setSize(300,400);      // die Angabe "this" ist optional, da
    this.setLocation(200, 200); // die Klasse selbst ein JFrame ist
    this.setVisible(true);
  } 
    
  public static void main (String[] args)
  {
    BasisFenster bf = new BasisFenster("BasisFenster");       
  }
}   
Erstelle im aktuellen BlueJ-Projekt eine neue Klasse wie oben beschrieben und erprobe diese in der Anwendung.

Die ContentPane verwenden und strukturieren

Im nächsten Schritt werden wir nun unseren leeren Fensterrahmen um verschiedene Strukturen / Inhalte erweitern.

Die verfügbare Fläche des Fensters wird als ContentPane bezeichnet. Um auf sie zugreifen zu können, stellt die Klasse JFrame die Methode getContentPane() zur Verfügung. Die ContentPane selbst stellt ebenfalls verschiedene Dienste wie z.B. add(...) und setLayout(...) bereit.

Mit der Anweisung

getContentPane().setLayout( new FlowLayout() )

muss zunächst eine Instanz des gewünschten Layout-Managers der ContentPane zugewiesen werden, bevor danach mit der Anweisung

getContentPane().add(...)

Objektinstanzen verschiedener Oberflächenelemente der ContentPane zugefügt werden können.

Im folgenden Beispiel werden zwei Schaltflächen (JButton) erzeugt und jeweils über einen Konstruktorparameter mit "Blau" bzw. "Rot" beschriftet.

 
import java.awt.*;     // awt-Bibliotheken importieren
import javax.swing.*;  // swing-Bibliotheken importieren

public class ButtonDemo extends JFrame
{
  JButton button1; // Variable für ein ButtonObjekt
  JButton button2; // Variable für ein ButtonObjekt
  
  public ButtonDemo(String title)
  {
    super(title); // Oberklassenkonstruktor aufrufen

    button1 = new JButton("Blau");   // Buttons instanziieren
    button2 = new JButton("Rot");
 
    getContentPane().setLayout(new FlowLayout()); // Layout setzen    
    getContentPane().add(button1);                // Button einfügen
    getContentPane().add(button2);                // Button einfügen
    
    setSize(300,400);      
    setLocation(200, 200);
    setVisible(true);
  } 
    
  public static void main (String[] args)
  {
    ButtonDemo demo = new ButtonDemo("ButtonDemo");       
  }
}   

Anders als in vielen anderen Programmiersprachen wird in Java die Anordnung bzw. exakte Position von GUI-Elementen nicht statisch programmiert, sondern von speziellen Klassen, den Layout-Managern, dynamisch verwaltet. In unserer GUI verwenden wir das FlowLayout, das nur für sehr einfache GUI's (so wie die unsere) geeignet ist. Es ordnet die einzelnen GUI-Elemente in der Reihenfolge, in der sie per .add(...) in die ContentPane eingefügt werden, einfach horizontal von links nach rechts im Fenster an, solange ausreichend Platz dafür vorhanden ist.

Möchte man komplexere GUI-Strukturen erzeugen, kann z.B. das GridBagLayout verwendet werden.

Erstelle im aktuellen BlueJ-Projekt eine neue Klasse wie oben beschrieben und erprobe diese in der Anwendung.

Verändere mit Hilfe der Maus die Fensterbreite und beobachte die Buttons.
Was stellst du fest?

Ändere die Beschriftung der Buttons von
Rot / Blau    auf    Rotf\u00e4rber / Blauf\u00e4rber.
Möchte man deutsche Umlaute verwenden, sollte deren Unicode-Wert angegeben werden, da sie sonst nicht immer korrekt dargestellt werden.
Wie wirkt sich diese Änderung auf die Buttons aus?

Um die Größe von Buttons nicht von deren Beschriftung abhängig zu machen, bietet die Klasse verschiedene Möglichkeiten, so. u.a. die Methode setPreferredSize(...). Informiere dich in der Online-Dokumentation über deren Verwendung und ändere die Größe eines Buttons auf eine Breite von 140px und eine Höhe von 40px.

Ereignisüberwachung: Der ActionListener

Unsere GUI besteht bisher nur aus der View-Komponente des MVC-Modells (s.o.), d.h. ausschliesslich aus einer funktionslosen graphischen Oberfläche. Um einen Controller zu realisieren bietet Java vielfältige Möglichkeiten, Ereignisse (events) auch in der Anwendung zu erzeugen und darauf zu reagieren (listener). Wir werden uns jedoch auf eine rudimentäre Ereignisbehandlung beschränken, die nur auf Fensterereignisse (z.B. Mausklicks) reagiert, da dies für unsere Zwecke ausreichend ist und sie mit einem minimalen Model (entspricht dem eigentlichen Anwendungsprogramm, hier nur das Umschalten des Hintergrundes von rot auf blau und umgekehrt) ergänzen.

Dazu müssen als erstes die entsprechenden AWT-Bibliotheken importiert werden (s.u.).
Da in Java nur eine Einfachvererbung möglich ist, wird der entsprechende Ereignisbehandler (auch Beobachter genannt) durch die Anweisung

implements ActionListener

in Form einer Schnittstelle (interface) im Klassenkopf dazugebunden. Der ActionListener ist eine abstrakte Klasse, deren Methode actionPerformed(...) überschrieben bzw. implementiert werden muss. Sie wird automatisch ausgeführt, sobald in einem überwachten Element ein Ereignis auftritt und erhält einen ActionEvent als Parameter, der zur Unterscheidung verschiedener Ereignisquellen (hier Buttons) ausgewertet werden kann (s.u.).

import java.awt.*; 
import javax.swing.*;
import java.awt.event.*; // Event-Bibliothek importieren

public class ButtonDemo extends JFrame implements ActionListener
{
  JButton button1 ; // Variable für ein ButtonObjekt
  JButton button2 ; // Variable für ein ButtonObjekt
   
  public ButtonDemo(String title) 
  {
    super(title);
    
    button1 = new JButton("Blauf\u00e4rber");
    button1.addActionListener(this); 
    button1.setActionCommand("blue");
    
    button2 = new JButton("Rotf\u00e4rber");
    button2.addActionListener(this);
    button2.setActionCommand("red");
    
    getContentPane().setLayout(new FlowLayout()); // Layout setzen
    getContentPane().add(button1);                // Button einfügen
    getContentPane().add(button2);                // Button einfügen  
    
    setSize(300, 400); 
    setLocation(200, 200);
    setVisible(true);
  }
 
 public void actionPerformed( ActionEvent ae)      
  {                                                
    String action = ae.getActionCommand();         
                                                   
    if ( action.equals( "blue" ) )                 
      getContentPane().setBackground( Color.blue );
    else                                           
      getContentPane().setBackground( Color.red ); 
                                                   
    repaint();   
  } 
  
  public static void main ( String[] args )
  {
     ButtonDemo demo = new ButtonDemo("ButtonDemo");   
  }
}

Damit Ereignisse in einem Oberflächenelement, wie z.B. ein Mausklick auf einen Button oder das Drücken der Eingabetaste in einem Textfeld, vom ActionListener erkannt und behandelt werden können, muß ein Bezug zwischen beiden her­gestellt werden. Dies erfolgt über die Anweisung

.addActionListener(this)

an dem entsprechenden Element (s.o.), wobei der Parameter this auf den ActionListener dieser Klasse verweist.

Weiterhin sollte das jeweilige ActionCommand explizit gesetzt werden (hier z.B. durch button1.setActionCommand("blue"), s.o), um von der Beschriftung und verwendeten Sprache unabhängig zu sein. Macht man dies nämlich nicht, wird automatisch die Beschriftung des Button dafür verwendet. Dies ist problematisch, da jede kleine Änderung hierin zu einem nicht mehr funktionierenden Programm führen würde.

Erweitere die Klasse ButtonDemo wie oben gezeigt um einen ActionListener und erprobe sie.

JLabel, JTextField und JTextArea

Was jetzt noch fehlt für eine vollständige GUI sind Möglichkeiten für die Eingabe und Ausgabe von Informationen. Dazu werden wir nun

ergänzen. Das unten gezeigte Listing erzeugt eine GUI wie rechts dargestellt und spricht weitgehend für sich selbst.

Die Eingabe beinhaltet ein einzeiliges Textfeld, die Ausgabe besteht aus einem sechszeiligen Textbereich, der automatisch mit Scrollbalken versehen wird, wenn der Inhalt dessen Größe überschreitet. Inhalte können mit .getText() ausgelesen bzw. mit .setText(...) dem ganzen Bereich zugewiesen und mit .append(...) am Ende angehängt werden.

import java.awt.*; 
import java.awt.event.*;
import javax.swing.*;

public class TextDemo extends JFrame implements ActionListener
{
  JButton berechneKnopf; // Variable für ein ButtonObjekt
  JButton beendeKnopf;   // Variable für ein ButtonObjekt
  
  JTextField eingabeFeld;  // einzeiliges Textfeld
  JLabel eingabeFeldLabel; 
  JTextArea ausgabeFeld;   // mehrzeiliges Textfeld
  JLabel ausgabeFeldLabel;
  
  public TextDemo(String title) 
  {
    super(title);
    
    eingabeFeld = new JTextField(15);         // 15 Spalten breit
    eingabeFeldLabel = new JLabel("Eingabe");
    
    berechneKnopf = new JButton("Kopieren");
    berechneKnopf.addActionListener( this );
    berechneKnopf.setActionCommand("copy");
    
    ausgabeFeld = new JTextArea(6, 15);       // 6 Zeilen hoch,
    ausgabeFeldLabel = new JLabel("Ausgabe"); // 15 Spalten breit
        
    beendeKnopf = new JButton("Beenden");
    beendeKnopf.addActionListener( this );
    beendeKnopf.setActionCommand("exit");
    
    getContentPane().setLayout(new FlowLayout());
    getContentPane().add( eingabeFeldLabel );
    getContentPane().add( eingabeFeld );
    getContentPane().add( ausgabeFeldLabel );
    getContentPane().add( new JScrollPane(ausgabeFeld) );// scrollbar
    getContentPane().add( berechneKnopf );    
    getContentPane().add( beendeKnopf );   
    
    setSize( 210, 250 );      // Fenstergröße setzen
    setResizable(false);      // Fenstergröße fixieren 
    setLocation(200, 200);    // Fensterposition setzen  
    this.setVisible(true);                        
  }

  
  public void actionPerformed( ActionEvent ae)   // Controller
  {
    if ( ae.getActionCommand().equals( "copy" ) )
    {
       ausgabeFeld.append(eingabeFeld.getText() + "\r\n");  // Model
       eingabeFeld.setText("");  
    }
    else    
      System.exit(0);
  }

  
  public static void main ( String[] args )
  {
      // lokale Instanz der Klasse erzeugen
      TextDemo tc = new TextDemo("Text Demo");     
  }
  
}

Das Model besteht wiederum nur aus zwei Codezeilen, in denen der Inhalt Eingabefeldes erst in den Ausgabebereich kopiert und dann gelöscht wird.
In einem komplexeren Programm mit mehreren Klassen würden hier die öffent­lichen Dienste / Methoden dieser Klassen aufgerufen / eingebunden.

Bemerkenswert ist zum Schluss noch die Anweisung setResizable(false), mit der die Fenstergröße fixiert wird, sodass es nicht mit der Maus vergrössert oder verkleinert werden kann, wobei wegen des FlowLayout die Anordnung aller Elemente "verrutschen" würde.

Implementiere die Klasse TextDemo wie oben gezeigt als Erweiterung / Abwandlung des vorherigen Beispiels ButtonDemo und erprobe sie.

 

  © K. Milzner, 2019