Das Card-Layout in Swing

Was uns Swing an die Hand gibt - Gängige Tutorials und Tipps - Aufteilung - Java-Codes - Download
Eingabe der Zahl
Berechnung des Ergebnis
Anzeige des Ergebnis

Beim CardLayout handelt es sich um ein Layout für grafische Oberflächen, die mit Swing erstellt werden.

Was uns Swing an die Hand gibt

Zunächst gibt es das eigentliche CardLayout, das man einem Panel zuweisen kann:

    private JPanel cardPanel;
    private CardLayout cardLayout;

    // ...

        cardPanel = new JPanel();
        cardPanel.setLayout(cardLayout);

Ferner kann man dem Panel nun Karten zuweisen:

        JPanel card1 = createInputCard();
            JPanel card2 = createWorkCard();
            JPanel card3 = createOutputCard();

            panel.add(card1, "Input");
            panel.add(card2, "Work");
            panel.add(card3, "Ouput");
            

Nun kann man das Card-Layout anweisen, auf dem Card-Panel eine bestimmte Karte anzuzeigen:

        cardLayout.show(cardPanel, "Input");

Natürlich sollte man es nicht ganz so einfach machen, wie oben dargestellt, besser ist die Verwendung von String-Konstanten (static final) oder Enums, damit nicht ein Tippfehler im Namen einer Karte zu Problemen führt.

Allerdings sind nun alle Details in dieser einen Klasse, die die Karten erstellt. Bei sehr kleinem Umfang der Karten mag das noch angehen, schöner ist es aber, diese Dinge voneinander zu trennen. Dazu komme ich im folgenden, siehe den Abschnitt "Aufteilung".

Gängige Tutorials und Tipps

Zum Beispiel wird im Oracle Tutorial empfohlen, die oben erwähnten String-Konstanten für die Namen der Karten zu verwenden.

Weiteres findet sich bei der Suche nach "swing card layout example".

Möchte man die Klassenvariable cardLayout einsparen, kann man auch das Panel danach fragen:

CardLayout cardLayout = (CardLayout) cardPanel.getLayout();

Bei der Kompaktheit der Klasse CardHandling habe ich darauf verzichtet.

Aufteilung

Möchte man Zuständigkeiten von Klassen (etwa nach dem single responsibility princliple) gerne aufteilen, so wird die Verwendung des Card-Layouts schnell ein wenig komplizierter.

Meine Lösung dazu stelle ich nun am Beispiel einer Anwendung, die eine Zahl vom Benutzer entgegen nimmt, zu dieser ein Ergebnis berechnet und dann eben dieses Ergebnis anzeigt, dar. So haben wir drei logische Schritte und dazu auch drei Karten:

  1. Wert vom Benutzer einlesen (Karte "Input").
    Eingabe der Zahl


  2. Ergebnis berechnen (Karte "Work").
    Berechnung des Ergebnis
    Hier werden künstlich zwei Sekunden gewartet, da die "Berechnung", ob die Zahl gerade oder ungerade ist, natürlich blitzschnell geht.

  3. Dem Benutzer das Ergebnis anzeigen (Karte "Output").
    Anzeige des Ergebnis


Aufteilen möchte ich die verschiedenen Zuständigkeiten pro Karte in die folgenden Bereiche:

Außerdem wird alles, was von verschiedenen Programmen, die das Card-Layout nutzen wollen, verwendet werden kann, in allgemeine Klassen und Interfaces aufgeteilt, die im Paket de.duehl.swing.ui.layout.card für alle Oberflächen mit Card-Layout zur Verfügung gestellt:

Es werden im gleichen Paket (de.duehl.swing.ui.layout.card) die folgenden Interfaces verwendet, damit die Klassen nicht unnötig viel übereinander wissen müssen:

Nur für das Demo-Programm gibt es im Paket de.duehl.swing.ui.start.card die folgenden Klassen in meiner Aufteilung:

Java-Code


Im folgenden zeige ich den Quellcode meiner Aufteilung:

Allgemein verwendbare Klassen


Im Paket de.duehl.swing.ui.layout.card werden die allgemein verwendbaren Klassen und Interfaces rund um das Card-Layout bereit gestellt:

Card

Diese Klasse stellt eine Karte dar.

Card

package de.duehl.swing.ui.layout.card;

import java.awt.Component;

