Beim CardLayout handelt es sich um ein Layout für grafische Oberflächen, die mit Swing erstellt werden.
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".
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.
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:
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:
Im folgenden zeige ich den Quellcode meiner Aufteilung:
Im Paket de.duehl.swing.ui.layout.card werden die allgemein verwendbaren Klassen und Interfaces rund um das Card-Layout bereit gestellt:
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(); } } |
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); } } |
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(); } } |
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(); } |
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); } |
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(); } |
Dieses Interface wird von den spezifischen Ergebnissen der eigentlichen Karten implementiert.
CardResult |
package de.duehl.swing.ui.layout.card; public interface CardResult { } |
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(); } } |
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())); } } |
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... } } |
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; } } |
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 + "]"; } } |
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... } } |
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; } } |
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 + "]"; } } |
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... } } |
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."); } } |
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.