Chapter 15. More Swing Components

In the previous chapter, we described most of the components that Swing offers for building user interfaces. In this chapter, you’ll find out about the rest. These include Swing’s text components, trees, and tables. These types of components have considerable depth, but are quite easy to use if you accept their default options. We’ll show you the easy way to use these components, and start to describe the more advanced features of each. The chapter ends with a brief description of how to implement your own components in Swing.

Text Components

Swing gives us sophisticated text components, from plain text entry boxes to HTML interpreters. For full coverage of Swing’s text capabilities, see Java Swing, by Robert Eckstein, Marc Loy, and Dave Wood (O’Reilly & Associates). In that encyclopedic book, six meaty chapters are devoted to text. It’s a huge subject; we’ll just scratch the surface here.

Let’s begin by examining the simpler text components: JTextArea is a multiline text editor; JTextField is a simple, single-line text editor. Both JTextField and JTextArea derive from the JTextComponent class, which provides the functionality they have in common. This includes methods for setting and retrieving the displayed text, specifying whether the text is “editable” or read-only, manipulating the cursor position within the text, and manipulating text selections.

Observing changes in text components requires an understanding of how the components implement the Model-View-Controller (MVC) architecture. You may recall from the last chapter that Swing components implement a true MVC architecture. It’s in the text components that you first get an inkling of a clear separation between the M and VC parts of the MVC architecture. The model for text components is an object called a Document . When you add or remove text from a JTextField or a JTextArea, the corresponding Document is changed. It’s the document itself, not the visual components, that generates text events when something changes. To receive notification of JTextArea changes, therefore, you register with the underlying Document, not with the JTextArea component itself:

JTextArea textArea = new JTextArea( );
Document d = textArea.getDocument( );
d.addDocumentListener(someListener);

As you’ll see in an upcoming example, you can easily have more than one visual text component use the same underlying data model, or Document.

In addition, JTextField components generate an ActionEvent whenever the user presses the Return key within the field. To get these events, implement the ActionListener interface, and call addActionListener( ) to register.

The next sections contain a couple of simple applications that show you how to work with text areas and fields.

The TextEntryBox Application

Our first example, TextEntryBox , creates a JTextArea and ties it to a JTextField, as you can see in Figure 15.1. When the user hits Return in the JTextField, we receive an ActionEvent and add the line to the JTextArea’s display. Try it out. You may have to click your mouse in the JTextField to give it focus before typing in it. If you fill up the display with lines, you can test drive the scrollbar:

//file: TextEntryBox.java
import java.awt.*; 
import java.awt.event.*;
import javax.swing.*;
 
public class TextEntryBox extends JFrame {
  
  public TextEntryBox( ) {
    super("TextEntryBox v1.0");
    setSize(200, 300);
    setLocation(200, 200);
    
    final JTextArea area = new JTextArea( );
    area.setFont(new Font("Serif", Font.BOLD, 18));
    area.setText("Howdy!\n");
    final JTextField field = new JTextField( );
    
    Container content = getContentPane( );
    content.add(new JScrollPane(area), BorderLayout.CENTER);
    content.add(field, BorderLayout.SOUTH);
    setVisible(true);
    field.requestFocus( );
    
    field.addActionListener(new ActionListener( ) {
      public void actionPerformed(ActionEvent ae) {
        area.append(field.getText( ) + '\n');
        field.setText("");
      }
    });
  }

  public static void main(String[] args) {
    JFrame f = new TextEntryBox( );
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent we) { System.exit(0); }
    });
    f.setVisible(true);
  }
}
The TextEntryBox application

Figure 15-1. The TextEntryBox application

TextEntryBox is exceedingly simple; we’ve done a few things to make it more interesting. We give the text area a bigger font using Component ’s setFont( ) method; fonts are discussed in Chapter 17. Finally, we want to be notified whenever the user presses Return in the text field, so we register an anonymous inner class as a listener for action events.

Pressing Return in the JTextField generates an action event, and that’s where the fun begins. We handle the event in the actionPerformed( ) method of our inner ActionListener implementation. Then we use the getText( ) and setText( ) methods to manipulate the text the user has typed. These methods can be used for both JTextField and JTextArea, because these components are derived from the JTextComponent class, and therefore have some common functionality.

The event handler, actionPerformed( ), calls field.getText( ) to read the text that the user typed into our JTextField. It then adds this text to the JTextArea by calling area.append( ). Finally, we clear the text field by calling the method field.setText(""), preparing it for more input.

Remember, the text components really are distinct from the text data model, the Document. When you call setText( ), getText( ), or append( ), these methods are shorthand for operations on an underlying Document.

By default, JTextField and JTextArea are editable; you can type and edit in both text components. They can be changed to output-only areas by calling setEditable(false). Both text components also support selections. A selection is a range of text that is highlighted for copying and pasting in your windowing system. You select text by dragging the mouse over it; you can then copy and paste it into other text windows. The current text selection is returned by getSelected-Text( ) .

Notice how JTextArea fits neatly inside a JScrollPane . The scroll pane gives us the expected scrollbars and scrolling behavior if the text in the JTextArea becomes too large for the available space.

Say the Magic Word

Swing includes a class just for typing passwords, called JPasswordField. A JPasswordField behaves just like a JTextField (it’s a subclass), except every character that’s typed is echoed as a single character, typically an asterisk. Figure 15.2 shows the option dialog example that was presented in Chapter 14. The example includes a JTextField and a JPasswordField.

Using a JPasswordField in a dialog

Figure 15-2. Using a JPasswordField in a dialog

The creation and use of JPasswordField is basically the same as for JTextField. If you find asterisks distasteful, you can tell the JPasswordField to use a different character using the setEchoChar( ) method.

Normally, you would use getText( ) to retrieve the text typed into the JPasswordField. This method, however, is deprecated; you should use getPassword( ) instead. The getPassword( ) method returns a character array rather than a String object. This is done because character arrays are less vulnerable than Strings to discovery by memory-snooping password sniffer programs. If you’re not that concerned, you can simply create a new String from the character array. Note that methods in the Java cryptographic classes accept passwords as character arrays, not strings, so it makes a lot of sense to pass the results of a getPassword( ) call directly to methods in the cryptographic classes, without ever creating a String.

Sharing a Data Model

Our next example shows how easy it is to make two or more text components share the same Document; Figure 15.3 shows what the application looks like. Anything the user types into any text area is reflected in all of them. All we had to do is make all the text areas use the same data model, like this:

JTextArea areaFiftyOne = new JTextArea( );
JTextArea areaFiftyTwo = new JTextArea( );
areaFiftyTwo.setDocument(areaFiftyOne.getDocument( ));
JTextArea areaFiftyThree = new JTextArea( );
areaFiftyThree.setDocument(areaFiftyOne.getDocument( ));
Three views of the same data model

Figure 15-3. Three views of the same data model

We could just as easily make seven text areas sharing the same document, or seventy. While this example may not look very useful, keep in mind that you can scroll different text areas to different places in the same document. That’s one of the beauties of putting multiple views on the same data—you get to examine different parts of it. Another useful technique is viewing the same data in different ways. You could, for example, view some tabular numerical data as both a spreadsheet and a pie chart. The MVC architecture that Swing uses means that it’s possible to do this in an intelligent way, so that if numbers in a spreadsheet are updated, a pie chart that uses the same data will automatically be updated also.

This example works because behind the scenes, there are a lot of events flying around. When you type in one of the text areas, the text area receives the keyboard events. It calls methods in the document to update its data. In turn, the document sends events to the other text areas telling them about the updates, so they can correctly display the document’s new data. But you don’t have to worry about any of this—you just tell the text areas to use the same data and Swing takes care of the rest:

//file: SharedModel.java
import java.awt.*; 
import java.awt.event.*;
import javax.swing.*;
 
public class SharedModel extends JFrame {
  
  public SharedModel( ) {
    super("SharedModel v1.0");
    setSize(300, 300);
    setLocation(200, 200);
    
    JTextArea areaFiftyOne = new JTextArea( );
    JTextArea areaFiftyTwo = new JTextArea( );
    areaFiftyTwo.setDocument(areaFiftyOne.getDocument( ));
    JTextArea areaFiftyThree = new JTextArea( );
    areaFiftyThree.setDocument(areaFiftyOne.getDocument( ));
    
    Container content = getContentPane( );
    content.setLayout(new GridLayout(3, 1));
    content.add(new JScrollPane(areaFiftyOne));
    content.add(new JScrollPane(areaFiftyTwo));
    content.add(new JScrollPane(areaFiftyThree));
    setVisible(true);
  }

  public static void main(String[] args) {
    JFrame f = new SharedModel( );
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent we) { System.exit(0); }
    });
    f.setVisible(true);
  }
}

Setting up the display is simple. We use a GridLayout (discussed in the next chapter) and add three text areas to the layout. Then all we have to do is tell the text areas to use the same Document.

HTML and RTF for Free

Most user interfaces will use only two subclasses of JTextComponent. These are the simple JTextField and JTextArea classes that we just covered. That’s just the tip of the iceberg, however. Swing offers sophisticated text capabilities through two other subclasses of JTextComponent: JEditorPane and JTextPane.

The first of these, JEditorPane, can display HTML and RTF documents. It also fires one more type of event, a HyperlinkEvent . Subtypes of this event are fired off when the mouse enters, exits, or clicks on a hyperlink. Combined with JEditorPane’s HTML display capabilities, it’s very easy to build a simple browser. Here’s one in fewer than 100 lines:

//file: CanisMinor.java
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import javax.swing.*;
import javax.swing.event.*;
 
public class CanisMinor extends JFrame {
  protected JEditorPane mEditorPane;
  protected JTextField mURLField;
  
  public CanisMinor(String urlString) {
    super("CanisMinor v1.0");
    createUI(urlString);
    setVisible(true);
  }
 
  protected void createUI(String urlString) {
    setSize(500, 600);
    center( );
    Container content = getContentPane( );
    content.setLayout(new BorderLayout( ));
 
    // add the URL control
    JToolBar urlToolBar = new JToolBar( );
    mURLField = new JTextField(urlString, 40);
    urlToolBar.add(new JLabel("Location:"));
    urlToolBar.add(mURLField);
    content.add(urlToolBar, BorderLayout.NORTH);
    
    // add the editor pane
    mEditorPane = new JEditorPane( );
    mEditorPane.setEditable(false);
    content.add(new JScrollPane(mEditorPane), BorderLayout.CENTER);
    
    // open the initial URL
    openURL(urlString);
    
    // go to a new location when enter is pressed in the URL field
    mURLField.addActionListener(new ActionListener( ) {
      public void actionPerformed(ActionEvent ae) {
        openURL(ae.getActionCommand( ));
      }
    });
    
    // add the plumbing to make links work
    mEditorPane.addHyperlinkListener(new LinkActivator( ));
    
    // exit the application when the window is closed
    addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent e) { System.exit(0); }
    });
  }
 
  protected void center( ) {
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize( );
    Dimension us = getSize( );
    int x = (screen.width - us.width) / 2;
    int y = (screen.height - us.height) / 2;
    setLocation(x, y);
  }
  
  protected void openURL(String urlString) {
    try {
      URL url = new URL(urlString);
      mEditorPane.setPage(url);
      mURLField.setText(url.toExternalForm( ));
    }
    catch (Exception e) {
      System.out.println("Couldn't open " + urlString + ":" + e);
    }
  }

  class LinkActivator implements HyperlinkListener {
    public void hyperlinkUpdate(HyperlinkEvent he) {
      HyperlinkEvent.EventType type = he.getEventType( );
      if (type == HyperlinkEvent.EventType.ENTERED)
        mEditorPane.setCursor(
            Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      else if (type == HyperlinkEvent.EventType.EXITED)
        mEditorPane.setCursor(Cursor.getDefaultCursor( ));
      else if (type == HyperlinkEvent.EventType.ACTIVATED)
        openURL(he.getURL().toExternalForm( ));
    }
  }

  public static void main(String[] args) {
    String urlString = "http://www.oreilly.com/catalog/java2d/";
    if (args.length > 0)
       urlString = args[0];
    new CanisMinor(urlString);
  }
}

This browser is shown in Figure 15.4.

The CanisMinor application, a simple web browser

Figure 15-4. The CanisMinor application, a simple web browser

JEditorPane is the center of this little application. Passing a URL to setPage( ) causes the JEditorPane to load a new page, either from a local file or from somewhere across the Internet. To go to a new page, enter it in the text field at the top of the window and press Return. This fires an ActionEvent which sets the new page location of the JEditorPane. It can display RTF files, too. (RTF is the text or non-binary storage format for Microsoft Word documents.)

Responding to hyperlinks correctly is simply a matter of responding to the HyperlinkEvents thrown by the JEditorPane. This behavior is encapsulated in the LinkActivator inner class. If the mouse enters a hyperlink area, the cursor is changed to a hand. It’s changed back when the mouse exits a hyperlink. If the user “activates” the hyperlink by clicking on it, we set the location of the JEditorPane to the location given under the hyperlink. Surf away!

Behind the scenes, something called an EditorKit handles displaying documents for the JEditorPane. Different kinds of EditorKits are used to display different kinds of documents. For HTML, the HTMLEditorKit class (in the javax.swing.text.html package) handles the display. Currently, this class supports HTML 3.2. Subsequent releases of the SDK will contain enhancements to the capabilities of HTMLEditorKit; eventually, it will support HTML 4.0.

There’s another component here that we haven’t covered before—the JToolBar . This nifty container houses our URL text field. Initially, the JToolBar starts out at the top of the window. But you can pick it up by clicking on the little dotted box near its left edge, then drag it around to different parts of the window. You can place this toolbar at the top, left, right, or bottom of the window, or you can drag it outside the window entirely. It will then inhabit a window of its own. All this behavior comes for free from the JToolBar class. All we had to do was create a JToolBar and add some components to it. The JToolBar is just a container, so we add it to the content pane of our window to give it an initial location.

Managing Text Yourself

Swing offers one last subclass of JTextComponent that can do just about anything you want: JTextPane. The basic text components, JTextField and JTextArea, are limited to a single font in a single style. But JTextPane, a subclass of JEditorPane, can display multiple fonts and multiple styles in the same component. It also includes support for a cursor (caret), highlighting, image embedding, and other advanced features.

We’ll just take a peek at JTextPane here by creating a text pane with some styled text. Remember, the text itself is stored in an underlying data model, the Document . To create styled text, we simply associate a set of text attributes with different parts of the document’s text. Swing includes classes and methods for manipulating sets of attributes, like specifying a bold font or a different color for the text. Attributes themselves are contained in a class called SimpleAttributeSet ; these attribute sets are manipulated with static methods in the StyleConstants class. For example, to create a set of attributes that specifies the color red, you could do this:

SimpleAttributeSet redstyle = new SimpleAttributeSet( );
StyleConstants.setForeground(redstyle, Color.red);

To add some red text to a document, you would just pass the text and the attributes to the document’s insertString( ) method, like this:

document.insertString(6, "Some red text", redstyle);

The first argument to insertString( ) is an offset into the text. An exception is thrown if you pass in an offset that’s greater than the current length of the document. If you pass null for the attribute set, the text is added in the JTextPane’s default font and style.

Our simple example creates several attribute sets and uses them to add plain and styled text to a JTextPane , as shown in Figure 15.5.

//file: Styling.java
import java.awt.*; 
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
 
public class Styling extends JFrame {
  private JTextPane textPane;
  
  public Styling( ) {
    super("Styling v1.0");
    setSize(300, 200);
    setLocation(200, 200);
    
    textPane = new JTextPane( );
    textPane.setFont(new Font("Serif", Font.PLAIN, 24));
    
    // create some handy attribute sets
    SimpleAttributeSet red = new SimpleAttributeSet( );
    StyleConstants.setForeground(red, Color.red);
    StyleConstants.setBold(red, true);
    SimpleAttributeSet blue = new SimpleAttributeSet( );
    StyleConstants.setForeground(blue, Color.blue);
    SimpleAttributeSet italic = new SimpleAttributeSet( );
    StyleConstants.setItalic(italic, true);
    StyleConstants.setForeground(italic, Color.orange);

    // add the text
    append("In a ", null);
    append("sky", blue);
    append(" full of people\nOnly some want to ", null);
    append("fly", italic);
    append("\nIsn't that ", null);
    append("crazy", red);
    append("?", null);
    
    Container content = getContentPane( );
    content.add(new JScrollPane(textPane), BorderLayout.CENTER);
    setVisible(true);
  }
  
  protected void append(String s, AttributeSet attributes) {
    Document d = textPane.getDocument( );
    try { d.insertString(d.getLength( ), s, attributes); }
    catch (BadLocationException ble) {}
  }

  public static void main(String[] args) {
    JFrame f = new Styling( );
    f.addWindowListener(new WindowAdapter( ) {
      public void windowClosing(WindowEvent we) { System.exit(0); }
    });
    f.setVisible(true);
  }
}
Using styled text in a JTextPane

Figure 15-5. Using styled text in a JTextPane

This example creates a JTextPane, which is saved away in a member variable. Three different attribute sets are created, using combinations of text styles and foreground colors. Then, using a helper method called append( ) , text is added to the JTextPane.

The append( ) method tacks a text String on the end of the JTextPane’s document, using the supplied attributes. Remember that if the attributes are null, the text is displayed with the JTextPane’s default font and style.

You can go ahead and add your own text, if you wish. If you place the caret inside one of the differently styled words and type, the new text comes out in the appropriate style. Pretty cool, eh? You’ll also notice that JTextPane gives us word-wrapping behavior for free. And since we’ve wrapped the JTextPane in a JScrollPane, we get scrolling for free, too. Swing allows you to do some really cool stuff without breaking a sweat. Just wait—there’s plenty more to come.

This simple example should give you some idea of what JTextPane can do. It’s reasonably easy to build a simple word processor with JTextPane, and complex commercial-grade word processors are definitely possible.

If JTextPane still isn’t good enough for you, or you need some finer control over character, word, and paragraph layout, you can actually draw text, carets, and highlight shapes yourself. A class in the 2D API called TextLayout simplifies much of this work, but it’s outside the scope of this book. For coverage of TextLayout and other advanced text drawing topics, see Java 2D Graphics by Jonathan Knudsen (O’Reilly & Associates).

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.