Java Swing Tips & Tricks
CONTENTS
Putting Data on the Clipboard
Finish Table Editing
Thin Table Columns in a Scroll Pane
Multi-line Message Dialogs
JOptionPane Options
Setting the Application Icon
Easy GridBagLayout
CardLayout
UIManager Defaults
Uneditable Text Fields
Forcing Text Area to Scroll
Listening for Text Changes
Showing HTML with a Stylesheet
JButton Basics
Handling Window Border Close
Centering a Frame/Dialog
Anti-aliased Drawing
Changing the Font on the Fly
JList Basics
JComboBox Basics

Putting Data on the Clipboard

It's sometimes useful to automatically put some data on the clipboard. For example, if you're expecting the user to copy the data that your application displays in a text area to some other application, you might like to put the string directly on the clipboard. Here's how to do it:
You'll needs some imports:
import java.awt.datatransfer.*; import javax.swing.*;
Declare a data member of your class and initialize it as follows:
ClipboardOwner clipboardOwner = new ClipboardOwner() { public void lostOwnership(Clipboard clip, Transferable tr) { // who cares? } };
Now suppose you have a string which you want to put into a text area, and then select the string in the text area, and make it available on the clipboard:
String s = "whatever"; textArea.setText(s); textArea.requestFocus(); // seems to be necessary, why? textArea.selectAll(); Toolkit tk = Toolkit.getDefaultToolkit(); StringSelection st = new StringSelection(s); Clipboard cp = tk.getSystemClipboard(); cp.setContents(st, clipboardOwner);
The user just needs to Ctrl-V (on a Windows system) into another application. No Ctrl-C is necessary!
to top

Finish Table Editing

If you allow the user to edit values in a table directly, using a table cell editor, it is important to make sure the last edit is completed before the values are saved.
For example, if the table is in a dialog box, and the user edits a table value using a text field, and clicks on the Ok button without completing the edit (by clicking on another cell, or pressing Return), then it's possible that the last entered value will not be saved to the model before the dialog ok procedure takes place.
Here's how to do it; just put this code at the beginning of the ok callback (or before accessing the model data in any method):
if (table.isEditing()) { table.getCellEditor().stopCellEditing(); }
to top

Thin Table Columns in a Scroll Pane

By default, the table will appear with all columns sized down so as to appear entirely within the scroll pane, and there will be no horizontal scrollbar. This can be annoying if there's lots of columns, since all the headers and cells will contain "...". To make the horizontal scrollbar appear, and give the columns a default with, use:
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
to top

Multi-line Message Dialogs

Here's the easiest way:
String[] lines = { "First line", "Second line", "Third line" }; JOptionPane.showMessageDialog(this, lines);
See the next topic for more versions of showMessageDialog() in which you can specify the title and icon type.
to top

JOptionPane Options

Here's how to use some of the showXxxDialog methods of JOptionPane:
import javax.swing.*; public class Option extends JFrame { public static void main(String[] args) { new Option(); } public Option() { ImageIcon icon = new ImageIcon("icon.gif"); String[] options = { "Jupiter", "Saturn", "Uranus", "Neptune" }; String initialValue = "Saturn"; int i; String s; Object o; //------------------------------------------------------------------- // message dialog with a single Ok button // arguments are: parent, message, title, icon type, icon // PLAIN_MESSAGE results in no icon displayed //------------------------------------------------------------------- JOptionPane.showMessageDialog(this, "Mercury"); // title is "Message", information icon JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.PLAIN_MESSAGE); JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.QUESTION_MESSAGE); JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.WARNING_MESSAGE); // custom icon - in this case the icon type is ignored JOptionPane.showMessageDialog(this, "Mercury", "Planets", JOptionPane.QUESTION_MESSAGE, icon); //------------------------------------------------------------------- // confirmation dialog with multiple buttons // arguments are: parent, message, title, option type (e.g. buttons to show), icon type, icon // return value is one of: OK_OPTION, CANCEL_OPTION, YES_OPTION, NO_OPTION // return value is CLOSED_OPTION if the user clicked window manager close button //------------------------------------------------------------------- // this one produces: title = "Select an Option", question icon, yes/no/cancel buttons i = JOptionPane.showConfirmDialog(this, "Venus?"); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.DEFAULT_OPTION); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.OK_CANCEL_OPTION); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.YES_NO_CANCEL_OPTION); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.YES_NO_OPTION); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); i = JOptionPane.showConfirmDialog(this, "Venus?", "Planets", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, icon); //------------------------------------------------------------------- // confirmation dialog with customizable buttons // arguments are: // parent, message, title, option type, icon type, icon, button labels, label of default button // option type is not used // return value is 0 to options.length-1, or CLOSED_OPTION if user cancelled //------------------------------------------------------------------- i = JOptionPane.showOptionDialog(this, "Mars", "Planets", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, icon, options, initialValue); i = JOptionPane.showOptionDialog(this, "Mars", "Planets", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, initialValue); i = JOptionPane.showOptionDialog(this, "Mars", "Planets", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, icon, options, null); i = JOptionPane.showOptionDialog(this, "Mars", "Planets", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, null); //------------------------------------------------------------------- // text dialog // arguments are: parent, message, title, icon type // return value is the entered string, or null if the user cancelled //------------------------------------------------------------------- s = JOptionPane.showInputDialog(this, "Planet?"); s = JOptionPane.showInputDialog(this, "Planet?", "Saturn"); s = JOptionPane.showInputDialog(this, "Planet?", "Pick a Planet", JOptionPane.QUESTION_MESSAGE); //------------------------------------------------------------------- // choice dialog displaying a combo box // arguments are: parent, message, title, icon type, icon, options, initial value // return value is the selected option, or null if the user cancelled //------------------------------------------------------------------- o = JOptionPane.showInputDialog(this, "Planet?", "Pick a Planet", JOptionPane.QUESTION_MESSAGE, icon, options, initialValue); o = JOptionPane.showInputDialog(this, "Planet?", "Pick a Planet", JOptionPane.QUESTION_MESSAGE, null, options, initialValue); o = JOptionPane.showInputDialog(this, "Planet?", "Pick a Planet", JOptionPane.QUESTION_MESSAGE, icon, options, null); o = JOptionPane.showInputDialog(this, "Planet?", "Pick a Planet", JOptionPane.QUESTION_MESSAGE, null, options, null); } }
to top

Setting the Application Icon

Use the JFrame's setIconImage() method:
JFrame frame = new JFrame("Frump"); frame.setIconImage(new ImageIcon(imgURL).getImage());
A convenient way to manage the icon is to simply include it in the same directory as the .class file, and use the getResource() method of the Class class:
class CoolWindow extends JFrame { public CoolWindow() { setIconImage(new ImageIcon( getClass().getResource("cool.gif")).getImage()); ... } ... }
to top

Easy GridBagLayout

It's really annoying that there's no standard layout manager that lets you *easily* layout some labels and text fields in columns that would be suitable for your basic dialog box. I guess GridBagLayout is the best of a poor bunch, but it is so painful to use - you have to waste time reading the doc every time you use it (unless you use twice a day for a couple of years.
Anyway, here's a brain-dead method that you can just paste into your class, if you're not concerned about elegance or maintainability. I find it useful for just rapidly getting things going. If you're serious, you can always turn it into a utility method and clean it up.
private void place(int x, int y, int w, int h, double wx, double wy, String fill, String anchor, Insets insets, Container p, GridBagLayout gbl, GridBagConstraints gbc, Component c) { gbc.gridx = x; gbc.gridy = y; gbc.gridwidth = w; gbc.gridheight = h; gbc.weightx = wx; gbc.weighty = wy; if ("nw".equals(anchor)) gbc.anchor = GridBagConstraints.NORTHWEST; if ("n".equals(anchor)) gbc.anchor = GridBagConstraints.NORTH; if ("ne".equals(anchor)) gbc.anchor = GridBagConstraints.NORTHEAST; if ("w".equals(anchor)) gbc.anchor = GridBagConstraints.WEST; if ("c".equals(anchor)) gbc.anchor = GridBagConstraints.CENTER; if ("e".equals(anchor)) gbc.anchor = GridBagConstraints.EAST; if ("sw".equals(anchor)) gbc.anchor = GridBagConstraints.SOUTHWEST; if ("s".equals(anchor)) gbc.anchor = GridBagConstraints.SOUTH; if ("se".equals(anchor)) gbc.anchor = GridBagConstraints.SOUTHEAST; if ("n".equals(fill)) gbc.fill = GridBagConstraints.NONE; if ("h".equals(fill)) gbc.fill = GridBagConstraints.HORIZONTAL; if ("v".equals(fill)) gbc.fill = GridBagConstraints.VERTICAL; if ("b".equals(fill)) gbc.fill = GridBagConstraints.BOTH; if (insets != null) gbc.insets = insets; gbl.setConstraints(c, gbc); p.add(c); } private void build() { // and here's how to lay out four rows, the first three with // label and text field, the last with label and list, so that // the text fields expand horizontally & the list expands // horizontally and vertically: GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); JPanel p = new JPanel(); p.setLayout(gbl); Insets i = new Insets(3, 3, 3, 3); // creation of widgets omitted place(0, 0, 1, 1, 0, 0, "b", "w", i, p, gbl, gbc, label1); place(0, 1, 1, 1, 0, 0, "b", "w", i, p, gbl, gbc, label2); place(0, 2, 1, 1, 0, 0, "b", "w", i, p, gbl, gbc, label3); place(0, 3, 1, 1, 0, 0, "n", "nw", i, p, gbl, gbc, label4); place(1, 0, 1, 1, 1, 0, "b", "w", i, p, gbl, gbc, text1); place(1, 1, 1, 1, 1, 0, "b", "w", i, p, gbl, gbc, text2); place(1, 2, 1, 1, 1, 0, "b", "w", i, p, gbl, gbc, text3); place(1, 3, 1, 1, 1, 0, "b", "w", i, p, gbl, gbc, list); // etc. etc. }
to top

CardLayout

Create the cards like this:
private final static String CARD1 = "card1"; private final static String CARD2 = "card2"; // etc. private CardLayout cardLayout; private JPanel panel; cardLayout = new CardLayout(); panel = new JPanel(cardLayout); JPanel card1 = buildCard1(); panel.add(card1, CARD1); JPanel card2 = buildCard2(); panel.add(card1, CARD2); // etc.
Then you can set the card to want to be visible with:
cardLayout.show(panel, CARD1);
If you don't want to store a reference to the layout, you can always do:
private JPanel panel; panel = new JPanel(new CardLayout()); CardLayout layout = (CardLayout) panel.getLayout(); layout.show(panel, CARD2);
to top

UIManager Defaults

You can globally set defaults for many features of Swing controls using the UIManager class. This is perhaps most useful when you need to change the default font or color for an entire application. The basic approach is:
UIManager.put(key, value);
As an example, the default foreground color for JLabel in the Swing Metal look-and-feel often turns out to be a cheesy blue, which clashes with the black used on buttons and other component. You can make all the labels use black text just by including the following line near the beginning of the application:
UIManager.put("Label.foreground", Color.black);
The keys in the call to the put method are strings like "Tree.background", and the values are objects of various classes, such as Color, Font, String or Integer. The actual type depends on what's expected for the key. The set of permissible keys doesn't seem to be defined anywhere.
However, many of the keys appear to be used consistently by the built-in look-and-feels. The following little app prints out the keys and the expected value type for all defaults:
import java.awt.*; import java.util.*; import javax.swing.*; import javax.swing.border.*; public class UIDefs { public static void main(String[] args) { UIDefaults uidefs = UIManager.getLookAndFeelDefaults(); String[] keys = (String[]) uidefs.keySet().toArray(new String[0]); Arrays.sort(keys); for (int i = 0; i < keys.length; i++) { String k = keys[i]; Object v = uidefs.get(k); if (v instanceof Integer) { int intVal = uidefs.getInt(k); System.out.println(k + ": int"); } else if (v instanceof String) { String strVal = uidefs.getString(k); System.out.println(k + ": string"); } else if (v instanceof Dimension) { Dimension dimVal = uidefs.getDimension(k); System.out.println(k + ": dimension"); } else if (v instanceof Insets) { Insets insetsVal = uidefs.getInsets(k); System.out.println(k + ": insets"); } else if (v instanceof Color) { Color colorVal = uidefs.getColor(k); System.out.println(k + ": color"); } else if (v instanceof Font) { Font fontVal = uidefs.getFont(k); System.out.println(k + ": font"); } else if (v instanceof Border) { Border borderVal = uidefs.getBorder(k); System.out.println(k + ": border"); } else if (v instanceof Icon) { Icon iconVal = uidefs.getIcon(k); System.out.println(k + ": icon"); } else if ( v instanceof javax.swing.text.JTextComponent.KeyBinding[]) { javax.swing.text.JTextComponent.KeyBinding[] keyBindsVal = (javax.swing.text.JTextComponent.KeyBinding[]) v; System.out.println(k + ": key binding"); } else if (v instanceof InputMap) { InputMap imapVal = (InputMap) v; System.out.println(k + ": input map"); } else if (v != null) { System.out.println(k + ": " + v.getClass().getName()); } else { System.out.println(k + ": null"); } } } }
Here's the result of running this app: Typical Defaults
to top

Uneditable Text Fields

Sometimes you want to use a plain, ordinary text field as a label; that is, you don't want the user to be able to edit it. You can call setEditable(false) on a JTextField, but that changes the color and appearance. Here's a way to make a text field whose content can be modified programmatically, but not by the user:
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.text.*; public class Main extends JFrame { public static void main(String[] args) { new Main(); } public Main() { JPanel p = new JPanel(new BorderLayout()); final JTextField text = new UneditableText(20); p.add(text, BorderLayout.NORTH); JButton button = new JButton("Test"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { text.setText("nummy"); } }); p.add(button, BorderLayout.CENTER); getContentPane().add(p); pack(); setVisible(true); } } class UneditableText extends JTextField { public UneditableText(int columns) { super(new SwitchableDocument(), "", columns); } public void setText(String s) { SwitchableDocument doc = (SwitchableDocument) getDocument(); doc.setModifiable(true); super.setText(s); doc.setModifiable(false); } } class SwitchableDocument extends DefaultStyledDocument { private boolean modifiable; public SwitchableDocument() { modifiable = false; } public void setModifiable(boolean f) { modifiable = f; } public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if (modifiable) super.insertString(offs, str, a); } public void remove(int offs, int len) throws BadLocationException { if (modifiable) super.remove(offs, len); } }
It's probably possible to do the same thing by calling setEditable(false), and then playing with colors and so on, but I couldn't get it to work.
to top

Forcing Text Area to Scroll

If you're using a text area in a scroll pane to show log message, you probably want it to scroll to the bottom each time a new block of text is appended. Here's an easy way to do it:
text.append(block); text.setCaretPosition(text.getDocument().getLength());
to top

Listening for Text Changes

Here's how to listen for changes in a text component:
textArea = new JTextArea(); textArea.getDocument().addDocumentListener(new MyDocumentListener()); class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { // text inserted } public void removeUpdate(DocumentEvent e) { // text removed } public void changedUpdate(DocumentEvent e) { //Plain text components don't fire these events } }
to top

Showing HTML with a Stylesheet

The JEditorPane can display HTML. It can also be configured to use a stylesheet.
The stylesheet support, however, is quite primitive. Here is the list of stylesheet attributes that are supported.
It's not clear whether any of the CSS selectors, other than for plain elements, are supported. For example, id and class selectors don't seem to have any effect. Many simple settings don't seem to work either (e.g. setting background color for th elements).
Here's some example code:
import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import javax.swing.*; import javax.swing.text.html.*; public class HtmlDisplayer { public HtmlDisplayer(String pageUrl, String stylesheetUrl) { try { StyleSheet ss = new StyleSheet(); ss.importStyleSheet(new URL(stylesheetUrl)); HTMLEditorKit kit = new HTMLEditorKit(); kit.setStyleSheet(ss); JEditorPane editorPane = new JEditorPane(); editorPane.setEditorKit(kit); editorPane.setPage(pageUrl); JScrollPane scroll = new JScrollPane(editorPane); JFrame frame = new JFrame(); frame.getContentPane().add(scroll, BorderLayout.CENTER); frame.setSize(600, 600); frame.setLocation(200, 200); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setVisible(true); } catch (IOException e) { System.out.println(e.getMessage()); } } }
to top

JButton Basics

import javax.swing.*; import java.awt.event.*; JButton b = new JButton("Exit"); b.setMnemonic('x'); b.addActionListener(new ActionListener(ActionEvent e)) { public void actionPerformed(ActionEvent e) { exit(); } }); b.setEnabled(true);
to top

Handling Window Border Close

When the user closes a window by clicking on the close button in the window manager border, you probably want to do the same as if the user pressed the Close or Cancel button that you've created in the window. The old mechanism is to add a window listener, and call the same method that gets called by your button:
addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { cancel(); } });
Since 1.4, JFrame and JDialog provide a setDefaultCloseOperation() method. It's a bit easier, and works like this:
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
to top

Centering a Frame/Dialog

This ain't too complicated:
public static void centerOnScreen(JFrame f) { Dimension s = Toolkit.getDefaultToolkit().getScreenSize(); Dimension d = f.getSize(); f.setLocation((s.width - d.width)/2, (s.height - d.height)/2); } public static void centerOnScreen(JDialog dlg) { Dimension s = Toolkit.getDefaultToolkit().getScreenSize(); Dimension d = dlg.getSize(); dlg.setLocation((s.width - d.width)/2, (s.height - d.height)/2); }
to top

Anti-aliased Drawing

Anti-aliasing improves the quality of shapes drawn to a window or image:
RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); ... public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHints(renderHints); ... // go ahead and draw something }
to top

Changing the Font on the Fly

Here's the technique:
javax.swing.plaf.FontUIResource fr = new javax.swing.plaf.FontUIResource( font.getFontName(), font.getStyle(), font.getSize()); java.util.Enumeration keys = UIManager.getDefaults().keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); Object value = UIManager.get (key); if (value instanceof javax.swing.plaf.FontUIResource) { UIManager.put (key, fr); } } javax.swing.SwingUtilities.updateComponentTreeUI(frame);
You can also just change the font for a particular type of widget, by checking the values of the keys. These are "TextField.font", "Button.font", etc - see the section on UI Defaults.
to top