public class Card {

    private final String name;
    private final CardGui gui;
    private final CardLogic logic;

    public Card(String name, CardGui gui, CardLogic logic) {
        this.name = name;
        this.gui = gui;
        this.logic = logic;
        logic.setGui(gui);
        gui.setLogic(logic);
    }

    String getName() {
        return name;
    }

    Component createCardGui(CardSwitcher switcher) {
        return gui.createGui(switcher);
    }

    void runWhenShown(Card previousCard) {
        CardLogic previousLogic = previousCard.logic;
        CardResult result = previousLogic.getResult();
        logic.setResultFromPreviousCard(result);
        logic.runWhenShown();
    }

    void cleanUp() {
        gui.cleanUp();
    }

}

CardsBase

Diese Klasse ist die abstrakte Basis für die Klassen, die die projektspezifischen Karten für eine Oberfläche definiert.

CardsBase

package de.duehl.swing.ui.layout.card;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

public abstract class CardsBase {

    private final List<Card> cards;

    protected CardsBase() {
        cards = new ArrayList<>();
    }

    protected void addCard(Card card) {
        cards.add(card);
    }

    Card getNext(Card currentCard) {
        int currentIndex = cards.indexOf(currentCard);
        int nextIndex = currentIndex + 1;
        if (nextIndex >= cards.size()) {
            throw new RuntimeException("Keine weitere Karte vorhanden.");
        }
        Card card = cards.get(nextIndex);
        return card;
    }

    void addAllCardsToPanel(JPanel panel, CardSwitcher switcher) {
        for (Card card : cards) {
            panel.add(card.createCardGui(switcher), card.getName());
        }
    }

    Card getFirstCard() {
        return cards.get(0);
    }

}

CardHandling

Diese Klasse dient zur Handhabung der Karten zur Verwendung in allen Oberflächen, die das CardLayout nutzen möchten.

CardHandling

package de.duehl.swing.ui.layout.card;

import java.awt.CardLayout;

import javax.swing.JPanel;

public class CardHandling {

    private final CardLayout cardLayout;
    private final JPanel cardPanel;
    private final CardsBase cards;
    private Card currentCard;

    public CardHandling(CardsBase cards) {
        this.cards = cards;
        cardPanel = new JPanel();
        cardLayout = new CardLayout();
        cardPanel.setLayout(cardLayout);
        currentCard = cards.getFirstCard();
    }

    public JPanel getCardPanel() {
        return cardPanel;
    }

    public void addAllCardsToPanel(CardSwitcher switcher) {
        cards.addAllCardsToPanel(cardPanel, switcher);
    }

    public void switchCard() {
        Card previousCard = currentCard;
        previousCard.cleanUp();

        currentCard = cards.getNext(previousCard);
        cardLayout.show(cardPanel, currentCard.getName());
        currentCard.runWhenShown(previousCard);
        cardPanel.validate();
    }

}

Allgemein verwendbare Interfaces


CardSwitcher

Dieses Interface dient zur Entkopplung zwischen der eigentlichen Projekt-Oberfläche, dem CardHandling, CardsBase und den spezifischen Oberflächen der eigentlichen Karten.

CardSwitcher

package de.duehl.swing.ui.layout.card;

public interface CardSwitcher {

    void switchCard();

    void close();

}

CardGui

Dieses Interface wird von den spezifischen Oberflächen der eigentlichen Karten implementiert.

CardGui

package de.duehl.swing.ui.layout.card;

import java.awt.Component;

public interface CardGui {

    Component createGui(CardSwitcher switcher);

    void cleanUp();

    void setLogic(CardLogic logic);

}

CardLogic

Dieses Interface wird von den spezifischen Logiken der eigentlichen Karten implementiert.

CardLogic

package de.duehl.swing.ui.layout.card;

public interface CardLogic {

    void setGui(CardGui gui);

    void setResultFromPreviousCard(CardResult previousResult);

    void runWhenShown();

    CardResult getResult();

}

CardResult

Dieses Interface wird von den spezifischen Ergebnissen der eigentlichen Karten implementiert.

CardResult

package de.duehl.swing.ui.layout.card;

public interface CardResult {

}

Spezielle Klassen für das Beispielprogramm


Die Hauptanwendung CardLayoutDemo

