April 5, 1999
Swingin’ Java: Text components
by Dan Simon
So far in this series we’ve discussed how to trap events in Swing and how to safely change the GUI components in a multithreaded environment. In this article, we’ll begin to get into Swing’s feature-rich text components.
In the old days of the AWT, text components were more or less limited to the basic text field and text area components. With the advent of Swing, however, the playing field got a lot bigger. The Swing library provides a comprehensive series of components that allow the developer to deal with practically any text presentation issue in GUI development. There are components to handle single-line text, multi-line text, and multi-line styled text. Swings text components range from the simple JTextField to the wonderfully full-featured JTextPane. To begin our discussion of text handling in Swing, we’ll start with the mother of them all: JTextComponent.
JTextComponent is the parent for all text components in Swing. It contains many of the basic methods shared by all five of the concrete text components, such as assigning and getting content from the component, assigning/altering the document, changing the caret, keymaps and bindings, and much more. If you are going to work with Swing text components, you are going to need to be familiar with the methods defined in this parent class. Of particular note, methods such as
setText(String text) |
getText() |
The Document interface is the content model for Swing text components. Every Swing text component has an instance of Document associated with it. The Document contains both the content for its component as well as any style information for the rendering of the content. All content is stored in the Document’s Element array. Style information is stored in each Element as an AttributeSet. We’ll get into AttributeSets in the next article, when we discuss styled text components.
For now, the main point to understand about the Document interface is that it controls all content for a text component. Whether text is being inserted through keyboard input, cutting and pasting or programmatically, the Document receives all updates through its
insertString(int offset, String str, AttributeSet attr) |
The PlainDocument class is an implementation of the Document interface that is used by text components that do not need to manage complex formatting styles. JTextField, JTextArea, and JPasswordField all use PlainDocument as their model. Since PlainDocument is, in essence, a “minimal” implementation of the Document interface, it is an ideal class to extend to provide custom Documents for your text components. Example 1 shows how you would extend PlainDocument to provide a Document that will accept only numeric characters and will limit its length to the number of characters specified in its constructor (a fixed-length numeric text field).
Example 1
import javax.swing.*; import javax.swing.text.*; import java.awt.*; public class FixedNumericDocument extends PlainDocument { private int maxLength = 9999; private boolean numericOnly public FixedNumericDocument(int maxLength, boolean numericOnly) { super(); this.maxLength = maxLength; this.numericOnly = numericOnly; } //this is where we'll control all input to our document. //If the text that is being entered passes our criteria, then we'll just call //super.insertString(...) public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException { if (getLength() + str.length() > maxLength) { Toolkit.getDefaultToolkit().beep(); return; } else { try { if (numericOnly) { //check if str is numeric only Integer.parseInt(str); //if we get here then str contains only numbers //so it's ok to insert } super.insertString(offset, str, attr); } catch(NumberFormatException exp) { Toolkit.getDefaultToolkit().beep(); return; } } return; } } |
The rendering of a JTextField on screen, specifically determining its preferred width, can be a bit tricky. The first thing that JTextField will check in determining its preferred width is whether the number of columns has been set. You can set the number of columns for JTextField in its constructor or explicitly through
setColumns(int columns) |
If the number of columns has not been set, the JTextField next checks to see if the preferred size has been set. If so, it uses this value.
If neither the number of columns nor the preferred size have been set, the JTextField will allow the TextUI to determine its preferred size. The default behavior of the TextUI is to return a preferred size that will allow all of the text in the JTextField to be displayed.
Like JTextField, JPasswordField is another of the non-styled text components defined in Swing. JPasswordField is almost identical in behavior to JTextField with only a couple of exceptions. For one, JPasswordField displays only its echo character (“*” by default) rather than the actual content, allowing passwords to be kept private. The echo character can be easily set on JPasswordField through the
setEchoChar(char echoChar) |
The other difference between JPasswordField and JTextField is that the
getText() |
getPassword() |
The final non-styled text component defined by Swing is the ever-useful JTextArea. Unlike JTextField, JTextArea allows you to display multiple lines of text. As such, it contains settings for turning auto line wrapping on and off —
setLineWrap(boolean wrap) |
setWrapStyleWord(boolean wrapWords) |
Unlike its cousin java.awt.TextArea, JTextArea does not contain direct support for scrolling. Instead, it is expected that you will add it into a JScrollPane if you desire scrolling support.
Example 2 shows how to use all three of these components with our custom document from Example 1.
Example 2
import javax.swing.*; import javax.swing.text.*; import java.awt.*; import java.awt.event.*; public class Example2 extends JFrame { public Example2() { super("Swingin' Java -- Example 2"); } public static void main(String[] args) { Example2 frame = new Example2(); //create a JTextField that is six characters long JTextField textField = new JTextField(new FixedNumericDocument(6), "", 6); //create a JPasswordField that is 8 characters long (still numeric only) JPasswordField passwordField = new JPasswordField(new FixedNumericDocument(8), "", 8); //set the echo character to be "#" passwordField.setEchoChar('#'); //finally, we'll create a JTextArea that will contain up to 256 numeric characters JTextArea textArea = new JTextArea(new FixedNumericDocument(256)); //add the text area into a scroll pane JScrollPane scroll = new JScrollPane(textArea); //give the scroll pane a reasonable size scroll.setPreferredSize(new Dimension(150,100)); //just to demonstrate, we'll turn on line wrapping textArea.setLineWrap(true); //now we'll tell it to wrap whole words only (don't break on characters) textArea.setWrapStyleWord(true); //create a panel to contain everything JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); //initialize the constraints parameters gbc.gridx=0; gbc.gridy=0; gbc.anchor=gbc.EAST; gbc.fill=gbc.NONE; gbc.insets = new Insets(5,5,5,5); panel.add(new JLabel("JTextField:"), gbc); gbc.gridx=1; gbc.anchor=gbc.WEST; panel.add(textField, gbc); gbc.gridy=1; panel.add(passwordField, gbc); gbc.gridx=0; gbc.anchor=gbc.EAST; panel.add(new JLabel("JPasswordField:"), gbc); gbc.gridy=2; gbc.anchor=gbc.NORTHEAST; panel.add(new JLabel("JTextArea:"), gbc); gbc.gridx=1; gbc.anchor=gbc.WEST; //add the scroll pane, which already contains the text area panel.add(scroll, gbc); //set the panel as our content pane frame.setContentPane(panel); //just to be nice, we'll shut down properly frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.pack(); frame.setVisible(true); } } |
About the author
Dan Simon is a Sun Certified Java Developer with Osage Systems Group Inc., in Wood Dale, Ill.