JCardPane

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

Die im Bild dargestellte GUI verwendet eine JTabbedPane, deren Reiter versteckt sind. Der Wechsel auf eine andere Komponente in der JTabbedPane, also einen anderen Reiter, erfolgt mit Hilfe der JButtons.

Bei dem Beispiel gibt es also große Ähnlichkeit mit einem CardLayout.

JCardPaneDemo.jpg

/*
 * JCardPane.java
 * wraps a JTabbedPane, hiding the tabs.
 * The main method shows an example.
 */
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import java.util.logging.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;

public class JCardPane extends JComponent {

    private JTabbedPane pane;
    private CardAction nextAction;
    private CardAction previousAction;
    private TabFocusHandler tabFocusHandler;
    private TabbedPaneUI uiNoTabs = new BasicTabbedPaneUI() {

        @Override
        protected int calculateTabAreaHeight(int tabPlacement,
                int horizRunCount, int maxTabHeight) {
            return 0;
        }

        @Override
        protected Insets getContentBorderInsets(int tabPlacement) {
            return new Insets(0, 0, 0, 0);
        }

        @Override
        protected MouseListener createMouseListener() {
            return null;
        }

        @Override
        protected void installKeyboardActions() {
        }

        @Override
        protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
            return 0;
        }
    };

    /**
     * Creates an empty cardpane
     */
    public JCardPane() {
        pane = new JTabbedPane();
        tabFocusHandler = new TabFocusHandler(pane);
        hideTabs();
        pane.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(final ChangeEvent e) {
                if (nextAction != null) {
                    nextAction.setEnabled(hasNext());
                }
                if (previousAction != null) {
                    previousAction.setEnabled(hasPrevious());
                }
            }
        });
        setLayout(new BorderLayout());
        super.add(pane);
    }

    /**
     * Flips to the first card of the cardpane.
     * @return 0 if this cardpane has a card
     * or -1 if this cardpane has no card
     */
    public int first() {
        return show(0);
    }

    /**
     * Flips to the last card of the cardpane.
     * @return 0 if this cardpane has a card
     * or -1 if this cardpane has no card
     */
    public int last() {
        return show(-1);
    }

    /**
     * Flips to the next card of the cardpane.
     * If the currently visible card is the last one,
     * this method flips to the first card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int next() {
        return show(pane.getSelectedIndex() + 1);
    }

    /**
     * Flips to the previous card of the cardpane.
     * If the currently visible card is the first one,
     * this method flips to the last card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int previous() {
        return show(pane.getSelectedIndex() - 1);
    }

    /**
     * Flips to the card at the specified index.
     * If the index is positive but does not exist,
     * this method flips to the first card of the cardpane.
     * If the index is negative,
     * this method flips to the last card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int show(final int index) {
        if (pane.getTabCount() == 0) {
            return -1;
        }
        if (index >= pane.getTabCount()) {
            pane.setSelectedIndex(0);
            return 0;

        }
        if (index < 0) {
            pane.setSelectedIndex(pane.getTabCount() - 1);
            return pane.getTabCount() - 1;

        }
        pane.setSelectedIndex(index);
        return index;
    }

    /**
     * Flips to the card with the specified name.
     * If the name does not exist, nothing happens.
     * @return the index of the card
     * or -1 if the name does not exist
     */
    public int show(final String name) {
        int index = pane.indexOfTab(name);
        if (index >= 0) {
            pane.setSelectedIndex(index);
        }
        return index;
    }

    /**
     * Flips to the specified card.
     * If the card does not exist in the cardpane, it is added to it.
     * @return the index of the specified card
     */
    public int show(final JComponent card) {
        int index = pane.indexOfComponent(card);
        if (index < 0) {
            index = pane.getTabCount();
            pane.addTab(card.getName(), card);
        }
        pane.setSelectedIndex(index);
        return index;
    }

    /**
     * Flips to the specified card.
     * If the card does not exist in the cardpane, it is added to it.
     * @return the index of the specified card
     */
    public int add(final JComponent card) {
        return show(card);
    }

    /**
     * Inserts the specified card at the sepcified index and
     * flips to the specified card.
     */
    public void insertAt(JComponent card, int index){
        pane.insertTab(null, null, card, null, index);
        pane.setSelectedIndex(index);
    }

    /**
     * @return true if there is a card after the visible one
     */
    public boolean hasNext() {
        return pane.getSelectedIndex() < pane.getComponentCount() - 1;
    }

    /**
     * @return true if there is a card before the visible one
     */
    public boolean hasPrevious() {
        return pane.getSelectedIndex() > 0;
    }

    /**
     * @return the visible card
     */
    public Component getCurrent() {
        return pane.getSelectedComponent();
    }

    /**
     * Adds a <code>ChangeListener</code> to this cardpane.
     */
    public void addChangeListener(final ChangeListener l) {
        pane.addChangeListener(l);
    }

    /**
     * Removes a <code>ChangeListener</code> from this cardpane.
     */
    public void removeChangeListener(final ChangeListener l) {
        pane.removeChangeListener(l);
    }

    /**
     * gets the action to be used for a "Next" button
     */
    public Action getNextAction(final String name) {
        if (nextAction == null) {
            nextAction = new CardAction(name, true);
            nextAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
            nextAction.setEnabled(hasNext());
        }
        return nextAction;
    }

    /**
     * gets the action to be used for a "Previous" button
     */
    public Action getPreviousAction(final String name) {
        if (previousAction == null) {
            previousAction = new CardAction(name, false);
            previousAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
            previousAction.setEnabled(hasPrevious());
        }
        return previousAction;
    }

    /**
     *
     * retain: if true, the component focused within a card will remain focused
     * each time it becomes visible, otherwise the first component of each card is focused
     */
    public void retainFocus(final boolean retain) {
        int focusPolicy = TabFocusHandler.RESET_FOCUS;
        if (retain) {
            focusPolicy = TabFocusHandler.RETAIN_FOCUS;
        }
        tabFocusHandler.uninstall();
        tabFocusHandler = new TabFocusHandler(pane, focusPolicy);
    }

    private void hideTabs() {
        pane.setUI(uiNoTabs);
    }

    private class CardAction extends AbstractAction {

        private boolean isNext;

        CardAction(final String text, final boolean isNext) {
            super(text);
            this.isNext = isNext;
            putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            if (isNext) {
                next();
            } else {
                previous();
            }
        }
    }

    /**
     *  Manage the focus when a new tab is selected. You can select a focus policy:
     *  a) Reset Focus - focus is reset to the first focusable component on the tab
     *  b) Retain Focus - focus returns to the last component with focus on the tab
     *
     *  In addition you add tabs that you want to exclude from the focus policy,
     *  in which case the other policy will be in effect.
     */
    private class TabFocusHandler {

        public final static int RESET_FOCUS = 0;
        public final static int RETAIN_FOCUS = 1;
        private HashMap<Component, Component> tabFocus = new HashMap<Component, Component>(16);
        private HashSet<Component> exceptions;
        private JTabbedPane tabbedPane;
        private int focusPolicy;
        private final KeyboardFocusManager focusManager;
        private final ChangeListener changeListener;
        private final PropertyChangeListener propertyChangeListener;

        /**
         *  Create with the default Retain Focus policy
         */
        TabFocusHandler(final JTabbedPane tabbedPane) {
            this(tabbedPane, RETAIN_FOCUS);
        }

        /**
         *  Create using the specified focus policy
         */
        TabFocusHandler(final JTabbedPane tabbedPane, final int focusPolicy) {
            if (focusPolicy != RESET_FOCUS && focusPolicy != RETAIN_FOCUS) {
                throw new IllegalArgumentException("Invalid focus policy");
            }
            this.tabbedPane = tabbedPane;
            this.focusPolicy = focusPolicy;
            //  Add listeners to manage a tab change
            changeListener = new TabChangeListener();
            propertyChangeListener = new TabPropertyChangeListener();
            tabbedPane.addChangeListener(changeListener);
            focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            focusManager.addPropertyChangeListener("permanentFocusOwner", propertyChangeListener);
        }

        public void uninstall() {
            tabbedPane.removeChangeListener(changeListener);
            focusManager.removePropertyChangeListener(propertyChangeListener);
        }

        /**
         *  Specify a tab with an exception to the focus policy rule
         */
        public void addException(final int index) {
            if (exceptions == null) {
                exceptions = new HashSet<Component>(16);
            }
            Component key = tabbedPane.getComponentAt(index);
            exceptions.add(key);
        }

        private class TabPropertyChangeListener implements PropertyChangeListener {

            TabPropertyChangeListener() {
            }

            /**
             *  Track focus changes and update the current focus component
             *  for the current tab
             */
            @Override
            public void propertyChange(final PropertyChangeEvent e) {
                //  No need to track focus change
                if (exceptions == null && focusPolicy == RESET_FOCUS) {
                    return;
                }
                Component key = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());
                if (exceptions != null) {
                    if (focusPolicy == RESET_FOCUS && !exceptions.contains(key)) {
                        return;
                    }
                    if (focusPolicy == RETAIN_FOCUS && exceptions.contains(key)) {
                        return;
                    }
                }
                Component value = (Component) e.getNewValue();
                if (value != null && SwingUtilities.isDescendingFrom(value, key)) {
                    tabFocus.put(key, value);
                }
            }
        }

        private class TabChangeListener implements ChangeListener {

            TabChangeListener() {
            }

            /**
             * Tab has changed. Focus on saved component for the given tab.
             * When there is no saved component, focus on the first component.
             */
            @Override
            public void stateChanged(final ChangeEvent e) {
                Component key = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());
                if (key == null) {
                    return;
                }
                Component value = tabFocus.get(key);
                //  First time selecting this tab or focus policy is RESET_FOCUS
                if (value == null) {
                    key.transferFocus();
                    tabFocus.put(key, value);
                } else //  Use the saved component for focusing
                {
                    value.requestFocusInWindow();
                }
            }
        }
    }

    //for testing only:
    public static void main(final String... args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        }
        Runnable gui = new Runnable() {

            @Override
            public void run() {
                final JFrame f = new JFrame("JCardPane Demo");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(400, 150);
                f.setLocationRelativeTo(null);
                final JCardPane cardPane = new JCardPane();
                f.add(cardPane, BorderLayout.CENTER);
                class Card extends JPanel {

                    Card(final String name) {
                        JTextField tf = new JTextField(20);
                        tf.setText(name);
                        add(tf);
                        tf = new JTextField(20);
                        tf.setText(name);
                        add(tf);
                        setName(name);
                    }
                }
                for (int i = 0; i < 5; i++) {
                    cardPane.show(new Card(String.valueOf(i + 1)));
                }
                cardPane.first();
                JPanel control = new JPanel();
                control.add(new JButton(cardPane.getPreviousAction("Previous")));
                control.add(new JButton(cardPane.getNextAction("Next")));
                f.add(control, BorderLayout.SOUTH);
                f.setVisible(true);
            }
        };
        //GUI must start on EventDispatchThread:
        SwingUtilities.invokeLater(gui);
    }
}