Startpunkt des kleinen Programms ist die Klasse CardLayoutDemo. In dieser findet sich auch die main-Methode. Hier wird die Gui des Programms aufgebaut und die Karten erstellt und weiterschaltet.

CardLayoutDemo

package de.duehl.swing.ui.start.card;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import de.duehl.swing.ui.layout.card.CardHandling;
import de.duehl.swing.ui.layout.card.CardSwitcher;

public class CardLayoutDemo implements CardSwitcher {

    private final JFrame frame;
    private final CardHandling cardHandling;

    public CardLayoutDemo() {
        frame = new JFrame();
        cardHandling = new CardHandling(new Cards());

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGui();
            }
        });
    }

    private void createGui() {
        frame.setTitle("CardLayoutDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        frame.add(createCardPanel(), BorderLayout.CENTER);

        frame.setLocation(250, 100);
        frame.setPreferredSize(new Dimension(300, 90));
        frame.pack();
    }

    private Component createCardPanel() {
        cardHandling.addAllCardsToPanel((CardSwitcher) this);
        return cardHandling.getCardPanel();
    }

    @Override
    public void switchCard() {
        cardHandling.switchCard();
    }

    @Override
    public void close() {
        frame.setVisible(false);
        frame.dispose();
    }

    public void start() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.setVisible(true);
            }
        });
    }

    public static void main(String[] args) {
        new CardLayoutDemo().start();
    }

}

Cards

Diese Klasse dient zur Verwaltung der Karten.

Man beachte, wie kurz und prägnant diese Klasse auf der Anwendungsseite ist!

Cards

package de.duehl.swing.ui.start.card;

import de.duehl.swing.ui.layout.card.Card;
import de.duehl.swing.ui.layout.card.CardsBase;
import de.duehl.swing.ui.start.card.input.InputCardGui;
import de.duehl.swing.ui.start.card.input.InputCardLogic;
import de.duehl.swing.ui.start.card.output.OutputCardGui;
import de.duehl.swing.ui.start.card.output.OutputCardLogic;
import de.duehl.swing.ui.start.card.work.WorkCardGui;
import de.duehl.swing.ui.start.card.work.WorkCardLogic;

public class Cards extends CardsBase {

    Cards() {
        addCard(new Card("Input", new InputCardGui(), new InputCardLogic()));
        addCard(new Card("Work", new WorkCardGui(), new WorkCardLogic()));
        addCard(new Card("Output", new OutputCardGui(), new OutputCardLogic()));
    }

}

Die Klassen der Eingabe ("Input")


InputCardGui

Grafische Oberfläche der Karte zur Eingabe der Zahl.

InputCardGui

package de.duehl.swing.ui.start.card.input;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardSwitcher;

public class InputCardGui implements CardGui {

    private CardSwitcher switcher;
    private JTextField inputField;
    private InputCardLogic logic;

    @Override
    public void setLogic(CardLogic logic) {
        if (logic instanceof InputCardLogic) {
            this.logic = (InputCardLogic) logic;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Logik!");
        }
    }

    @Override
    public Component createGui(CardSwitcher switcher) {
        this.switcher = switcher;

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createTitleLabel(), BorderLayout.NORTH);
        panel.add(createInputPanel(), BorderLayout.CENTER);
        panel.add(createButtonPanel(), BorderLayout.SOUTH);

        return panel;
    }

    private Component createTitleLabel() {
        JLabel label = new JLabel("Bitte geben sie eine ganze Zahl ein:");
        return label;
    }

    private Component createInputPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createInputTextField(), BorderLayout.CENTER);

        return panel;
    }

    private Component createInputTextField() {
        inputField = new JTextField();
        return inputField;
    }

    private Component createButtonPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createNextButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createNextButton() {
        JButton button = new JButton("weiter");

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                apply();
            }
        });

        return button;
    }

    private void apply() {
        String value = inputField.getText().trim();
        boolean success = logic.calculateResult(value);
        if (success) {
            switcher.switchCard();
        }
        else {
            JOptionPane.showMessageDialog(null,
                    "Ihre Eingabe ist fehlerhaft!",
                    "Es ist ein Fehler aufgetreten...",
                    JOptionPane.INFORMATION_MESSAGE);
        }
    }

    @Override
    public void cleanUp() {
        // Hier muss nichts aufgeräumt werden...
    }

}
InputCardLogic

Logik der Karte zur Eingabe der Zahl.

