JCardPane: Unterschied zwischen den Versionen

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen
(Die Seite wurde neu angelegt: „<code=java>* JCardPane.java * wraps a JTabbedPane, optionally hiding the tabs. * The main method shows an example: import java.awt.*; import java.awt.ev…“)
 
K
 
(8 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
<code=java>/*
+
Die im Bild dargestellte GUI verwendet eine {{JAPI|JTabbedPane}}, deren Reiter versteckt sind. Der Wechsel auf eine andere Komponente in der JTabbedPane, also einen anderen Reiter, erfolgt mit Hilfe der [[JButton|JButtons]].
 +
 
 +
Bei dem Beispiel gibt es also große Ähnlichkeit mit einem [[CardLayout]].
 +
 
 +
[[Bild:JCardPaneDemo.jpg]]
 +
<syntaxhighlight lang="java">
 +
/*
 
  * JCardPane.java
 
  * JCardPane.java
  * wraps a JTabbedPane, optionally hiding the tabs.
+
  * wraps a JTabbedPane, hiding the tabs.
  * The main method shows an example
+
  * The main method shows an example.
 
  */
 
  */
 
 
import java.awt.*;
 
import java.awt.*;
 
import java.awt.event.*;
 
import java.awt.event.*;
 +
import java.beans.*;
 +
import java.util.*;
 +
import java.util.logging.*;
 
import javax.swing.*;
 
import javax.swing.*;
 
import javax.swing.event.*;
 
import javax.swing.event.*;
import javax.swing.plaf.TabbedPaneUI;
+
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
+
import javax.swing.plaf.basic.*;
  
 
public class JCardPane extends JComponent {
 
public class JCardPane extends JComponent {
  
 
     private JTabbedPane pane;
 
     private JTabbedPane pane;
    private boolean requestFocus = true;
 
 
     private CardAction nextAction;
 
     private CardAction nextAction;
 
     private CardAction previousAction;
 
     private CardAction previousAction;
     private TabbedPaneUI uiTabbed;
+
     private TabFocusHandler tabFocusHandler;
 
     private TabbedPaneUI uiNoTabs = new BasicTabbedPaneUI() {
 
     private TabbedPaneUI uiNoTabs = new BasicTabbedPaneUI() {
  
Zeile 47: Zeile 54:
 
     };
 
     };
  
 +
    /**
 +
    * Creates an empty cardpane
 +
    */
 
     public JCardPane() {
 
     public JCardPane() {
        this(false);
 
    }
 
 
    public JCardPane(boolean tabbed) {
 
 
         pane = new JTabbedPane();
 
         pane = new JTabbedPane();
         uiTabbed = pane.getUI();
+
         tabFocusHandler = new TabFocusHandler(pane);
         if (!tabbed) {
+
         hideTabs();
            hideTabs();
 
        }
 
 
         pane.addChangeListener(new ChangeListener() {
 
         pane.addChangeListener(new ChangeListener() {
  
             public void stateChanged(ChangeEvent e) {
+
            @Override
                if (requestFocus) {
+
             public void stateChanged(final ChangeEvent e) {
                    Runnable transferFocus = new Runnable() {
 
 
 
                        public void run() {
 
                            pane.getSelectedComponent().transferFocus();
 
                        }
 
                    };
 
                    SwingUtilities.invokeLater(transferFocus);
 
                }
 
 
                 if (nextAction != null) {
 
                 if (nextAction != null) {
 
                     nextAction.setEnabled(hasNext());
 
                     nextAction.setEnabled(hasNext());
Zeile 78: Zeile 74:
 
         });
 
         });
 
         setLayout(new BorderLayout());
 
         setLayout(new BorderLayout());
         add(pane);
+
         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() {
 
     public int first() {
 
         return show(0);
 
         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() {
 
     public int last() {
 
         return show(-1);
 
         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() {
 
     public int next() {
 
         return show(pane.getSelectedIndex() + 1);
 
         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() {
 
     public int previous() {
 
         return show(pane.getSelectedIndex() - 1);
 
         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) {
 
     public int show(final int index) {
 
         if (pane.getTabCount() == 0) {
 
         if (pane.getTabCount() == 0) {
Zeile 115: Zeile 144:
 
     }
 
     }
  
 +
    /**
 +
    * 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) {
 
     public int show(final String name) {
 
         int index = pane.indexOfTab(name);
 
         int index = pane.indexOfTab(name);
Zeile 123: Zeile 158:
 
     }
 
     }
  
 +
    /**
 +
    * 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) {
 
     public int show(final JComponent card) {
 
         int index = pane.indexOfComponent(card);
 
         int index = pane.indexOfComponent(card);
Zeile 133: Zeile 173:
 
     }
 
     }
  
 +
    /**
 +
    * 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() {
 
     public boolean hasNext() {
 
         return pane.getSelectedIndex() < pane.getComponentCount() - 1;
 
         return pane.getSelectedIndex() < pane.getComponentCount() - 1;
 
     }
 
     }
  
 +
    /**
 +
    * @return true if there is a card before the visible one
 +
    */
 
     public boolean hasPrevious() {
 
     public boolean hasPrevious() {
 
         return pane.getSelectedIndex() > 0;
 
         return pane.getSelectedIndex() > 0;
 
     }
 
     }
  
 +
    /**
 +
    * @return the visible card
 +
    */
 
     public Component getCurrent() {
 
     public Component getCurrent() {
 
         return pane.getSelectedComponent();
 
         return pane.getSelectedComponent();
 
     }
 
     }
  
     public void setRequestFocus(boolean b) {
+
     /**
        requestFocus = b;
+
    * Adds a <code>ChangeListener</code> to this cardpane.
    }
+
    */
 
+
     public void addChangeListener(final ChangeListener l) {
    public boolean isRequestFocus() {
 
        return requestFocus;
 
    }
 
 
 
     public void addChangeListener(ChangeListener l) {
 
 
         pane.addChangeListener(l);
 
         pane.addChangeListener(l);
 
     }
 
     }
  
     public void removeChangeListener(ChangeListener l) {
+
    /**
 +
    * Removes a <code>ChangeListener</code> from this cardpane.
 +
    */
 +
     public void removeChangeListener(final ChangeListener l) {
 
         pane.removeChangeListener(l);
 
         pane.removeChangeListener(l);
 
     }
 
     }
  
     public void hideTabs() {
+
     /**
        pane.setUI(uiNoTabs);
+
    * gets the action to be used for a "Next" button
    }
+
    */
 
+
     public Action getNextAction(final String name) {
    public void showTabs() {
 
        pane.setUI(uiTabbed);
 
    }
 
 
 
     public Action getNextAction() {
 
 
         if (nextAction == null) {
 
         if (nextAction == null) {
             nextAction = new CardAction("Next", true);
+
             nextAction = new CardAction(name, true);
             nextAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_N);
+
             nextAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
 
             nextAction.setEnabled(hasNext());
 
             nextAction.setEnabled(hasNext());
 
         }
 
         }
 
 
         return nextAction;
 
         return nextAction;
 
     }
 
     }
  
     public Action getPreviousAction() {
+
    /**
 +
    * gets the action to be used for a "Previous" button
 +
    */
 +
     public Action getPreviousAction(final String name) {
 
         if (previousAction == null) {
 
         if (previousAction == null) {
             previousAction = new CardAction("Previous", false);
+
             previousAction = new CardAction(name, false);
             previousAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_P);
+
             previousAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
 
             previousAction.setEnabled(hasPrevious());
 
             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);
 +
    }
  
         return previousAction;
+
    private void hideTabs() {
 +
         pane.setUI(uiNoTabs);
 
     }
 
     }
  
     class CardAction extends AbstractAction {
+
     private class CardAction extends AbstractAction {
  
 
         private boolean isNext;
 
         private boolean isNext;
  
         public CardAction(String text, boolean isNext) {
+
         CardAction(final String text, final boolean isNext) {
 
             super(text);
 
             super(text);
 
             this.isNext = isNext;
 
             this.isNext = isNext;
Zeile 199: Zeile 278:
 
         }
 
         }
  
         public void actionPerformed(ActionEvent e) {
+
        @Override
 +
         public void actionPerformed(final ActionEvent e) {
 
             if (isNext) {
 
             if (isNext) {
 
                 next();
 
                 next();
 
             } else {
 
             } else {
 
                 previous();
 
                 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();
 +
                }
 
             }
 
             }
 
         }
 
         }
Zeile 209: Zeile 408:
  
 
     //for testing only:
 
     //for testing only:
     public static void main(final String[] args) {
+
     public static void main(final String... args) {
 
         try {
 
         try {
 
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
 
             UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
         } catch (Exception ex) {
+
         } catch (ClassNotFoundException ex) {
             ex.printStackTrace();
+
             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() {
 
         Runnable gui = new Runnable() {
Zeile 227: Zeile 432:
 
                 class Card extends JPanel {
 
                 class Card extends JPanel {
  
                     public Card(final String name) {
+
                     Card(final String name) {
 
                         JTextField tf = new JTextField(20);
 
                         JTextField tf = new JTextField(20);
 +
                        tf.setText(name);
 +
                        add(tf);
 +
                        tf = new JTextField(20);
 
                         tf.setText(name);
 
                         tf.setText(name);
 
                         add(tf);
 
                         add(tf);
Zeile 239: Zeile 447:
 
                 cardPane.first();
 
                 cardPane.first();
 
                 JPanel control = new JPanel();
 
                 JPanel control = new JPanel();
                 control.add(new JButton(cardPane.getPreviousAction()));
+
                 control.add(new JButton(cardPane.getPreviousAction("Previous")));
                 control.add(new JButton(cardPane.getNextAction()));
+
                 control.add(new JButton(cardPane.getNextAction("Next")));
                final JToggleButton btTabbed = new JToggleButton("Tabbed");
 
                btTabbed.setFocusable(false);
 
                control.add(btTabbed);
 
                btTabbed.addActionListener(new ActionListener() {
 
 
 
                    public void actionPerformed(ActionEvent e) {
 
                        if (btTabbed.isSelected()) {
 
                            cardPane.showTabs();
 
                        } else {
 
                            cardPane.hideTabs();
 
                        }
 
                    }
 
                });
 
 
                 f.add(control, BorderLayout.SOUTH);
 
                 f.add(control, BorderLayout.SOUTH);
 
                 f.setVisible(true);
 
                 f.setVisible(true);
Zeile 262: Zeile 457:
 
     }
 
     }
 
}
 
}
</code=java>
+
</syntaxhighlight>
[[Kategorie:Java]]
+
 
 
[[Kategorie:Swing]]
 
[[Kategorie:Swing]]
 
[[Kategorie:Java-Codeschnipsel]]
 
[[Kategorie:Java-Codeschnipsel]]

Aktuelle Version vom 8. April 2018, 11:45 Uhr

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