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:
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 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();
}
}
});
}
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:
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();
}
@Override
protected int calculateTabHeight(int tabPlacement, int tabIndex,
int fontHeight) {
return Math.max(super.calculateTabHeight(tabPlacement, tabIndex, fontHeight), this.minHeight);
}
@Override
protected Insets getTabAreaInsets(int tabPlacement) {
return new Insets(0, this.leadingOffset, 0, this.trailingOffset);
}
public void setLeadingOffset(int offset) {
this.leadingOffset = offset;
}
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):
History
- 2014-09-03 0603 CDT: Added screenshot
- Previously: This is the first version