InputCardLogic

package de.duehl.swing.ui.start.card.input;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardResult;

public class InputCardLogic implements CardLogic {

    private InputCardResult result;

    @Override
    public void setGui(CardGui gui) {
        if (gui instanceof InputCardGui) {
            // Diese Logik benötigt keinen Zugriff auf die grafische Oberfläche.
            //this.gui = (InputCardGui) gui;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Gui!");
        }
    }

    @Override
    public void setResultFromPreviousCard(CardResult previousResult) {
        // Hier muss nichts gemacht werden, da es die erste Karte ist.
        // Die Methode wird auch gar nicht aufgerufen:
        throw new RuntimeException("Darf nicht aufgerufen werden.");
    }

    @Override
    public void runWhenShown() {
        // Hier muss nichts gemacht werden, da es die erste Karte ist.
        // Die Methode wird auch gar nicht aufgerufen:
        throw new RuntimeException("Darf nicht aufgerufen werden.");
    }

    public boolean calculateResult(String value) {
        if (value.matches("\\d+")) {
            int enteredValue = Integer.parseInt(value);
            result = new InputCardResult(enteredValue);
            return true;
        }
        else {
            return false;
        }
    }

    @Override
    public CardResult getResult() {
        return result;
    }

}
InputCardResult

Ergebnis der Karte zur Eingabe der Zahl.

InputCardResult

package de.duehl.swing.ui.start.card.input;

import de.duehl.swing.ui.layout.card.CardResult;

public class InputCardResult implements CardResult {

    private final int enteredValue;

    public InputCardResult(int enteredValue) {
        this.enteredValue = enteredValue;
    }

    public int getEnteredValue() {
        return enteredValue;
    }

    @Override
    public String toString() {
        return "InputCardResult [enteredValue=" + enteredValue + "]";
    }

}

Die Klassen der Verarbeitung ("Work")


WorkCardGui

Grafische Oberfläche der Karte zur Verarbeitung.

WorkCardGui

package de.duehl.swing.ui.start.card.work;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;

import javax.swing.JLabel;
import javax.swing.JPanel;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardSwitcher;

public class WorkCardGui implements CardGui {

    private CardSwitcher switcher;

    @Override
    public void setLogic(CardLogic logic) {
        if (logic instanceof WorkCardLogic) {
            // Diese Gui braucht keinen Zugriff auf die Logik.
            // this.logic = (WorkCardLogic) logic;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Logik!");
        }
    }

    @Override
    public Component createGui(CardSwitcher switcher) {
        this.switcher = switcher;

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createTitleLabel(), BorderLayout.CENTER);

        return panel;
    }

    private Component createTitleLabel() {
        JLabel label = new JLabel("Berechnung des Ergebnisses ...");
        label.setForeground(Color.BLUE);
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    }

    public void done() {
        switcher.switchCard();
    }

    @Override
    public void cleanUp() {
        // Hier muss nichts aufgeräumt werden...
    }

}
WorkCardLogic

Logik der Karte zur Verarbeitung.

WorkCardLogic

package de.duehl.swing.ui.start.card.work;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardResult;
import de.duehl.swing.ui.start.card.input.InputCardResult;

public class WorkCardLogic implements CardLogic {

    private WorkCardGui gui;
    private WorkCardResult result;
    private InputCardResult previousResult;

    @Override
    public void setGui(CardGui gui) {
        if (gui instanceof WorkCardGui) {
            this.gui = (WorkCardGui) gui;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Gui!");
        }
    }

    @Override
    public void setResultFromPreviousCard(CardResult previousResult) {
        if (previousResult instanceof InputCardResult) {
            this.previousResult = (InputCardResult) previousResult;
        }
        else {
            throw new RuntimeException("Aufruf mit dem falschen CardResult!");
        }
    }

    @Override
    public void runWhenShown() {
        int enteredValue = previousResult.getEnteredValue();
        boolean odd = enteredValue % 2 != 0;
        result = new WorkCardResult(odd);

        waitTwoSecondsBeforeGoOn();
    }

