Behandlung eines Abbruchs durch den Benutzer im Card-Layout (Swing)

Neue Klassen und Interfaces - Veränderungen an den bekannten Klassen und Interfaces - 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. 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.

Neue Klassen und Interfaces


Zusätzlich zu den im Artikel Das Card-Layout vorgestellten Klassen und Interfaces werden die folgenden beiden Quelldateien benötigt:

Quitter

Dieses Interface wird von dem Rahmenprogramm implementiert.

Quitter

package de.duehl.swing.logic;

public interface Quitter {

    void quit();

}

ClosingWindowListener

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) {
    }

}

Veränderungen an den bekannten Klassen und Interfaces


An den aus dem Artikel Das Card-Layout vorgestellten Klassen und Interfaces werden die folgenden Änderungen vorgenommen:

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.

Hier werden nun die folgenden Änderungen durchgeführt:

  • CardLayoutDemo implementiert nun Quitter.
  • In der Methode createGui wird zusätzlich die Zeile frame.addWindowListener(new ClosingWindowListener(this)); eingefügt.
  • Es wird die zu Quitter gehörende Methode quit() eingebaut:
        @Override
        public void quit() {
            boolean quit = askUserToQuit();
            if (quit) {
                cardHandling.quit();
                close();
            }
        }
  • Außerdem wird die in quit() aufgerufene Methode askUserToQuit() eingebaut:
        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();
    }

}

CardHandling

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:

  • Es wird eine neue Klassenvariable private boolean interrupted eingeführt.
  • interrupted wird mit false initialisiert.
  • Die Methode switchCard ist nun synchronisiert und am Anfang wird das Weiterschalten unterbunden, wenn interrupted wahr ist.
  • Neue Methode quit():
        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();
    }

}

Card

Diese Klasse stellt eine Karte dar.

Hier werden nun die folgenden Änderungen durchgeführt:

  • Neue Methode quit():
        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();
    }

}

CardLogic

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

Hier werden nun die folgenden Änderungen durchgeführt:

  • Neue Methode quit().

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();

}

InputCardLogic

Logik der Karte zur Eingabe der Zahl.

Hier werden nun die folgenden Änderungen durchgeführt:

  • Neue Methode quit():
        @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.
    }

}

WorkCardLogic

Logik der Karte zur Verarbeitung.

Hier werden nun die folgenden Änderungen durchgeführt:

  • Es wird eine neue Konstante NOT_YET_STARTET eingeführt, deren Wert anzeigt, dass der externe Thread zur Berechnung noch nicht gestartet wurde.
  • Es wird eine neue Klassenvariable private boolean interrupted eingeführt.
  • interrupted wird mit false initialisiert.
  • Die Methode waitTwoSecondsBeforeGoOn() ist nun synchronisiert und am Anfang wird die Verarbeitung unterbunden, wenn interrupted wahr ist.
  • Es wird eine neue Klassenvariable private Thread runningThread eingeführt.
  • runningThread wird mit NOT_YET_STARTET initialisiert.
  • In waitTwoSecondsBeforeGoOn() ist der Thread, der gestartet wird, nun nicht mehr anonym, sondern wird runningThread zugewiesen.
  • In sleep() wird beim Fangen einer InterruptedException nun kein Stacktrace mehr ausgegeben (aber eine Test-Ausgabe, dass unterbrochen wurde, um die korrekte Funktionsweise bei einem Abbruch in dieser Karte sehen zu können.
  • Neue Methode quit():
        @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();
        }
    }

}

OutputCardLogic

Logik der Karte zur Ausgabe der Zahl.

Hier werden nun die folgenden Änderungen durchgeführt:

  • Neue Methode quit():
        @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.
    }

}

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 CardLayoutWithQuit_01.zip downloaden

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: