Swing Basics (Teil 1)

Dieses Beispiel zeigt, wie ein Fenster angelegt wird und wie man Bedienelemente zufügt.
Das Projekt befindet sich unter "SwingBasics1" in Gitlab: https://gitlab.cs.hs-rm.de/knauf/swt-ws19-knauf/tree/master/SwingBasics1

Der Code ist auch hier zu finden: SwingBasics1.zip


Fenster hinzufügen

Nach dem Erstellen des Projekts wird ein "JFrame form" zugefügt:
JFrame hinzufügen

In jedem so generierten "JFrame Form" befindet sich eine main-Methode. Um Verwirrung vorzubeugen (falls man wie in meinem Beispiel diese Methode schon in einer eigenen Klasse eingebaut hat), sollte man die direkt wieder löschen.


Das Anzeige des Fensters erfolgt über die main-Methode der Klasse SwingBasics:

package de.hsrm.cs.swt.swingbasics1;

public class SwingBasics {

    public static void main(String[] args) {
        
        MainForm form = new MainForm();
        
        form.setVisible(true);
    }
    
}
Öffnet man diese Fensterklasse "MainForm" per Doppelklick, kann man zwischen Quellcode und Design-Modus umschalten.

Source/Design

Der Design-Modus ermittelt seine Informationen durch Parsen der Methode "initComponents" der Fensterklasse, die in NetBeans eingeklappt/versteckt ist. Der Code ist grau hinterlegt, d.h. man kann ihn in NetBeans nicht direkt ändern. Aber man kann natürlich mit einem externen Editor daran herumpfuschen und hoffen, dass der Designer ihn danach noch interpretieren kann ;-).

Button-Basics

Als erstes fügen wir dem Fenster ein Button zugefügt. Dazu im Fenster "Palette" den Button anklicken und dann in das "Design"-Fenster unseres "MainForm" klicken:
Button hinzufügen
Dieser Button taucht jetzt im Quellcode auf: Es wurde eine Variable erzeugt:
    // Variables declaration - do not modify                     
    private javax.swing.JButton jButton1;
    // End of variables declaration  
Im Code von "initComponents" steht er ebenfalls:

        jButton1 = new javax.swing.JButton();

        ....

        jButton1.setText("jButton1");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(87, 87, 87)
                .addComponent(jButton1)
                .addContainerGap(240, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(73, 73, 73)
                .addComponent(jButton1)
                .addContainerGap(204, Short.MAX_VALUE))
        );
Der erste Schritt nach dem Hinzufügen eines jeden Swing-Controls sollte sein, die Variable sinnvoll zu benennen. Dazu wird der Button angeklickt, und im Fenster "jbutton1[JButton] Properties" wird der Karteireiter "Code" ausgewählt. Dort ändert man in der Zeile "Variable Name" diesen:
Button-Variable umbenennen
Das ändert den Namen der Variablen im Java-Code:
    // Variables declaration - do not modify                     
    private javax.swing.JButton jButtonTest;
    // End of variables declaration   
Auf dem Karteireiter "Properties" kann man diverse Darstellungs- und Verhaltens-Einstellungen vornehmen. Die wichtigste ist wohl die Property "text" - das ist der auf dem Button angezeigte Text:
Button-Text
In der "initComponents"-Methode sieht das dann so aus:
        jButtonTest.setText("Test-Button");
Jetzt wird Code hinzugefügt, der beim Klick des Buttons eine MessageBox anzeigt. Dazu den Button auswählen und in den Properties auf den Karteireiter "Events" gehen. In der Zeile "actionPerformed" das Dropdown anklicken und auf den ersten vorgeschlagenen Namen "jButtonTestActionPerformed" klicken:

Es entsteht dadurch eine Methode "jButtonTestActionPerformed", in der wir den Code für den Button-Klick einbauen. Im Beispiel wird eine MessageBox mit "OK"-Button angezeigt:
    private void jButtonTestActionPerformed(java.awt.event.ActionEvent evt) {                                            
        JOptionPane.showMessageDialog(this, "Button wurde geklickt");
    }
In "initComponents" wird dieser Code generiert, der den Button-Klick auf die vorherstehende Methode weiterleitet:
        jButtonTest.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonTestActionPerformed(evt);
            }
        });
Eventhandling funktioniert in Java generell so, dass ein Event-Absender für jedes Event eine Liste von Event-Empfängern verwaltet. Jedes Event erfordert (je nach Typ) ein bestimmtes Interface. Im Beispiel (Klick eines Buttons) ist dies das Interface java.awt.event.ActionListener, das eine Methode actionPerformed definiert:
public interface ActionListener extends EventListener {
    public void actionPerformed(ActionEvent e);
}
Obiger Code erzeugt eine anonyme Implementierung dieses Interface (also eine Klasse ohne Namen), die in ihrer Implementierung der Interface-Methode actionPerformed die Methode jButtonTestActionPerformed aufruft. Damit wird der Button-Klick auf eine Methode der JFrame-Subklasse weitergeleitet.

Man könnte einen Button-Klick auf verarbeiten, indem die Klasse "MainForm" direkt das Interface ActionListener implementiert:

