Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Swing

Enhanced JTabbedPane

5.00/5 (1 vote)
2 Sep 2014CPOL1 min read 15.2K  
This tip provides code for an enhanced version of the JTabbedPane which allows the developer to add toolbar buttons to the beginning or end of the tab set.

Introduction

I have decided that in the interest of modern design and encouraging a certain workflow, a project on which I am working would be best presented with a minimalist design consisting of only tabs in the main frame. However, after much searching, I was only able to find discouraging advice on the ability to add toolbar buttons to the tab set. The key is that these buttons should be just buttons. It's easy enough to add a 16x16 image as a tab header, but the desired effect is not for the Save button, for example, to have content associated with it. Instead, the Save button should just be there waiting to be clicked, and always be visible regardless of whether the array of tabs has scrolled. These classes provide the ability to add one or more buttons to either the leading or trailing side (Locale dependent) of the tab set.

Using the Code

The main class here is ETabbedPane which derives from JPanel. It contains a JTabbedPane which fills the widget and two toolbars, one each at the northwest and northeast corner. To interact with the tabs, fetch them using getJTabbedPane() and interact with them as you normally would. Rather than referring to the toolbars as left and right, they are referenced as 'leading' and 'trailing' to facilitate internationalization.

Here is the source:

Java
import java.awt.ComponentOrientation;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.SpringLayout;

public class ETabbedPane extends JPanel {
    public enum ButtonSide {
        LEADING,
        TRAILING;
    }
    /**
     *
     */
    private static final long serialVersionUID = -2090869893562246860L;
    private JTabbedPane tabs;
    private JToolBar leadingButtons;
    private JToolBar trailingButtons;
    //private JPanel dropDown;
    private OffsetTabbedPaneUI tabUI;
    
    public ETabbedPane(ComponentOrientation orient) {
        SpringLayout sl = new SpringLayout();
        this.setLayout(sl);
        
        this.leadingButtons = new JToolBar();
        this.leadingButtons.setBorderPainted(false);
        this.leadingButtons.setFloatable(false);
        this.leadingButtons.setOpaque(false);
        this.leadingButtons.setComponentOrientation(orient);
        this.leadingButtons.setLayout(new FlowLayout(FlowLayout.LEADING, 1, 1));
        sl.putConstraint(SpringLayout.NORTH, this.leadingButtons, 0, SpringLayout.NORTH, this);
        String side = ((orient == ComponentOrientation.RIGHT_TO_LEFT) ? SpringLayout.EAST : SpringLayout.WEST);
        sl.putConstraint(side, this.leadingButtons, 0, side, this);
        this.add(leadingButtons);
        
        this.trailingButtons = new JToolBar();
        this.trailingButtons.setBorderPainted(false);
        this.trailingButtons.setFloatable(false);
        this.trailingButtons.setOpaque(false);
        this.trailingButtons.setComponentOrientation(orient);
        this.trailingButtons.setLayout(new FlowLayout(FlowLayout.LEADING, 1, 1));
        sl.putConstraint(SpringLayout.NORTH, this.trailingButtons, 0, SpringLayout.NORTH, this);
        side = ((orient == ComponentOrientation.RIGHT_TO_LEFT) ? SpringLayout.WEST : SpringLayout.EAST);
        sl.putConstraint(side, this.trailingButtons, 0, side, this);
        this.add(trailingButtons);
        
        this.tabs = new JTabbedPane();
        this.tabs.setComponentOrientation(orient);
        sl.putConstraint(SpringLayout.NORTH, this.tabs, 0, SpringLayout.NORTH, this);
        sl.putConstraint(SpringLayout.SOUTH, this.tabs, 0, SpringLayout.SOUTH, this);
        sl.putConstraint(SpringLayout.WEST, this.tabs, 0, SpringLayout.WEST, this);
        sl.putConstraint(SpringLayout.EAST, this.tabs, 0, SpringLayout.EAST, this);
        tabUI = new OffsetTabbedPaneUI();
        this.tabs.setUI(tabUI);
        this.add(tabs);
        
        this.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                if (tabs.getTabCount() > 0) {
                    tabs.getSelectedComponent().requestFocusInWindow();
                }
            }
        });
    }

    /**
     * @return the tabs
     */
    public JTabbedPane getJTabbedPane() {
        return tabs;
    }
    
    public void addButton(JButton button, ButtonSide side) {
        button.setBorderPainted(false);
        button.setFocusable(false);
        button.setMargin(new Insets(1, 1, 1, 1));
        ((side == ButtonSide.LEADING) ? this.leadingButtons : this.trailingButtons).add(button);
        this.tabUI.setMinHeight(
                Math.max(this.leadingButtons.getPreferredSize().height,
                         this.trailingButtons.getPreferredSize().height));
        this.tabUI.setLeadingOffset(this.leadingButtons.getPreferredSize().width);
        this.tabUI.setTrailingOffset(this.trailingButtons.getPreferredSize().width);
        this.validate();
    }
}

In order for the widget to work as intended, you need an additional class which overrides the default L&F. Source:

Java
import java.awt.Insets;

import javax.swing.plaf.basic.BasicTabbedPaneUI;

public class OffsetTabbedPaneUI extends BasicTabbedPaneUI {
    private int leadingOffset = 0;
    private int minHeight = 0;
    private int trailingOffset;
    
    public OffsetTabbedPaneUI() {
        super();
    }

    /* (non-Javadoc)
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#calculateTabHeight(int, int, int)
     */
    @Override
    protected int calculateTabHeight(int tabPlacement, int tabIndex,
            int fontHeight) {
        return Math.max(super.calculateTabHeight(tabPlacement, tabIndex, fontHeight), this.minHeight);
    }

    /* (non-Javadoc)
     * @see javax.swing.plaf.basic.BasicTabbedPaneUI#getTabAreaInsets(int)
     */
    @Override
    protected Insets getTabAreaInsets(int tabPlacement) {
        // ignores tab placement for now
        return new Insets(0, this.leadingOffset, 0, this.trailingOffset);
    }

    /**
     * @param offset the offset to set
     */
    public void setLeadingOffset(int offset) {
        this.leadingOffset = offset;
    }

    /**
     * @param minHeight the minHeight to set
     */
    public void setMinHeight(int minHeight) {
        this.minHeight = minHeight;
    }

    public void setTrailingOffset(int offset) {
        this.trailingOffset = offset;
    }
}

Here is an example application demonstrating several tabs and buttons on the leading and trailing ends (running under Kubuntu):

Image 1

History

  • 2014-09-03 0603 CDT: Added screenshot
  • Previously: This is the first version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)