Grid Bag Layout

GridBagLayout is one of the most flexible — and complex — layout managers the Java platform provides. A GridBagLayout places components in a grid of rows and columns, allowing specified components to span multiple rows or columns. Not all rows necessarily have the same height. Similarly, not all columns necessarily have the same width. Essentially, GridBagLayout places components in rectangles (cells) in a grid, and then uses the components’ preferred sizes to determine how big the cells should be.

Example

package observer;

import java.awt.*;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JTextField;

public class GridBagLayoutDemo {

	public static void addComponentsToPane(Container pane) {

		JButton jbnButton;
		pane.setLayout(new GridBagLayout());
		GridBagConstraints gBC = new GridBagConstraints();
		gBC.fill = GridBagConstraints.HORIZONTAL;

		jbnButton = new JButton("Button 1");
		gBC.weightx = 0.5;
		gBC.gridx = 0;
		gBC.gridy = 0;
		pane.add(jbnButton, gBC);

		JTextField jtf = new JTextField("TextField 1");
		gBC.gridx = 2;
		gBC.gridy = 0;
		jtf.setEditable(false);
		pane.add(jtf, gBC);

		jbnButton = new JButton("Button 3");
		gBC.gridx = 2;
		gBC.gridy = 0;
		pane.add(jbnButton, gBC);

		jbnButton = new JButton("Button 4");
		gBC.ipady = 40; // This component has more breadth compared to other
						// buttons
		gBC.weightx = 0.0;
		gBC.gridwidth = 3;
		gBC.gridx = 0;
		gBC.gridy = 1;
		pane.add(jbnButton, gBC);

		JComboBox jcmbSample = new JComboBox(new String[] { "ComboBox 1", "hi",
				"hello" });
		gBC.ipady = 0;
		gBC.weighty = 1.0;
		gBC.anchor = GridBagConstraints.PAGE_END;
		gBC.insets = new Insets(10, 0, 0, 0); // Padding
		gBC.gridx = 1;
		gBC.gridwidth = 2;
		gBC.gridy = 2;
		pane.add(jcmbSample, gBC);
	}

	private static void createAndShowGUI() {

		JFrame.setDefaultLookAndFeelDecorated(true);
		JFrame frame = new JFrame("GridBagLayout Source Demo");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		// Set up the content pane.
		addComponentsToPane(frame.getContentPane());

		frame.pack();
		frame.setVisible(true);
	}

	public static void main(String[] args) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				createAndShowGUI();
			}
		});
	}
}

Output

gridBagLayout

Look and Feel customize

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class ResourceModExample {
	public static void main(String[] args) {

		// A custom border for all buttons
		Border border = BorderFactory.createRaisedBevelBorder();
		Border tripleBorder = new CompoundBorder(new CompoundBorder(border, border), border);

		UIManager.put("Button.border", tripleBorder);

		// Custom icons for internal frames
		UIManager.put("InternalFrame.closeIcon", new ImageIcon("close.gif"));
		UIManager.put("InternalFrame.iconizeIcon", new ImageIcon("iconify.gif"));
		UIManager.put("InternalFrame.maximizeIcon", new ImageIcon("maximize.gif"));
		UIManager.put("InternalFrame.altMaximizeIcon", new ImageIcon("altMax.gif"));

		// A custom internal frame title font
		UIManager.put("InternalFrame.titleFont", new Font("Serif", Font.ITALIC, 12));

		// Make scrollbars really wide.

		UIManager.put("ScrollBar.width", new Integer(30));

		// Throw together some components to show what we've done. Nothing below
		// here is
		// L&F-specific.
		// ***********************************
		JFrame f = new JFrame();
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container c = f.getContentPane();

		JDesktopPane desk = new JDesktopPane();
		c.add(desk, BorderLayout.CENTER);

		JButton cut = new JButton("Cut");
		JButton copy = new JButton("Copy");
		JButton paste = new JButton("Paste");

		JPanel p = new JPanel(new FlowLayout());
		p.add(cut);
		p.add(copy);
		p.add(paste);
		c.add(p, BorderLayout.SOUTH);

		JInternalFrame inf = new JInternalFrame("MyFrame", true, true, true, true);
		JLabel l = new JLabel(new ImageIcon("luggage.jpeg"));
		JScrollPane scroll = new JScrollPane(l);
		inf.setContentPane(scroll);
		inf.setBounds(10, 10, 350, 280);
		desk.add(inf);
		inf.setVisible(true);

		f.setSize(380, 360);
		f.setVisible(true);
	}
}

The Timer Class