    private void waitTwoSecondsBeforeGoOn() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                sleep(2); // Damit diese Karte überhaupt zu sehen ist...
                gui.done();
            }
        }).start();
    }

    private void sleep(int seconds) {
        long millis = 1000 * seconds;
        try {
            Thread.sleep(millis);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public CardResult getResult() {
        return result;
    }

}
WorkCardResult

Ergebnis der Karte zur Verarbeitung.

WorkCardResult

package de.duehl.swing.ui.start.card.work;

import de.duehl.swing.ui.layout.card.CardResult;

public class WorkCardResult implements CardResult {

    private final boolean odd;

    public WorkCardResult(boolean odd) {
        this.odd = odd;
    }

    public boolean isOdd() {
        return odd;
    }

    @Override
    public String toString() {
        return "WorkCardResult [odd=" + odd + "]";
    }

}

Die Klassen der Ausgabe ("Ouput")


OuputCardGui

Grafische Oberfläche der Karte zur Ausgabe der Zahl.

OutputCardGui

package de.duehl.swing.ui.start.card.output;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardSwitcher;

public class OutputCardGui implements CardGui {

    private CardSwitcher switcher;
    private JLabel resultLabel;

    @Override
    public void setLogic(CardLogic logic) {
        if (logic instanceof OutputCardLogic) {
            // Diese Gui braucht keinen Zugriff auf die Logik.
            //this.logic = (OutputCardLogic) logic;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Logik!");
        }
    }

    @Override
    public Component createGui(CardSwitcher switcher) {
        this.switcher = switcher;

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createResultPanel(), BorderLayout.CENTER);
        panel.add(createButtonPanel(), BorderLayout.SOUTH);

        return panel;
    }

    private Component createResultPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createResultLabel(), BorderLayout.CENTER);

        return panel;
    }

    private Component createResultLabel() {
        resultLabel = new JLabel("");
        resultLabel.setHorizontalAlignment(JLabel.CENTER);
        return resultLabel;
    }

    private Component createButtonPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createOkButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createOkButton() {
        JButton button = new JButton("Beenden");

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                apply();
            }
        });

        return button;
    }

    private void apply() {
        switcher.close();
    }

    public void showEven() {
        resultLabel.setText("Die Zahl ist gerade.");
    }

    public void showOdd() {
        resultLabel.setText("Die Zahl ist ungerade.");
    }

    @Override
    public void cleanUp() {
        // Hier muss nichts aufgeräumt werden...
    }

}
OutputCardLogic

Logik der Karte zur Ausgabe der Zahl.

OutputCardLogic

package de.duehl.swing.ui.start.card.output;

import de.duehl.swing.ui.layout.card.CardGui;
import de.duehl.swing.ui.layout.card.CardLogic;
import de.duehl.swing.ui.layout.card.CardResult;
import de.duehl.swing.ui.start.card.work.WorkCardResult;

public class OutputCardLogic implements CardLogic {

    private OutputCardGui gui;
    private WorkCardResult previousResult;

    @Override
    public void setGui(CardGui gui) {
        if (gui instanceof OutputCardGui) {
            this.gui = (OutputCardGui) gui;
        }
        else {
            throw new RuntimeException("Aufruf mit der falschen Gui!");
        }
    }

    @Override
    public void setResultFromPreviousCard(CardResult previousResult) {
        if (previousResult instanceof WorkCardResult) {
            this.previousResult = (WorkCardResult) previousResult;
        }
        else {
            throw new RuntimeException("Aufruf mit dem falschen CardResult!");
        }
    }

    @Override
    public void runWhenShown() {
        boolean odd = previousResult.isOdd();
        if (odd) {
            gui.showOdd();
        }
        else {
            gui.showEven();
        }
    }

    @Override
    public CardResult getResult() {
        // Wird bei der letzten Karte nicht aufgerufen.
        throw new RuntimeException("Darf nicht aufgerufen werden.");
    }

}

Download

Ich habe das Programm unter die selbe Lizenz wie Perl gestellt.
Dabei handelt es sich um eine doppelte Open Source Lizenz: GPL / artistic.

Aktuelle Version CardLayoutPure_01.zip downloaden

Für eine Version, die auch einen zwischenzeitlichen Abbruch durch den Benutzer richtig behandelt, siehe meinen Artikel Das Card-Layout und Abbruch durch den Benutzer.

zu meiner Javaseite zu meiner Javaseite

zur Startseite des Servers zur Startseite des Servers

Impressum

Valid CSS! Valid XHTML 1.0!

TOP Zum Seitenanfang

zuletzt geändert: