Monday 22 August 2011

Configuring Pre Tab selection action in a JTabbedPane

In Swing, the standard component for Tabbed Panel is JTabbedPane. The JTabbedPane does not have a listener for configuring actions which we want to execute before a tab selection. 


We will come across many scenarios in which we may require to do some validation before we proceed from the current tab. In such scenarios, the user should be allowed to proceed only based on the validation outcome.


We don't have a ready made solution for achieving the above outcome. We will discuss here the solution to achieve the above outcome.


During a tab selection in swing JTabbedPane, the javax.swing.JTabbedPane#setSelectedIndex(int) always gets called. So, we will override this method to configure our listener/actions accordingly.


The sample code for achieving this is as follows:



package com.prasune.swing;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JTabbedPane;

/**
 * This class is a custom tabbed pane extended from basic JTabbedPane swing
 * component.<br>
 * The basic JTabbedPane swing component in Java does not have a pre tab
 * selection listener.<br>
 * The tab selection in JTabbedPane swing component happens with the call of
 * method {@link #setSelectedIndex(int)}. <br>
 * Hence, for plugging in pre tab selection actions we are overriding
 * {@link #setSelectedIndex(int)}
 */
public class CustomTabbedPane extends JTabbedPane
{

    /**
     * The index of the tab which the user is trying to select.
     */
    private int currentTargetIndex;

    /**
     * The Listeners to be fired before tab changing action.
     */
    private List<TabChangeListener> tabChangeListeners = new ArrayList<TabChangeListener>();

    /**
     * Constructor made private to ensure that the instance creation happens via
     * call to {@link #createInstance(String)}.
     */
    private CustomTabbedPane()
    {
    }

    /**
     * This method ensures that component name will be associated with the
     * instance after creation. Component name help in writing automated test
     * cases.
     *
     * @param name
     *            The component name to be associated with instance.
     * @return instance of CustomTabbedPane.
     */
    public static CustomTabbedPane createInstance(String name)
    {
        CustomTabbedPane tabbedPane = new CustomTabbedPane();
        tabbedPane.setName(name);
        return tabbedPane;
    }

    /**
     * This method gets called when we select a tab. This method is overridden
     * to plug in pre tab selection actions which we require to execute.
     *
     * @param targetIndex
     *            The index of tab which user is trying to select.
     */
    @Override
    public void setSelectedIndex(int targetIndex)
    {
        if (targetIndex >= 0 && targetIndex < getTabCount())
        {
            this.currentTargetIndex = targetIndex;
            // This check is required for exceptions during tab creation.
            if (getSelectedIndex() == -1)
            {
                super.setSelectedIndex(targetIndex);
            }
            else
            {
                boolean continueTabSelectionFlag = false;
                // Add your listener execution here, update
                // continueTabSelectionFlag based on boolean returned by custom
                // listener execution.
                for (TabChangeListener tabChangeListener : tabChangeListeners)
                {
                    continueTabSelectionFlag = tabChangeListener.execute(this);
                    if (!continueTabSelectionFlag){
                        break;
                    }
                }               
                if (continueTabSelectionFlag)
                {
                    super.setSelectedIndex(targetIndex);
                }
            }
        }
    }

    /**
     * @return the index of the tab which the user is trying to select.
     */
    public int getCurrentTargetIndex()
    {
        return currentTargetIndex;
    }

    /**
     * Add your custom listener instance via this method.
     */
    public void addTabChangingListeners(
            final TabChangeListener tabChangeListener)
    {
        tabChangeListeners.add(tabChangeListener);
    }
}


Our Tab change listener could be something as follows:


package com.prasune.swing;


public class TabChangeListener
{

    public boolean execute(CustomTabbedPane tabbedPane){
        // This is required to identify current index as during
        // listener callback index is not yet set.
        int currentTabIndex = tabbedPane.getCurrentTargetIndex();
        // do necessary validations on tabbed pane and return flag accordingly
        return true;
    }
}