The Timer class provides a mechanism to generate timed events. It has properties and events, and thus can be used in application builders that understand JavaBeans. It fires an ActionEvent at a given time. The timer can be set to repeat, and an optional initial delay can be set before the repeating event starts.

import javax.swing.*;
import javax.swing.Timer;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

class ClockLabel extends JLabel implements ActionListener {

	public ClockLabel() {
		super("" + new Date());
		Timer t = new Timer(1000, this);
		t.start();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		setText((new Date()).toString());

	}
}

public class ClockTest extends JFrame {

	public ClockTest() {
		super("Timer Demo");
		setSize(300, 100);
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		ClockLabel clock = new ClockLabel();
		getContentPane().add(clock, BorderLayout.NORTH);
	}

	public static void main(String args[]) {
		ClockTest ct = new ClockTest();
		ct.setVisible(true);
	}
}

Making a Component Draggable in Java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SwingDragDrop {
	JTextField txtField;
	JLabel lbl;

	public static void main(String[] args) {
		SwingDragDrop sdd = new SwingDragDrop();
	}

	public SwingDragDrop() {
		JFrame frame = new JFrame("Drag Drop Demo");
		txtField = new JTextField(20);
		lbl = new JLabel("This is the text for drag and drop.");
		lbl.setTransferHandler(new TransferHandler("text"));
		MouseListener ml = new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				JComponent jc = (JComponent) e.getSource();
				TransferHandler th = jc.getTransferHandler();
				th.exportAsDrag(jc, e, TransferHandler.COPY);
			}
		};
		lbl.addMouseListener(ml);
		JPanel panel = new JPanel();
		panel.add(txtField);
		frame.add(lbl, BorderLayout.CENTER);
		frame.add(panel, BorderLayout.NORTH);
		frame.setSize(400, 400);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setResizable(false);
	}
}

Highlighters

Highlighters determine how text is marked to make it stand out. The order in which we discuss the highlighter interfaces may seem counterintuitive. The basic Highlighter interface is so straightforward that you’ll rarely need to work with it directly, so we will describe it later. At this point, we discuss the interface you’re most likely to use first: the Highlighter.HighlightPainter interface.

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;

public class LineHighlightPainter implements 
Highlighter.HighlightPainter {

	// Paint a thick line under one line of text, from r 
	// extending rightward to x2.
	private void paintLine(Graphics g, Rectangle r, int x2) {
		int ytop = r.y + r.height - 3;
		g.fillRect(r.x, ytop, x2 - r.x, 3);
	}

	// Paint thick lines under a block of text.
	public void paint(Graphics g, int p0, int p1, Shape bounds, 
JTextComponent c) 
        {
		Rectangle r0 = null, r1 = null, rbounds = bounds.getBounds();
		int xmax = rbounds.x + rbounds.width; 
// x-coordinate of right edge
		try { // Convert positions to pixel coordinates.
			r0 = c.modelToView(p0);
			r1 = c.modelToView(p1);
		} catch (BadLocationException ex) {
			return;
		}
		if ((r0 == null) || (r1 == null))
			return;

		g.setColor(c.getSelectionColor());

		// Special case if p0 and p1 are on the same line
		if (r0.y == r1.y) {
			paintLine(g, r0, r1.x);
			return;
		}

		// First line, from p1 to end-of-line
		paintLine(g, r0, xmax);

		// All the full lines in between, if any 
		// (assumes that all lines have the same height--not 
		// a good assumption with JEditorPane/JTextPane)

		r0.y += r0.height; // Move r0 to next line.
		r0.x = rbounds.x; // Move r0 to left edge.
		while (r0.y < r1.y) {
			paintLine(g, r0, xmax);
			r0.y += r0.height; // Move r0 to next line.
		}

		// Last line, from beginning-of-line to p1
		paintLine(g, r0, r1.x);
	}

	public static void main(String args[]) {

		// Extend DefaultCaret as an anonymous inner class.
		Caret lineHighlightPainterCaret = new DefaultCaret() {
			private Highlighter.HighlightPainter lhp = 
new LineHighlightPainter();

		// Override getSelectionPainter to return 
                // the LineHighlightPainter.
		protected Highlighter.HighlightPainter getSelectionPainter() {
		 return lhp;
			}
		};

		JFrame frame = new JFrame("LineHighlightPainter demo");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JTextArea area = new JTextArea(9, 45);
		area.setCaret(lineHighlightPainterCaret);
		area.setLineWrap(true);
		area.setWrapStyleWord(true);
		area.setText("This is the story\nof 
the hare who\nlost his spectacles.");
		frame.getContentPane().add(new JScrollPane(area), 
BorderLayout.CENTER);
		frame.pack();
		frame.setVisible(true);
	}
}

The JTextPane Class

JTextPane is a multiline text component that can display text with multiple fonts, colors, and even embedded images. It supports named hierarchical text styles and has other features that can help implement a word processor or a similar application.
 
Technically, JTextPane is a subclass of JEditorPane, but it usually makes more sense to think of JTextPane as a text component in its own right. JEditorPane uses “editor kits” to handle text in various formats (such as HTML or RTF) in a modular way. Use a JEditorPane when you want to view or edit text in one of these formats. Use a JTextPane when you want to handle the text yourself.

import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.event.*;

// Show how icons, components, and text can be added to a JTextPane.
public class PaneInsertionMethods {

	public static void main(String[] args) {

		final JTextPane pane = new JTextPane();

		// Button to insert some text
		JButton textButton = new JButton("Insert Text");
		textButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				pane.replaceSelection("text");
			}
		});

		// Button to insert an icon
		final ImageIcon icon = new ImageIcon("bluepaw.gif");
		JButton iconButton = new JButton(icon);
		iconButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				pane.insertIcon(icon);
			}
		});

		// Button to insert a button
		JButton buttonButton = new JButton("Insert Button");
		buttonButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				pane.insertComponent(new JButton("Click Me"));
			}
		});

		// Layout
		JPanel buttons = new JPanel();
		buttons.add(textButton);
		buttons.add(iconButton);
		buttons.add(buttonButton);

		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(pane, BorderLayout.CENTER);
		frame.getContentPane().add(buttons, BorderLayout.SOUTH);
		frame.setSize(360, 180);
		frame.setVisible(true);
	}
}

Formatted Text Fields

Swing provides extended functionality for text fields through the JFormattedTextField class introduced in SDK 1.4. A JFormattedTextField can display its value in a friendly (and locale-specific) way, enforce restrictions on its value, be used to edit non-String objects, and permit its value (or part of its value) to be incremented or decremented with the keyboard.

import javax.swing.*;

public class SimpleFTF extends JPanel {

	public SimpleFTF() {
		JFormattedTextField ftf[] = new JFormattedTextField[7];
                // Description of each field
		String des[] = new String[ftf.length]; 

		des[0] = "Date";
		ftf[0] = new JFormattedTextField(new java.util.Date());

		des[1] = "Integer";
		ftf[1] = new JFormattedTextField(new Integer(90032221));

		des[2] = "Float";
		ftf[2] = new JFormattedTextField(new Float(3.14));
                // Manually specify a NumberFormat.
		des[3] = "Float work-around"; 
		ftf[3] = 
new JFormattedTextField(java.text.NumberFormat.getInstance());
		ftf[3].setValue(new Float(3.14));

		des[4] = "currency";
		ftf[4] = new JFormattedTextField(java.text.NumberFormat.
                         getCurrencyInstance());
		ftf[4].setValue(new Float(5.99));

		des[5] = "percent";
		ftf[5] = new JFormattedTextField(java.text.NumberFormat.
                         getPercentInstance());
		ftf[5].setValue(new Float(0.33));

                // Works via 1-arg String constructor and // toString( )
		des[6] = "java.net.URL"; 
					
		java.net.URL u = null;
		try {
			u = new java.net.URL("http://www.ora.com/");
		} catch (java.net.MalformedURLException ignored) {
		}
		ftf[6] = new JFormattedTextField(u);
		ftf[6].setColumns(24);

		// Add each ftf[] to a BoxLayout.
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		for (int j = 0; j < ftf.length; j += 1) {
			JPanel borderPanel = 
new JPanel(new java.awt.BorderLayout());
			borderPanel.setBorder(
new javax.swing.border.TitledBorder(des[j]));
			borderPanel.add(ftf[j], java.awt.BorderLayout.CENTER);
			add(borderPanel);
		}
	}

	public static void main(String argv[]) {
		String localeString = 
java.util.Locale.getDefault().getDisplayName();
		JFrame f = new JFrame("SimpleFTF " + localeString);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.setContentPane(new SimpleFTF());
		f.pack();
		f.setVisible(true);
	}
}

The JTextArea Class

The JTextArea class displays multiple lines of text in a single font and style. Its default behavior is not to wrap lines of text, but line-wrapping can be enabled on word or character boundaries.
 
Like all Swing JTextComponents (but unlike java.awt.TextArea), JTextArea lacks integrated scrollbars. Fortunately, it is easy to embed a JTextArea inside a JScrollPane for seamless scrolling. (JTextArea implements the Scrollable interface, so JScrollPane can be intelligent about scrolling it.)
 
JTextArea handles newlines in a cross-platform way. Line separators in text files can be newline (\n), carriage return (\r), or carriage return newline (\r\n), depending on the platform. Swing’s text components remember which line separator was originally used, but always use a newline character to represent one in memory. So always use \n when working with the content of a text component. When writing the content back to disk (or to whatever destination you give the write( ) method), the text component translates newlines back to the remembered type. If there is no remembered type (because the content was created from scratch), newlines are translated to the value of the line.separator system property.

import javax.swing.*;
import javax.swing.text.*;

public class OffsetTest {
	public static void main(String[] args) {
		JTextArea ta = new JTextArea();
		ta.setLineWrap(true);
		ta.setWrapStyleWord(true);
		JScrollPane scroll = new JScrollPane(ta);

		// Add three lines of text to the JTextArea.
		ta.append("The first line.\n");
		ta.append("Line Two!\n");
		ta.append("This is the 3rd line of this document.");

		// Print some results.
		try {
			for (int n = 0; n < ta.getLineCount(); n += 1)
				System.out.println("line " + n + " starts at " + 
                               ta.getLineStartOffset(n) + ", ends at "
						+ ta.getLineEndOffset(n));
			System.out.println();

			int n = 0;
			while (true) {
				System.out.print("offset " + n + " is on ");
				System.out.println("line " + ta.getLineOfOffset(n));
				n += 13;
			}
		} catch (BadLocationException ex) {
			System.out.println(ex);
		}

		// Layout
		JFrame f = new JFrame();
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.getContentPane().add(scroll, java.awt.BorderLayout.CENTER);
		f.setSize(150, 150);
		f.setVisible(true);
	}
}

A Simple Swing Form

One of the most common user-interface constructs is the basic form. Typically, forms are made up of labels and fields, with the label describing the text to be entered in the field. Here’s a primitive TextForm class that shows the use of mnemonics, tooltips, and basic accessibility support. Note that we call setLabelFor( ) to associate each label with a text field. This association allows the mnemonics to set the focus and, together with setToolTipText( ), supports accessibility.

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

// A simple label/field form panel
public class TextForm extends JPanel {

	private JTextField[] fields;

	// Create a form with the specified labels, tooltips, and sizes.
	public TextForm(String[] labels, char[] mnemonics, int[] widths, 
                        String[] tips) {
		super(new BorderLayout());
		JPanel labelPanel = new JPanel(new GridLayout(labels.length, 1));
		JPanel fieldPanel = new JPanel(new GridLayout(labels.length, 1));
		add(labelPanel, BorderLayout.WEST);
		add(fieldPanel, BorderLayout.CENTER);
		fields = new JTextField[labels.length];

		for (int i = 0; i < labels.length; i += 1) {
			fields[i] = new JTextField();
			if (i < tips.length)
				fields[i].setToolTipText(tips[i]);
			if (i < widths.length)
				fields[i].setColumns(widths[i]);

			JLabel lab = new JLabel(labels[i], JLabel.RIGHT);
			lab.setLabelFor(fields[i]);
			if (i < mnemonics.length)
				lab.setDisplayedMnemonic(mnemonics[i]);

			labelPanel.add(lab);
			JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
			p.add(fields[i]);
			fieldPanel.add(p);
		}
	}

	public String getText(int i) {
		return (fields[i].getText());
	}

	public static void main(String[] args) {
		String[] labels = { "First Name", "Middle Initial", "Last Name", 
                                    "Age" };
		char[] mnemonics = { 'F', 'M', 'L', 'A' };
		int[] widths = { 15, 1, 15, 3 };
		String[] descs = { "First Name", "Middle Initial", "Last Name", 
                                    "Age" };

		final TextForm form = new TextForm(labels, mnemonics, widths, 
descs);

		JButton submit = new JButton("Submit Form");

		submit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.out.println(form.getText(0) + " " + 
                          form.getText(1) + ". " + form.getText(2) + 
                          ", age " + form.getText(3));
			}
		});

		JFrame f = new JFrame("Text Form Example");
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		f.getContentPane().add(form, BorderLayout.NORTH);
		JPanel p = new JPanel();
		p.add(submit);
		f.getContentPane().add(p, BorderLayout.SOUTH);
		f.pack();
		f.setVisible(true);
	}
}

The JTextField Class

JTextField allows the user to enter a single line of text, scrolling the text if its size exceeds the physical size of the field. A JTextField fires an ActionEvent to any registered ActionListeners (including the Action set via the setAction( ) method, if any) when the user presses the Enter key.
 
JTextFields (and all JTextComponents) are automatically installed with a number of behaviors appropriate to the L&F, so cut/copy/paste, special cursor movement keys, and text-selection gestures should work without any extra intervention on your part.

import javax.swing.*;
import java.awt.event.*;

public class JTextFieldExample {

	public static void main(String[] args) {

		final JTextField tf = new JTextField("press <enter>", 20);
		tf.setHorizontalAlignment(JTextField.RIGHT);

		tf.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int old = tf.getHorizontalAlignment();
				if (old == JTextField.LEFT)
					tf.setHorizontalAlignment(JTextField.RIGHT);
				if (old == JTextField.RIGHT)
					tf.setHorizontalAlignment(JTextField.CENTER);
				if (old == JTextField.CENTER)
					tf.setHorizontalAlignment(JTextField.LEFT);
			}
		});

		JFrame frame = new JFrame("JTextFieldExample");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(new java.awt.FlowLayout());
		frame.getContentPane().add(tf);
		frame.setSize(275, 75);
		frame.setVisible(true);
		tf.requestFocus();
	}
}

The Swing Text Components

Swing provides an extensive collection of classes for working with text in user interfaces. In fact, because there’s so much provided for working with text, Swing’s creators placed most of it into its own package: javax.swing.text. This package’s dozens of interfaces and classes (plus the six concrete component classes in javax.swing) provide a rich set of text-based models and components complex enough to allow endless customization yet simple to use in the common case.

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;
import java.awt.*;

public class TextComponentSampler extends JFrame {

	public static String word = "portmeiron";
	public static String markup = "Questions are <font size='+1' 
color='blue'>a burden</font> to others,\n"+ "answers <font size='+2' 
color='red'>a prison</font> for oneself.";

	public TextComponentSampler() {
		super("TextComponentSampler");

		JTextField tf = new JTextField(word, 12);
		JPasswordField pf = new JPasswordField(word, 12);

		MaskFormatter formatter = null;
		try {
			formatter = new MaskFormatter("UUUUU");
		} catch (java.text.ParseException ex) {
		}
		JFormattedTextField ftf = new JFormattedTextField(formatter);
		ftf.setColumns(12);
		ftf.setValue(word);

		JTextArea ta1 = new JTextArea(markup);
		JScrollPane scroll1 = new JScrollPane(ta1);

		JTextArea ta2 = new JTextArea(markup);
		ta2.setLineWrap(true);
		ta2.setWrapStyleWord(true);
		JScrollPane scroll2 = new JScrollPane(ta2);

		JTextPane tp = new JTextPane();
		tp.setText(markup);
		// Create an AttributeSet with which to change color and font.
		SimpleAttributeSet attrs = new SimpleAttributeSet();
		StyleConstants.setForeground(attrs, Color.blue);
		StyleConstants.setFontFamily(attrs, "Serif");
		// Apply the AttributeSet to a few blocks of text.
		StyledDocument sdoc = tp.getStyledDocument();
		sdoc.setCharacterAttributes(14, 29, attrs, false);
		sdoc.setCharacterAttributes(51, 7, attrs, false);
		sdoc.setCharacterAttributes(78, 28, attrs, false);
		sdoc.setCharacterAttributes(114, 7, attrs, false);
		JScrollPane scroll3 = new JScrollPane(tp);

		JEditorPane ep1 = new JEditorPane("text/plain", markup);
		JScrollPane scroll4 = new JScrollPane(ep1);

		JEditorPane ep2 = new JEditorPane("text/html", markup);
		JScrollPane scroll5 = new JScrollPane(ep2);

		// Done creating text components; now lay them out and 
                // make them pretty.
		JPanel panel_tf = new JPanel();
		JPanel panel_pf = new JPanel();
		JPanel panel_ftf = new JPanel();
		panel_tf.add(tf);
		panel_pf.add(pf);
		panel_ftf.add(ftf);

		panel_tf.setBorder(new TitledBorder("JTextField"));
		panel_pf.setBorder(new TitledBorder("JPasswordField"));
		panel_ftf.setBorder(new TitledBorder("JFormattedTextField"));
		scroll1.setBorder(new TitledBorder("JTextArea (line wrap off)"));
		scroll2.setBorder(new TitledBorder("JTextArea (line wrap on)"));
		scroll3.setBorder(new TitledBorder("JTextPane"));
		scroll4.setBorder(new TitledBorder("JEditorPane (text/plain)"));
		scroll5.setBorder(new TitledBorder("JEditorPane (text/html)"));

		JPanel pan = new JPanel(new FlowLayout(FlowLayout.LEFT));
		pan.add(panel_tf);
		pan.add(panel_pf);
		pan.add(panel_ftf);

		Container contentPane = getContentPane();
		contentPane.setLayout(new GridLayout(2, 3, 8, 8));

		contentPane.add(pan);
		contentPane.add(scroll1);
		contentPane.add(scroll2);
		contentPane.add(scroll3);
		contentPane.add(scroll4);
		contentPane.add(scroll5);
	}

	public static void main(String args[]) {
		JFrame frame = new TextComponentSampler();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(600, 450);
		frame.setVisible(true);
	}
}

The JTree Class

Now that you’ve seen all the tree models and some of the default implementations, let’s look at the visual representation we can give them. The JTree class can build up trees out of several different objects, including a TreeModel. JTree extends directly from JComponent and represents the visual side of any valid tree structure.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import java.util.*;

public class ObjectTree extends JFrame {

	JTree tree;
	String[][] sampleData = { { "Amy" }, 
			                  { "Brandon", "Bailey" }, 
			                  { "Jodi" }, 
			                  { "Trent", "Garrett", "Paige", "Dylan" },
			                  { "Donn" }, 
			                  { "Nancy", "Donald", "Phyllis", "John", "Pat" }, 
			                  { "Ron" },
			                  { "Linda", "Mark", "Lois", "Marvin" } };

	public ObjectTree() {
		super("Hashtable Test");
		setSize(400, 300);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
	}

	public void init() {
		Hashtable h = new Hashtable();
		// Build up the hashtable using every other entry in the String[][] as a
		// key,
		// followed by a String[] "value."
		for (int i = 0; i < sampleData.length; i += 2) {
			h.put(sampleData[i][0], sampleData[i + 1]);
		}
		tree = new JTree(h);
		getContentPane().add(tree, BorderLayout.CENTER);
	}

	public static void main(String args[]) {
		ObjectTree tt = new ObjectTree();
		tt.init();
		tt.setVisible(true);
	}
}

A Simple Tree

This example works by building up a series of unconnected nodes (using the DefaultMutableTreeNode class) and then connecting them. As long as we stick to the default classes provided with the tree package, we can build a regular model out of our nodes quite quickly. In this example, we build the model based on an empty root node, and then populate the tree by attaching the other nodes to the root or to each other. You can also build the tree first, and then create the model from the root node. Both methods have the same result. With a valid tree model in place, we can make a real JTree object and display it.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;

public class TestTree extends JFrame {

  JTree tree;
  DefaultTreeModel treeModel;

  public TestTree( ) {
    super("Tree Test Example");
    setSize(400, 300);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }

  public void init( ) {
    // Build up a bunch of TreeNodes. We use DefaultMutableTreeNode because the
    // DefaultTreeModel can use it to build a complete tree.
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
    DefaultMutableTreeNode subroot = new DefaultMutableTreeNode("SubRoot");
    DefaultMutableTreeNode leaf1 = new DefaultMutableTreeNode("Leaf 1");
		DefaultMutableTreeNode leaf2 = new DefaultMutableTreeNode("Leaf 2");
    
    // Build our tree model starting at the root node, and then make a JTree out
    // of it.
    treeModel = new DefaultTreeModel(root);
    tree = new JTree(treeModel);

    // Build the tree up from the nodes we created.
    treeModel.insertNodeInto(subroot, root, 0);
    // Or, more succinctly:
    subroot.add(leaf1);
    root.add(leaf2);

    // Display it.
    getContentPane( ).add(tree, BorderLayout.CENTER);
  }

  public static void main(String args[]) {
    TestTree tt = new TestTree( );
    tt.init( );
    tt.setVisible(true);
  }
}

Editing Cells

The data in the table is entirely contrived, but look at how simple it was to set up the combo box editor. In our data model we say that one column has a particular type (a simple inner class called ColorName in our case). Then we register a new DefaultCellEditor as the default editor for that type. Here’s the complete source code:

import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ColorTable extends JFrame {
	ColorName colors[] = { new ColorName("Red"), 
			               new ColorName("Green"), 
			               new ColorName("Blue"), 
			               new ColorName("Black"),
			new ColorName("White") };

	public ColorTable() {
		super("Table With DefaultCellEditor Example");
		setSize(500, 300);
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		JTable table = new JTable(new AbstractTableModel() {
			ColorName data[] = { colors[0], 
					             colors[1], 
					             colors[2], 
					             colors[3], 
					             colors[4], 
					             colors[0], 
					             colors[1],
					             colors[2], 
					             colors[3], 
					             colors[4] };

			public int getColumnCount() {
				return 3;
			}

			public int getRowCount() {
				return 10;
			}

			public Object getValueAt(int r, int c) {
				switch (c) {
				case 0:
					return (r + 1) + ".";
				case 1:
					return "Some pithy quote #" + r;
				case 2:
					return data[r];
				}
				return "Bad Column";
			}

			public Class getColumnClass(int c) {
				if (c == 2)
					return ColorName.class;
				return String.class;
			}

			// Make Column 2 editable.
			public boolean isCellEditable(int r, int c) {
				return c == 2;
			}

			public void setValueAt(Object value, int r, int c) {
				data[r] = (ColorName) value;
			}
		});

		table.setDefaultEditor(ColorName.class, 
				new DefaultCellEditor(new JComboBox(colors)));
		table.setDefaultRenderer(ColorName.class, 
				new DefaultTableCellRenderer());
		table.setRowHeight(20);
		getContentPane().add(new JScrollPane(table));
	}

	public static void main(String args[]) {
		ColorTable ex = new ColorTable();
		ex.setVisible(true);
	}

	public class ColorName {
		String cname;

		public ColorName(String name) {
			cname = name;
		}

		public String toString() {
			return cname;
		}

	}
}

Selecting Table Entries

All this, and all we can do is render and edit data in a table. “What about selecting data?” you ask. Yes, we can do that, too. And, as you might expect, the ListSelectionModel, drives us through these selections. Unlike most of the other components, however, the two-dimensional JTable has two selection models, one for rows and one for columns.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class SelectionExample extends JFrame {

	public SelectionExample() {
		super("Selection Model Test");
		setSize(450, 350);
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		TableModel tm = new AbstractTableModel() {
			// We'll create a simple multiplication table to serve as a
			// noneditable
			// table with several rows and columns.
			public int getRowCount() {
				return 10;
			}

			public int getColumnCount() {
				return 10;
			}

			public Object getValueAt(int r, int c) {
				return "" + (r + 1) * (c + 1);
			}
		};

		final JTable jt = new JTable(tm);

		JScrollPane jsp = new JScrollPane(jt);
		getContentPane().add(jsp, BorderLayout.CENTER);

		// Now set up our selection controls.
		JPanel controlPanel, buttonPanel, columnPanel, rowPanel;

		buttonPanel = new JPanel();
		final JCheckBox cellBox, columnBox, rowBox;
		cellBox = new JCheckBox("Cells", jt.getCellSelectionEnabled());
		columnBox = new JCheckBox("Columns", jt.getColumnSelectionAllowed());
		rowBox = new JCheckBox("Rows", jt.getRowSelectionAllowed());
		cellBox.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				jt.setCellSelectionEnabled(cellBox.isSelected());
				columnBox.setSelected(jt.getColumnSelectionAllowed());
				rowBox.setSelected(jt.getRowSelectionAllowed());
			}
		});

		columnBox.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				jt.setColumnSelectionAllowed(columnBox.isSelected());
				cellBox.setSelected(jt.getCellSelectionEnabled());
			}
		});

		rowBox.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				jt.setRowSelectionAllowed(rowBox.isSelected());
				cellBox.setSelected(jt.getCellSelectionEnabled());
			}
		});

		buttonPanel.add(new JLabel("Selections allowed:"));
		buttonPanel.add(cellBox);
		buttonPanel.add(columnBox);
		buttonPanel.add(rowBox);

		columnPanel = new JPanel();
		ListSelectionModel csm = jt.getColumnModel().getSelectionModel();
		JLabel columnCounter = new JLabel("(Selected Column Indices Go Here)");
		csm.addListSelectionListener(new SelectionDebugger(columnCounter, csm));
		columnPanel.add(new JLabel("Selected columns:"));
		columnPanel.add(columnCounter);

		rowPanel = new JPanel();
		ListSelectionModel rsm = jt.getSelectionModel();
		JLabel rowCounter = new JLabel("(Selected Row Indices Go Here)");
		rsm.addListSelectionListener(new SelectionDebugger(rowCounter, rsm));
		rowPanel.add(new JLabel("Selected rows:"));
		rowPanel.add(rowCounter);

		controlPanel = new JPanel(new GridLayout(0, 1));
		controlPanel.add(buttonPanel);
		controlPanel.add(columnPanel);
		controlPanel.add(rowPanel);

		getContentPane().add(controlPanel, BorderLayout.SOUTH);
	}

	public static void main(String args[]) {
		SelectionExample se = new SelectionExample();
		se.setVisible(true);
	}

	public class SelectionDebugger implements ListSelectionListener {
		JLabel debugger;
		ListSelectionModel model;

		public SelectionDebugger(JLabel target, ListSelectionModel lsm) {
			debugger = target;
			model = lsm;
		}

		public void valueChanged(ListSelectionEvent lse) {
			if (!lse.getValueIsAdjusting()) {
				// Skip all the intermediate events.
				StringBuffer buf = new StringBuffer();
				int[] selection = getSelectedIndices(model.getMinSelectionIndex(), 
						model.getMaxSelectionIndex());
				if (selection.length == 0) {
					buf.append("none");
				} else {
					for (int i = 0; i < selection.length - 1; i++) {
						buf.append(selection[i]);
						buf.append(", ");
					}
					buf.append(selection[selection.length - 1]);
				}
				debugger.setText(buf.toString());
			}
		}

		// This method returns an array of selected indices. It's guaranteed to
		// return a non-null value.
		protected int[] getSelectedIndices(int start, int stop) {
			if ((start == -1) || (stop == -1)) {
				// No selection, so return an empty array
				return new int[0];
			}

			int guesses[] = new int[stop - start + 1];
			int index = 0;
			// Manually walk through these.
			for (int i = start; i <= stop; i++) {
				if (model.isSelectedIndex(i)) {
					guesses[index++] = i;
				}
			}

			// Pare down the guess array to the real thing.
			int realthing[] = new int[index];
			System.arraycopy(guesses, 0, realthing, 0, index);
			return realthing;
		}
	}
}

Implementing a Column Model

Here’s a custom column model that keeps all of its columns in alphabetical order as they are added:

import javax.swing.table.*;

public class SortingColumnModel extends DefaultTableColumnModel {

  public void addColumn(TableColumn tc) {
    super.addColumn(tc);
    int newIndex = sortedIndexOf(tc);
    if (newIndex != tc.getModelIndex( )) {
      moveColumn(tc.getModelIndex( ), newIndex);
    }
  }

  protected int sortedIndexOf(TableColumn tc) {
    // Just do a linear search for now.
    int stop = getColumnCount( );
    String name = tc.getHeaderValue( ).toString( );

    for (int i = 0; i < stop; i++) {
      if (name.compareTo(getColumn(i).getHeaderValue( ).toString( )) <= 0) {
        return i;
      }
    }
    return stop;
  }
}

Implementing the model is simple. We override addColumn( ) to add the column to the superclass and then move it into the appropriate position. You can use this column model with any data model. The next section goes into much more detail on the table model used to store the real data, so for this simple example, we’ll use the DefaultTableModel class to hold the data. Once we have our table model and our column model, we can build a JTable with them. Then, any columns we add are listed in alphabetical order (by the header), regardless of the order in which they were added.

import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

class SortingColumnModel extends DefaultTableColumnModel {

	public void addColumn(TableColumn tc) {
		super.addColumn(tc);
		int newIndex = sortedIndexOf(tc);
		if (newIndex != tc.getModelIndex()) {
			moveColumn(tc.getModelIndex(), newIndex);
		}
	}

	protected int sortedIndexOf(TableColumn tc) {
		// Just do a linear search for now.
		int stop = getColumnCount();
		String name = tc.getHeaderValue().toString();

		for (int i = 0; i < stop; i++) {
			if (name.compareTo(getColumn(i).getHeaderValue().toString()) <= 0) {
				return i;
			}
		}
		return stop;
	}
}

public class ColumnExample extends JFrame {

	public ColumnExample() {
		super("Abstract Model JTable Test");
		setSize(300, 200);
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		DefaultTableModel dtm = new DefaultTableModel(new String[][] { 
				{ "1", "2", "3" }, { "4", "5", "6" } },
				new String[] { "Names", "In", "Order" });
		SortingColumnModel scm = new SortingColumnModel();
		JTable jt = new JTable(dtm, scm);
		jt.createDefaultColumnsFromModel();

		JScrollPane jsp = new JScrollPane(jt);
		getContentPane().add(jsp, BorderLayout.CENTER);
	}

	public static void main(String args[]) {
		ColumnExample ce = new ColumnExample();
		ce.setVisible(true);
	}
}