public class MainForm extends javax.swing.JFrame implements java.awt.event.ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        ...
    }
}
Dann könnte man den ActionListener des Buttons so registrieren:

        jButtonTest.addActionListener(this);
Aber das funktioniert nicht mehr, wenn es zwei Buttons auf dem Fenster gibt und für die Klicks von beiden Buttons Eventhandler registriert werden sollen.

Die diversen "show..."-Methoden von JOptionPane dienen dazu, Meldungsfenster anzuzeigen. Im obenstehenden Beispiel wird ein Dialog mit einem "OK"-Button angezeigt.

Im Folgenden ein Beispiel für eine Messagebox mit "Ja/Nein/Abbrechen":
       int result = JOptionPane.showConfirmDialog(this, "Sind Sie sicher?", "Abfrage", JOptionPane.YES_NO_CANCEL_OPTION);
        switch (result)
        {
            case JOptionPane.YES_OPTION:
            {
                JOptionPane.showMessageDialog(this, "JA wurde geklickt");
                break;
            }
            case JOptionPane.NO_OPTION:
            {
                JOptionPane.showMessageDialog(this, "NEIN wurde geklickt");
                break;
            }
            case JOptionPane.CANCEL_OPTION:
            {
                JOptionPane.showMessageDialog(this, "ABBRECHEN wurde geklickt");
                break;
            }
            default:
            {
                JOptionPane.showMessageDialog(this, "Unbekannte Rückgabe - kann nicht sein: " + result);    
            }
        }
Die Methode "showConfirmDialog" gibt einen Zahlenwert zurück, der dem geklickten Button entspricht.


Textfeld-Basics

Im nächsten Schritt wird ein Textfeld hinzugefügt, und der Button-Klick soll den Text dieses Textfelds anzeigen:
JTextField hinzufügen
Das Control wird in "jTextFieldTest" umbenannt.
In den Properties kann man über die Property "text" den angezeigten Text ändern - da unser Textfeld leer sein soll, löschen wir ihn heraus. Dadurch wird das Textfeld in der Design-Vorschau sehr schmal. Aber wir können es einfach wieder breiter ziehen.

Im "jButtonTestActionPerformed"-Eventhandler geben wir jetzt den Text des Textfelds aus:
    private void jButtonTestActionPerformed(java.awt.event.ActionEvent evt) {                                            
        JOptionPane.showMessageDialog(this, "Button wurde geklickt, im Textfeld steht: " + this.jTextFieldTest.getText());
    }  
Das Setzen des Textfeld-Texts geht umgekehrt über die Methode this.jTextFieldTest.setText("Neuer Text");.

Layout Manager

Swing verfügt über mehrere Layout-Manager, die unterschiedliche komplexe Layouts unterstützen.

NetBeans setzt in initComponents ein javax.swing.GroupLayout:
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
Dieses GroupLayout fasst Eingabeelemente zu Spalten- und Zeilen-Gruppen zusammen und kümmert sich um die Ausrichtung dieser Gruppen. Im NetBeans-Designer werden diese Gruppen als graue Balken dargestellt. Beim Hinzufügen eines neuen Controls sollte man darauf achten, dass es sinnvoll in eine bestehende Gruppe passt oder eine neue Gruppe erzeugt. Für unsere simplen Anwendungen sollte dieses Layout ausreichen, und da die IDE uns die Arbeit abnimmt, müssen wir uns hoffentlich nicht um die programmatische Erzeugung kümmern.
Offizielles Java-Tutorial: https://docs.oracle.com/javase/tutorial/uiswing/layout/group.html
API: https://docs.oracle.com/javase/8/docs/api/javax/swing/GroupLayout.html

Über das Kontextmenü kann man für Controls einige Einstellungen vornehmen:

Anchor:
Hiermit wird gesteuert, wie die Position des Controls sich ändern soll, wenn das Fenster vergrößert oder verkleinert wird. Der Default "Left/Top" verankert das Control am linken oberen Rand - bei einer Größenänderung des Fensters ändert sich die Position dadurch nicht. Ändert man es auch "Right/Bottom", dann wird das Control nach rechts/unten wandern, wenn das Fenster vergrößert wird:
Anchor

Resizing:
Hier kann aktiviert werden, dass das Control seine Größe an die Fensterposition anpasst: wird das Fenster vergrößert, ändert sich auch die Control-Größe:
Auto Resizing


Einer der komplexesten (und mächtigsten) Layout-Manager ist java.awt.GridBagLayout, der ein Gitternetz über den gesamten Fensterinhalt definiert und jedes Eingabeelement in eine Gitternetzzelle platziert. Controls können sich über mehrere Spalten oder Zeilen erstrecken und ihre Größe prozentual an die Fenstergröße anpassen.

Der Layout Manager läßt sich ändern, indem man im Fenster im Kontextmenü "SetLayout" wählt.
Layout ändern
Kurze Beschreibung dieser Layout Manager: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html

Stand 19.11.2019
Historie:
17.11.2019: Erstellt
19.11.2019: "main"-Methode aus Form gelöscht.