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;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)) {@Overridepublic 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);}public static void main(String[] args) {SwingUtilities.invokeLater(new Runnable() {@Overridepublic 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;public class CloseTabControl extends JPanel {private JTabbedPane parentTabbedPane;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() {@Overridepublic 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.//@Overridepublic void actionPerformed(ActionEvent event) {int index = parentTabbedPane.indexOfTabComponent(CloseTabControl.this);if (index != -1) parentTabbedPane.remove(index);}@Overridepublic void updateUI() {}@Overrideprotected 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() {@Overridepublic void mouseEntered(MouseEvent event) {if (event.getComponent() instanceof AbstractButton) {((AbstractButton)event.getComponent()).setBorderPainted(true);}}@Overridepublic 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;}@Overridepublic int getColumnCount() {return columnNames.length;}@Overridepublic int getRowCount() {return data != null ? data.length : 0 ;}@Overridepublic String getColumnName(int column) {return columnNames[column];}@Overridepublic Object getValueAt(int row, int col) {return data != null ? data[row][col] : null ;}@Overridepublic Class getColumnClass(int col) {return getValueAt(0, col).getClass();}@Overridepublic boolean isCellEditable(int row, int col) {return false;}@Overridepublic void setValueAt(Object value, int row, int col) {if (data != null) {data[row][col] = value;fireTableCellUpdated(row, col);}}}
You must be logged in to post a comment.