Beim CardLayout handelt es sich um ein Layout für grafische Oberflächen, die mit Swing erstellt werden. Zu der grundlegenden Trennung der Verantwortlichkeiten siehe meinen Vorgängerartikel Das Card-Layout.
Im Folgenden werde ich darauf eingehen, wie man einen Abbruch durch den Benutzer so behandelt, dass sich nicht nur die Oberfläche schließt, sondern die im Hintergrund ablaufenden Prozesse der gerade aktuellen Karte abgebrochen werden und auch keine neue Karte angezeigt und die dazugehörige Logik gestartet wird.
Swing bietet mit frame.addWindowListener(new WindowListener() { ... }) die Möglichkeit, auf einen Klick auf das kleine X oben rechts im Fenster der grafischen Oberfläche zu reagieren.
Dort hinterlegen wir einen eigenen WindowListener, nämlich eine Instanz der Klasse ClosingWindowListener. Der Listener ruft dann eine passende Methode der grafischen Oberfläche auf, diese informiert dann CardHandling über den Abbruch und schließt selbst die Oberfläche.
CardHandling kümmert sich um den ganzen Rest. Allerdings müssen die Logiken der Karten eine neue Methode quit() implementieren und passend auf den Abbruch reagiere.
Zusätzlich zu den im Artikel Das Card-Layout vorgestellten Klassen und Interfaces werden die folgenden beiden Quelldateien benötigt:
Dieses Interface wird von dem Rahmenprogramm implementiert.
Quitter |
package de.duehl.swing.logic; public interface Quitter { void quit(); } |
Diese Klasse wird von dem Rahmenprogramm verwendet, um auf einen Klick auf das kleine x oben rechts durch den Benutzer zu reagieren.
ClosingWindowListener |
package de.duehl.swing.ui.listener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import de.duehl.swing.logic.Quitter; public class ClosingWindowListener implements WindowListener { private final Quitter quitter; public ClosingWindowListener(Quitter quitter) { this.quitter = quitter; } @Override public void windowActivated(WindowEvent event) { } @Override public void windowClosed(WindowEvent event) { } @Override public void windowClosing(WindowEvent event) { quitter.quit(); } @Override public void windowDeactivated(WindowEvent event) { } @Override public void windowDeiconified(WindowEvent event) { } @Override public void windowIconified(WindowEvent event) { } @Override public void windowOpened(WindowEvent event) { } } |
An den aus dem Artikel Das Card-Layout vorgestellten Klassen und Interfaces werden die folgenden Änderungen vorgenommen:
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.
Hier werden nun die folgenden Änderungen durchgeführt:
@Override public void quit() { boolean quit = askUserToQuit(); if (quit) { cardHandling.quit(); close(); } }
private boolean askUserToQuit() { String[] yesNoOptions = { "Programm beenden", "Weiter machen" }; int answer = JOptionPane.showOptionDialog( frame, "Möchten sie das Programm wirklich beenden?", "Programm beenden?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, yesNoOptions, yesNoOptions[0]); boolean userAnswer = false; if (answer == JOptionPane.YES_OPTION) { userAnswer = true; } else if (answer == JOptionPane.NO_OPTION) { userAnswer = false; } return userAnswer; }
Zusammen ergibt sich damit die folgende Version:
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.JOptionPane; import javax.swing.SwingUtilities; import de.duehl.swing.logic.Quitter; import de.duehl.swing.ui.layout.card.CardHandling; import de.duehl.swing.ui.layout.card.CardSwitcher; import de.duehl.swing.ui.listener.ClosingWindowListener; /* * Copyright 2016 Christian Dühl. All rights reserved. * * This program is free software. You can redistribute it and/or * modify it under the same terms as perl: * * general: http://dev.perl.org/licenses/ * GPL: http://dev.perl.org/licenses/gpl1.html * artistic: http://dev.perl.org/licenses/artistic.html */ public class CardLayoutDemo implements CardSwitcher, Quitter { 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.addWindowListener(new ClosingWindowListener(this)); 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); } }); } @Override public void quit() { boolean quit = askUserToQuit(); if (quit) { cardHandling.quit(); close(); } } private boolean askUserToQuit() { String[] yesNoOptions = { "Programm beenden", "Weiter machen" }; int answer = JOptionPane.showOptionDialog( frame, "Möchten sie das Programm wirklich beenden?", "Programm beenden?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, yesNoOptions, yesNoOptions[0]); boolean userAnswer = false; if (answer == JOptionPane.YES_OPTION) { userAnswer = true; } else if (answer == JOptionPane.NO_OPTION) { userAnswer = false; } return userAnswer; } public static void main(String[] args) { new CardLayoutDemo().start(); } } |
Diese Klasse dient zur Handhabung der Karten zur Verwendung in allen Oberflächen, die das CardLayout nutzen möchten.
Hier werden nun die folgenden Änderungen durchgeführt:
synchronized public void quit() { interrupted = true; currentCard.quit(); }
Zusammen ergibt sich damit die folgende Version:
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; private boolean interrupted; public CardHandling(CardsBase cards) { this.cards = cards; cardPanel = new JPanel(); cardLayout = new CardLayout(); cardPanel.setLayout(cardLayout); currentCard = cards.getFirstCard(); interrupted = false; } public JPanel getCardPanel() { return cardPanel; } public void addAllCardsToPanel(CardSwitcher switcher) { cards.addAllCardsToPanel(cardPanel, switcher); } synchronized public void switchCard() { if (interrupted) { return; } Card previousCard = currentCard; previousCard.cleanUp(); currentCard = cards.getNext(previousCard); cardLayout.show(cardPanel, currentCard.getName()); currentCard.runWhenShown(previousCard); cardPanel.validate(); } synchronized public void quit() { interrupted = true; currentCard.quit(); } } |
Diese Klasse stellt eine Karte dar.
Hier werden nun die folgenden Änderungen durchgeführt:
public void quit() { logic.quit(); }
Zusammen ergibt sich damit die folgende Version:
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(); } public void quit() { logic.quit(); } } |
Dieses Interface wird von den spezifischen Logiken der eigentlichen Karten implementiert.
Hier werden nun die folgenden Änderungen durchgeführt:
Zusammen ergibt sich damit die folgende Version:
CardLogic |
package de.duehl.swing.ui.layout.card; public interface CardLogic { void setGui(CardGui gui); void setResultFromPreviousCard(CardResult previousResult); void runWhenShown(); CardResult getResult(); void quit(); } |
Logik der Karte zur Eingabe der Zahl.
Hier werden nun die folgenden Änderungen durchgeführt:
@Override public void quit() { // Hier muss nichts gemacht werden, weil es keinen // laufenden anderen Thread zur Berechnung gibt. }
Zusammen ergibt sich damit die folgende Version:
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; } @Override public void quit() { // Hier muss nichts gemacht werden, weil es keinen // laufenden anderen Thread zur Berechnung gibt. } } |
Logik der Karte zur Verarbeitung.
Hier werden nun die folgenden Änderungen durchgeführt:
@Override synchronized public void quit() { interrupted = true; if (runningThread != NOT_YET_STARTET) { runningThread.interrupt(); } }
Zusammen ergibt sich damit die folgende Version:
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 static final Thread NOT_YET_STARTET = new Thread(); private WorkCardGui gui; private WorkCardResult result; private InputCardResult previousResult; private Thread runningThread = NOT_YET_STARTET; private boolean interrupted = false; @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(); } synchronized private void waitTwoSecondsBeforeGoOn() { if (interrupted) { return; } runningThread = new Thread(new Runnable() { @Override public void run() { sleep(2); // Damit diese Karte überhaupt zu sehen ist... gui.done(); } }); runningThread.start(); } private void sleep(int seconds) { long millis = 1000 * seconds; try { Thread.sleep(millis); } catch (InterruptedException e) { System.out.println("ICH WURDE UNTERBROCHEN!"); //e.printStackTrace(); } } @Override public CardResult getResult() { return result; } @Override synchronized public void quit() { interrupted = true; if (runningThread != NOT_YET_STARTET) { runningThread.interrupt(); } } } |
Logik der Karte zur Ausgabe der Zahl.
Hier werden nun die folgenden Änderungen durchgeführt:
@Override public void quit() { // Hier muss nichts gemacht werden, weil es keinen // laufenden anderen Thread zur Berechnung gibt. }
Zusammen ergibt sich damit die folgende Version:
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."); } @Override public void quit() { // Hier muss nichts gemacht werden, weil es keinen // laufenden anderen Thread zur Berechnung gibt. } } |