
While waiting out the rain and tornado watches here in Orlando, I spent my time indoors hacking together a test framework to learn, yet again, how to drop tables (JTable) into tabbed panes (JTabbedPane) and provide a simple way to close individual tabs when needed. I decided to do all this Java hackery with NetBeans 8. NB8 was released at the same time as Java 8, and is meant to support the latest Java 8 features, including lambda expressions. Because this is research for a bigger project at work, and we’re still on Java 7 update 51, I had to forgo using all the latest features, especially lambda expressions.
I needed a framework to investigate (and frankly, re-learn) how to build tables, render data in them, and dismiss them. It’s been years since I had to do anything non-trivial with the Java Foundation Classes, and that had me scrambling through a few tutorials to come back up to speed. I knew what I wanted, just not quite how to achieve it.
For this first step, I wanted to:
- Create tabs with individual close buttons on each tab. You should be able to dismiss the tabs in any arbitrary order.
- Render independent tables on each tab.
- Alternately render row color, using the colors wheat2 and white (the default) in this case.

I pretty much achieved all those goals by the end of the day (in between doing all the other little Saturday chores that needed doing). What follows is the code I hacked together for this example. I will not claim this is the best example of Java programming, far from it. If you’re searching for the best idiomatic Java, then you should probably look elsewhere. My experiences with Java goes back to the mid-1990s and Java 1, with all that that implies.
As for NetBeans 8, for the most part it didn’t get in my way as I was hacking this prototype code together. It was fast and reasonably fluid, although I could have done with a bit less autocomplete. Right now I have three Java IDEs on my Windows notebook; Netbeans 8, IntelliJ IDE 13.1.1, and Eclipse. I’ve got Eclipse because of a lot of “legacy” Android projects I started there. I had every intention of moving out of Eclipse and into IntelliJ/Android Studio, but Android Studio is still under heavy development and I have real needs now, both in Android and regular Java. I’m not too happy with NetBeans 8 and its dropping Scala support, which it had in version 7.4 and earlier. IntelliJ has that support, for the latest Scala version, and I’m happy with that. IntelliJ 13.1.1 also support Java 8. If I had my way I’d migrate all my work to IntelliJ and step up from the Community edition to a fully paid-for IntelliJ license. Maybe in the near future.
I’m posting all this as much for my future benefit as for anyone else who’s interested. I don’t want to have to go scrambling to re-discover this again.
package tabbedtables;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;
/**
*
* @author
*/
public class TabbedTables {
public static Object[][] sampleData =
{
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
{ "sample", "sample", "sample", "sample" },
};
private static JPanel makeTable() {
JTable table = new JTable(new TableModel(sampleData)) {
@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
Component component = super.prepareRenderer(renderer, row, col);
//
// Alternate row color
//
if (!isRowSelected(row))
component.setBackground(row % 2 == 0 ? getBackground() : Color.decode("0xEED8AE"));
return component;
}
};
JPanel panel = new JPanel(new GridLayout(1,0));
table.setPreferredScrollableViewportSize(new Dimension(800, 600));
table.setFillsViewportHeight(true);
panel.add(new JScrollPane(table));
return panel;
}
private static ImageIcon createImageIcon(String path) {
java.net.URL imageURL = TabbedTables.class.getResource(path);
if (imageURL != null) return new ImageIcon(imageURL);
return null;
}
private static void createGUI() {
JTabbedPane tabbedPane = new JTabbedPane();
ImageIcon icon = createImageIcon("images/image.gif");
tabbedPane.addTab("Tab 1", icon, makeTable(), "Sample 1");
tabbedPane.setTabComponentAt(0, new CloseTabControl(tabbedPane));
tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);
tabbedPane.addTab("Tab 2", icon, makeTable(), "Sample 2");
tabbedPane.setTabComponentAt(1, new CloseTabControl(tabbedPane));
tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);
JFrame.setDefaultLookAndFeelDecorated(false);
JFrame frame = new JFrame("Test tabs and tables");
frame.setContentPane(tabbedPane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setVisible(true);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createGUI();
}
});
}
}
package tabbedtables;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.plaf.basic.BasicButtonUI;
/**
* Creates a button to be added to the tab on a JTabbedPane allowing the end
* user to close any arbitrary tab. This is a feature that's been available for
* about a decade (at least) on just about any major application you care to
* name, such as Firefox and Chrome.
*/
public class CloseTabControl extends JPanel {
private JTabbedPane parentTabbedPane;
/**
* Constructed with the parent JTabbledPane reference.
* @param pane parent JTabbedPane
*/
public CloseTabControl(final JTabbedPane pane) {
super(new FlowLayout(FlowLayout.LEFT,0,0));
if (pane == null) throw new NullPointerException("JTabbedPane is null");
parentTabbedPane = pane;
setOpaque(false);
JLabel label = new JLabel() {
@Override
public String getText() {
int index = pane.indexOfTabComponent(CloseTabControl.this);
if (index != -1) return pane.getTitleAt(index);
return null;
}
};
label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
add(label);
add(new TabButton());
setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
}
// This is where the key work gets done. Create a button and then
// associate mouse listeners to indicate when the mouse moves into and
// out of a given instance, as well as close the associated tab when
// mouse clicked.
//
private class TabButton extends JButton implements ActionListener {
public TabButton() {
initTabButton();
}
// initTabButton was created to squash the "leak this in constructor"
// warning, when all of this was in the constructor.
//
private void initTabButton() {
setPreferredSize(new Dimension(20, 20));
setToolTipText("Close this tab.");
setUI(new BasicButtonUI());
setContentAreaFilled(false);
setFocusable(false);
setBorder(BorderFactory.createEtchedBorder());
setBorderPainted(false);
addMouseListener(buttonMouseListener);
setRolloverEnabled(true);
addActionListener(this);
}
// This is where the tab is closed when a mouse click event is
// recieved.
//
@Override
public void actionPerformed(ActionEvent event) {
int index = parentTabbedPane.indexOfTabComponent(CloseTabControl.this);
if (index != -1) parentTabbedPane.remove(index);
}
@Override
public void updateUI() {}
@Override
protected void paintComponent(Graphics graphics) {
Graphics2D g2 = (Graphics2D) graphics.create();
if (getModel().isPressed()) g2.translate(1,1);
g2.setStroke(new BasicStroke(3));
g2.setColor(Color.BLACK);
if (getModel().isRollover()) g2.setColor(Color.RED);
int tweek = 4;
g2.drawLine(tweek, tweek, getWidth() - tweek - 1, getHeight() - tweek - 1);
g2.drawLine(getWidth() - tweek - 1, tweek, tweek, getHeight() - tweek - 1);
g2.dispose();
}
}
private final static MouseListener buttonMouseListener = new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent event) {
if (event.getComponent() instanceof AbstractButton) {
((AbstractButton)event.getComponent()).setBorderPainted(true);
}
}
@Override
public void mouseExited(MouseEvent event) {
if (event.getComponent() instanceof AbstractButton) {
((AbstractButton)event.getComponent()).setBorderPainted(false);
}
}
};
}
package tabbedtables;
import javax.swing.table.AbstractTableModel;
/**
*
*/
public class TableModel extends AbstractTableModel {
private final String[] columnNames = {
"Column 1", "Column 2", "Column 3", "Column 4"
};
private Object[][] data = null;
public TableModel(Object[][] initialData) {
data = initialData;
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public int getRowCount() {
return data != null ? data.length : 0 ;
}
@Override
public String getColumnName(int column) {
return columnNames[column];
}
@Override
public Object getValueAt(int row, int col) {
return data != null ? data[row][col] : null ;
}
@Override
public Class getColumnClass(int col) {
return getValueAt(0, col).getClass();
}
/**
* We want to make the tables read-only. We'll use a complex dialog to
* do any editing. Maybe later I'll do something fancier...
*
* @param row
* @param col
* @return
*/
@Override
public boolean isCellEditable(int row, int col) {
return false;
}
@Override
public void setValueAt(Object value, int row, int col) {
if (data != null) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
}
Like this:
Like Loading...
You must be logged in to post a comment.