JList Basics

import javax.swing.*; import javax.swing.event.*; import java.awt.event.*; int index; Object obj; DefaultListModel model = new DefaultListModel(); model.addElement(obj); model.setElementAt(obj, index); model.insertElementAt(obj, index); model.removeElementAt(index); int size = model.size(); JList list = new JList(model); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(javax.swing.event.ListSelectionEvent e) { // if you don't check for adjusting, you get called twice! if (!e.getValueIsAdjusting()) { select(); } } }); list.addMouseListener(new MouseAdapter()) { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int index = list.locationToIndex(e.getPoint()); doubleClick(index); } } }); JScrollPane scroll = new JScrollPane(list); // add scroll to the user interface Object sel = (Object) list.getSelectedValue(); int index = list.getSelectedIndex(); Object[] sels = (Object[]) list.getSelectedValues(); int[] indexes = list.getSelectedIndexes(); list.setSelectedIndex(index); list.setSelectedIndexes(indexes);
to top

JComboBox Basics

import javax.swing.*; import javax.swing.event.*; import java.awt.event.*; JComboBox cb = new JComboBox(); cb.addItem(obj0); cb.addItem(obj1); cb.addItem(obj2); ... JComboBox cb1 = new JComboBox(new Object[] { obj1, obj2, ... }); cb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { select(); } }); cb.setMaximumRowCount(16); // max # displayed rows cb.setSelectedIndex(1); cb.setSelectedItem(obj1); int i = cb.getSelectedIndex(); Object obj = cb.getSelectedItem(); cb.setEditable(true); // can type in a value
to top