We are finally ready to start introducing the Swing user interface components. We start with components that let a user input and edit text. You can use the JTextField and JTextArea components for gathering text input. A text field can accept only one line of text; a text area can accept multiple lines of text. Both of these classes inherit from a class called JTextComponent. You will not be able to construct a JTextComponent yourself because it is an abstract class. On the other hand, as is so often the case in Java, when you go searching through the API documentation, you may find that the methods you are looking for are actually in the parent class JTextComponent rather than in the derived class. For example, the methods that get or set the text in a text field or text area are actually methods in JTextComponent. javax.swing.text.JTextComponent 1.2
Text FieldsThe usual way to add a text field to a window is to add it to a panel or other container just as you would a button: JPanel panel = new JPanel(); JTextField textField = new JTextField("Default input", 20); panel.add(textField); This code adds a text field and initializes the text field by placing the string "Default input" inside it. The second parameter of this constructor sets the width. In this case, the width is 20 "columns." Unfortunately, a column is a rather imprecise measurement. One column is the expected width of one character in the font you are using for the text. The idea is that if you expect the inputs to be n characters or less, you are supposed to specify n as the column width. In practice, this measurement doesn't work out too well, and you should add 1 or 2 to the maximum input length to be on the safe side. Also, keep in mind that the number of columns is only a hint to the AWT that gives the preferred size. If the layout manager needs to grow or shrink the text field, it can adjust its size. The column width that you set in the JTextField constructor is not an upper limit on the number of characters the user can enter. The user can still type in longer strings, but the input scrolls when the text exceeds the length of the field. Users tend to find scrolling text fields irritating, so you should size the fields generously. If you need to reset the number of columns at run time, you can do that with the setColumns method. TIP
In general, you want to let the user add text (or edit the existing text) in a text field. Quite often these text fields start out blank. To make a blank text field, just leave out the string as a parameter for the JTextField constructor: JTextField textField = new JTextField(20); You can change the content of the text field at any time by using the setText method from the JTextComponent parent class mentioned in the previous section. For example: textField.setText("Hello!"); And, as was also mentioned in the previous section, you can find out what the user typed by calling the getText method. This method returns the exact text that the user typed. To trim any extraneous leading and trailing spaces from the data in a text field, apply the trim method to the return value of getText: String text = textField.getText().trim(); To change the font in which the user text appears, use the setFont method. javax.swing.JTextField 1.2
javax.swing.JComponent 1.2
java.awt.Component 1.0
Labels and Labeling ComponentsLabels are components that hold text. They have no decorations (for example, no boundaries). They also do not react to user input. You can use a label to identify components. For example, unlike buttons, text fields have no label to identify them. To label a component that does not itself come with an identifier:
The constructor for a JLabel lets you specify the initial text or icon, and optionally, the alignment of the content. You use constants from the SwingConstants interface to specify alignment. That interface defines a number of useful constants such as LEFT, RIGHT, CENTER, NORTH, EAST, and so on. The JLabel class is one of several Swing classes that implement this interface. Therefore, you can specify a right-aligned label either as JLabel label = new JLabel("Minutes", SwingConstants.RIGHT); or JLabel label = new JLabel("Minutes", JLabel.RIGHT); The setText and setIcon methods let you set the text and icon of the label at run time. TIP
Labels can be positioned inside a container like any other component. This means you can use the techniques you have seen before to place labels where you need them. javax.swing.JLabel 1.2
Change Tracking in Text FieldsLet us put a few text fields to work. Figure 9-12 shows the running application listed in Example 9-2. The program shows a clock and two text fields that enter the hours and minutes. Whenever the content of the text fields changes, the clock is updated. Figure 9-12. Text field exampleTo track every change in the text field requires a bit of an effort. First of all, note that it is not a good idea to monitor keystrokes. Some keystrokes (such as the arrow keys) don't change the text. And, depending on the look and feel, there may be mouse actions that result in text changes. As you saw in the beginning of this chapter, the Swing text field is implemented in a rather general way: the string that you see in the text field is just a visible manifestation (the view) of an underlying data structure (the model). Of course, for a humble text field, there is no great difference between the two. The view is a displayed string, and the model is a string object. But the same architecture is used in more advanced editing components to present formatted text, with fonts, paragraphs, and other attributes that are internally represented by a more complex data structure. The model for all text components is described by the Document interface, which covers both plain text and formatted text (such as HTML). The point is that you can ask the document (and not the text component) to notify you whenever the data has changed, by installing a document listener: textField.getDocument().addDocumentListener(listener); When the text has changed, one of the following DocumentListener methods is called: void insertUpdate(DocumentEvent event) void removeUpdate(DocumentEvent event) void changedUpdate(DocumentEvent event) The first two methods are called when characters have been inserted or removed. The third method is not called at all for text fields. For more complex document types, it would be called when some other change, such as a change in formatting, has occurred. Unfortunately, there is no single callback to tell you that the text has changed usually you don't much care how it has changed. And there is no adapter class either. Thus, your document listener must implement all three methods. Here is what we do in our sample program: private class ClockFieldListener implements DocumentListener { public void insertUpdate(DocumentEvent event) { setClock(); } public void removeUpdate(DocumentEvent event) { setClock(); } public void changedUpdate(DocumentEvent event) {} } The setClock method uses the getText method to obtain the current user-input strings from the text fields. Unfortunately, that is what we get: strings. We need to convert the strings to integers by using the familiar, if cumbersome, incantation: int hours = Integer.parseInt(hourField.getText().trim()); int minutes = Integer.parseInt(minuteField.getText().trim()); But this code won't work right when the user types a noninteger string, such as "two", into the text field or even leaves the field blank. For now, we catch the NumberFormatException that the parseInt method throws, and we simply don't update the clock when the text field entry is not a number. In the next section, you see how you can prevent the user from entering invalid input in the first place. NOTE
Finally, note how the ClockPanel constructor sets the preferred size: public ClockPanel() { setPreferredSize(new Dimension(2 * RADIUS + 1, 2 * RADIUS + 1)); } When the frame's pack method computes the frame size, it uses the panel's preferred size. Example 9-2. TextTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.geom.*; 4. import javax.swing.*; 5. import javax.swing.event.*; 6. 7. public class TextTest 8. { 9. public static void main(String[] args) 10. { 11. TextTestFrame frame = new TextTestFrame(); 12. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13. frame.setVisible(true); 14. } 15. } 16. 17. /** 18. A frame with two text fields to set a clock. 19. */ 20. class TextTestFrame extends JFrame 21. { 22. public TextTestFrame() 23. { 24. setTitle("TextTest"); 25. 26. DocumentListener listener = new ClockFieldListener(); 27. 28. // add a panel with text fields 29. 30. JPanel panel = new JPanel(); 31. 32. panel.add(new JLabel("Hours:")); 33. hourField = new JTextField("12", 3); 34. panel.add(hourField); 35. hourField.getDocument().addDocumentListener(listener); 36. 37. panel.add(new JLabel("Minutes:")); 38. minuteField = new JTextField("00", 3); 39. panel.add(minuteField); 40. minuteField.getDocument().addDocumentListener(listener); 41. 42. add(panel, BorderLayout.SOUTH); 43. 44. // add the clock 45. 46. clock = new ClockPanel(); 47. add(clock, BorderLayout.CENTER); 48. pack(); 49. } 50. 51. /** 52. Set the clock to the values stored in the text fields. 53. */ 54. public void setClock() 55. { 56. try 57. { 58. int hours = Integer.parseInt(hourField.getText().trim()); 59. int minutes = Integer.parseInt(minuteField.getText().trim()); 60. clock.setTime(hours, minutes); 61. } 62. catch (NumberFormatException e) {} 63. // don't set the clock if the input can't be parsed 64. } 65. 66. public static final int DEFAULT_WIDTH = 300; 67. public static final int DEFAULT_HEIGHT = 300; 68. 69. private JTextField hourField; 70. private JTextField minuteField; 71. private ClockPanel clock; 72. 73. private class ClockFieldListener implements DocumentListener 74. { 75. public void insertUpdate(DocumentEvent event) { setClock(); } 76. public void removeUpdate(DocumentEvent event) { setClock(); } 77. public void changedUpdate(DocumentEvent event) {} 78. } 79. } 80. 81. /** 82. A panel that draws a clock. 83. */ 84. class ClockPanel extends JPanel 85. { 86. public ClockPanel() 87. { 88. setPreferredSize(new Dimension(2 * RADIUS + 1, 2 * RADIUS + 1)); 89. } 90. 91. public void paintComponent(Graphics g) 92. { 93. // draw the circular boundary 94. 95. super.paintComponent(g); 96. Graphics2D g2 = (Graphics2D) g; 97. Ellipse2D circle = new Ellipse2D.Double(0, 0, 2 * RADIUS, 2 * RADIUS); 98. g2.draw(circle); 99. 100. // draw the hour hand 101. 102. double hourAngle = Math.toRadians(90 - 360 * minutes / (12 * 60)); 103. drawHand(g2, hourAngle, HOUR_HAND_LENGTH); 104. 105. // draw the minute hand 106. 107. double minuteAngle = Math.toRadians(90 - 360 * minutes / 60); 108. drawHand(g2, minuteAngle, MINUTE_HAND_LENGTH); 109. } 110. 111. public void drawHand(Graphics2D g2, double angle, double handLength) 112. { 113. Point2D end = new Point2D.Double( 114. RADIUS + handLength * Math.cos(angle), 115. RADIUS - handLength * Math.sin(angle)); 116. Point2D center = new Point2D.Double(RADIUS, RADIUS); 117. g2.draw(new Line2D.Double(center, end)); 118. } 119. 120. /** 121. Set the time to be displayed on the clock 122. @param h hours 123. @param m minutes 124. */ 125. public void setTime(int h, int m) 126. { 127. minutes = h * 60 + m; 128. repaint(); 129. } 130. 131. private double minutes = 0; 132. private int RADIUS = 100; 133. private double MINUTE_HAND_LENGTH = 0.8 * RADIUS; 134. private double HOUR_HAND_LENGTH = 0.6 * RADIUS; 135. } javax.swing.JComponent 1.2
javax.swing.text.Document 1.2
javax.swing.event.DocumentEvent 1.2
javax.swing.event.DocumentListener 1.2
Password FieldsPassword fields are a special kind of text field. To avoid nosy bystanders being able to glance at a password, the characters that the user entered are not actually displayed. Instead, each typed character is represented by an echo character, typically an asterisk (*). Swing supplies a JPasswordField class that implements such a text field. The password field is another example of the power of the model-view-controller architecture pattern. The password field uses the same model to store the data as a regular text field, but its view has been changed to display all characters as echo characters. javax.swing.JPasswordField 1.2
Formatted Input FieldsIn the last example program, we wanted the program user to type numbers, not arbitrary strings. That is, the user is allowed to enter only digits 0 through 9 and a hyphen ( ). The hyphen, if present at all, must be the first symbol of the input string. On the surface, this input validation task sounds simple. We can install a key listener to the text field and then consume all key events that aren't digits or a hyphen. Unfortunately, this simple approach, although commonly recommended as a method for input validation, does not work well in practice. First, not every combination of the valid input characters is a valid number. For example, --3 and 3-3 aren't valid, even though they are made up from valid input characters. But, more important, there are other ways of changing the text that don't involve typing character keys. Depending on the look and feel, certain key combinations can be used to cut, copy, and paste text. For example, in the Metal look and feel, the CTRL+V key combination pastes the content of the paste buffer into the text field. That is, we also need to monitor that the user doesn't paste in an invalid character. Clearly, trying to filter keystrokes to ensure that the content of the text field is always valid begins to look like a real chore. This is certainly not something that an application programmer should have to worry about. Perhaps surprisingly, before JDK 1.4, there were no components for entering numeric values. Starting with the first edition of Core Java, we supplied an implementation for an IntTextField, a text field for entering a properly formatted integer. In every new edition, we changed the implementation to take whatever limited advantage we could from the various half-baked validation schemes that were added to each version of the JDK. Finally, in JDK 1.4, the Swing designers faced the issues head-on and supplied a versatile JFormattedTextField class that can be used not just for numeric input but also for dates and for even more esoteric formatted values such as IP addresses. Integer InputLet's get started with an easy case first: a text field for integer input. JFormattedTextField intField = new JFormattedTextField(NumberFormat.getIntegerInstance()); The NumberFormat.getIntegerInstance returns a formatter object that formats integers, using the current locale. In the US locale, commas are used as decimal separators, allowing users to enter values such as 1,729. The internationalization chapter in Volume 2 explains in detail how you can select other locales. As with any text field, you can set the number of columns: intField.setColumns(6); You can set a default value with the setValue method. That method takes an Object parameter, so you'll need to wrap the default int value in an Integer object: intField.setValue(new Integer(100)); Typically, users will supply inputs in multiple text fields and then click a button to read all values. When the button is clicked, you can get the user-supplied value with the getValue method. That method returns an Object result, and you need to cast it into the appropriate type. The JFormattedTextField returns an object of type Long if the user edited the value. However, if the user made no changes, the original Integer object is returned. Therefore, you should cast the return value to the common superclass Number: Number value = (Number) intField.getValue(); int v = value.intValue(); The formatted text field is not very interesting until you consider what happens when a user provides illegal input. That is the topic of the next section. Behavior on Loss of FocusConsider what happens when a user supplies input to a text field. The user types input and eventually decides to leave the field, perhaps by clicking on another component with the mouse. Then the text field loses focus. The I-beam cursor is no longer visible in the text field, and keystrokes are directed toward a different component. When the formatted text field loses focus, the formatter looks at the text string that the user produced. If the formatter knows how to convert the text string to an object, the text is valid. Otherwise it is invalid. You can use the isEditValid method to check whether the current content of the text field is valid. The default behavior on loss of focus is called "commit or revert." If the text string is valid, it is committed. The formatter converts it to an object. That object becomes the current value of the field (that is, the return value of the getValue method that you saw in the preceding section). The value is then converted back to a string, which becomes the text string that is visible in the field. For example, the integer formatter recognizes the input 1729 as valid, sets the current value to new Long(1729) and then converts it back into a string with a decimal comma: 1,729. Conversely, if the text string is invalid, then the current value is not changed and the text field reverts to the string that represents the old value. For example, if the user enters a bad value, such as x1, then the old value is restored when the text field loses focus. NOTE
You can set other behaviors with the setFocusLostBehavior method. The "commit" behavior is subtly different from the default. If the text string is invalid, then both the text string and the field value stay unchanged they are now out of sync. The "persist" behavior is even more conservative. Even if the text string is valid, neither the text field nor the current value are changed. You would need to call commitEdit, setValue, or setText to bring them back in sync. Finally, there is a "revert" behavior that doesn't ever seem to be useful. Whenever focus is lost, the user input is disregarded, and the text string reverts to the old value. NOTE
FiltersThis basic functionality of formatted text fields is straightforward and sufficient for most uses. However, you can add a couple of refinements. Perhaps you want to prevent the user from entering nondigits altogether. You achieve that behavior with a document filter. Recall that in the model-view-controller architecture, the controller translates input events into commands that modify the underlying document of the text field, that is, the text string that is stored in a PlainDocument object. For example, whenever the controller processes a command that causes text to be inserted into the document, it calls the "insert string" command. The string to be inserted can be either a single character or the content of the paste buffer. A document filter can intercept this command and modify the string or cancel the insertion altogether. Here is the code for the insertString method of a filter that analyzes the string to be inserted and inserts only the characters that are digits or a - sign. (The code handles supplementary Unicode characters, as explained in Chapter 3. See Chapter 12 for the StringBuilder class.) public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { StringBuilder builder = new StringBuilder(string); for (int i = builder.length() - 1; i >= 0; i--) { int cp = builder.codePointAt(i); if (!Character.isDigit(cp) && cp != '-') { builder.deleteCharAt(i); if (Character.isSupplementaryCodePoint(cp)) { i--; builder.deleteCharAt(i); } } } super.insertString(fb, offset, builder.toString(), attr); } You should also override the replace method of the DocumentFilter class it is called when text is selected and then replaced. The implementation of the replace method is straightforward see the program at the end of this section. Now you need to install the document filter. Unfortunately, there is no straightforward method to do that. You need to override the getdocumentFilter method of a formatter class, and pass an object of that formatter class to the JFormattedTextField. The integer text field uses an InternationalFormatter that is initialized with NumberFormat.getIntegerInstance(). Here is how you install a formatter to yield the desired filter: JFormattedTextField intField = new JFormattedTextField(new InternationalFormatter(NumberFormat.getIntegerInstance()) { protected DocumentFilter getDocumentFilter() { return filter; } private DocumentFilter filter = new IntFilter(); }); NOTE
Try out the FormatTest example program at the end of this section. The third text field has a filter installed. You can insert only digits or the minus ('-') character. Note that you can still enter invalid strings such as "1-2-3". In general, it is impossible to avoid all invalid strings through filtering. For example, the string "-" is invalid, but a filter can't reject it because it is a prefix of a legal string "-1". Even though filters can't give perfect protection, it makes sense to use them to reject inputs that are obviously invalid. TIP
VerifiersThere is another potentially useful mechanism to alert users to invalid inputs. You can attach a verifier to any JComponent. If the component loses focus, then the verifier is queried. If the verifier reports the content of the component to be invalid, the component immediately regains focus. The user is thus forced to fix the content before supplying other inputs. A verifier must extend the abstract InputVerifier class and define a verify method. It is particularly easy to define a verifier that checks formatted text fields. The isEditValid method of the JFormattedTextField class calls the formatter and returns TRue if the formatter can turn the text string into an object. Here is the verifier. class FormattedTextFieldVerifier extends InputVerifier { public boolean verify(JComponent component) { JFormattedTextField field = (JFormattedTextField) component; return field.isEditValid(); } } You can attach it to any JFormattedTextField: intField.setInputVerifier(new FormattedTextFieldVerifier()); However, a verifier is not entirely foolproof. If you click on a button, then the button notifies its action listeners before an invalid component regains focus. The action listeners can then get an invalid result from the component that failed verification. There is a reason for this behavior: users may want to press a Cancel button without first having to fix an invalid input. The fourth text field in the example program has a verifier attached. Try entering an invalid number (such as x1729) and press the TAB key or click with the mouse on another text field. Note that the field immediately regains focus. However, if you press the OK button, the action listener calls getValue, which reports the last good value. Other Standard FormattersBesides the integer formatter, the JFormattedTextField supports several other formatters. The NumberFormat class has static methods getNumberInstance getCurrencyInstance getPercentInstance that yield formatters of floating-point numbers, currency values, and percentages. For example, you can obtain a text field for the input of currency values by calling JFormattedTextField currencyField = new JFormattedTextField(NumberFormat .getCurrencyInstance()); To edit dates and times, call one of the static methods of the DateFormat class: getDateInstance getTimeInstance getDateTimeInstance For example, JFormattedTextField dateField = new JFormattedTextField(DateFormat.getDateInstance()); This field edits a date in the default or "medium" format such as Feb 24, 2002 You can instead choose a "short" format such as 2/24/02 by calling DateFormat.getDateInstance(DateFormat.SHORT) NOTE
The DefaultFormatter can format objects of any class that has a constructor with a string parameter and a matching toString method. For example, the URL class has a URL(String) constructor that can be used to construct a URL from a string, such as URL url = new URL("http://java.sun.com"); Therefore, you can use the DefaultFormatter to format URL objects. The formatter calls toString on the field value to initialize the field text. When the field loses focus, the formatter constructs a new object of the same class as the current value, using the constructor with a String parameter. If that constructor throws an exception, then the edit is not valid. You can try that out in the example program by entering a URL that does not start with a prefix such as "http:". NOTE
Finally, the MaskFormatter is useful for fixed-size patterns that contain some constant and some variable characters. For example, social security numbers (such as 078-05-1120) can be formatted with a new MaskFormatter("###-##-####") The # symbol denotes a single digit. Table 9-2 shows the symbols that you can use in a mask formatter.
You can restrict the characters that can be typed into the field by calling one of the methods of the MaskFormatter class: setValidCharacters setInvalidCharacters For example, to read in a letter grade (such as A+ or F), you could use MaskFormatter formatter = new MaskFormatter("U*"); formatter.setValidCharacters("ABCDF+- "); However, there is no way of specifying that the second character cannot be a letter. Note that the string that is formatted by the mask formatter has exactly the same length as the mask. If the user erases characters during editing, then they are replaced with the placeholder character. The default placeholder character is a space, but you can change it with the setPlaceholderCharacter method, for example, formatter.setPlaceholderCharacter('0'); By default, a mask formatter is in overtype mode, which is quite intuitive try it out in the example program. Also note that the caret position jumps over the fixed characters in the mask. The mask formatter is very effective for rigid patterns such as social security numbers or American telephone numbers. However, note that no variation at all is permitted in the mask pattern. For example, you cannot use a mask formatter for international telephone numbers that have a variable number of digits. Custom FormattersIf none of the standard formatters is appropriate, it is fairly easy to define your own formatter. Consider 4-byte IP addresses such as 130.65.86.66 You can't use a MaskFormatter because each byte might be represented by one, two, or three digits. Also, we want to check in the formatter that each byte's value is at most 255. To define your own formatter, extend the DefaultFormatter class and override the methods String valueToString(Object value) Object stringToValue(String text) The first method turns the field value into the string that is displayed in the text field. The second method parses the text that the user typed and turns it back into an object. If either method detects an error, it should throw a ParseException. In our example program, we store an IP address in a byte[] array of length 4. The valueToString method forms a string that separates the bytes with periods. Note that byte values are signed quantities between -128 and 127. To turn negative byte values into unsigned integer values, you add 256. public String valueToString(Object value) throws ParseException { if (!(value instanceof byte[])) throw new ParseException("Not a byte[]", 0); byte[] a = (byte[]) value; if (a.length != 4) throw new ParseException("Length != 4", 0); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 4; i++) { int b = a[i]; if (b < 0) b += 256; builder.append(String.valueOf(b)); if (i < 3) builder.append('.'); } return builder.toString(); } Conversely, the stringToValue method parses the string and produces a byte[] object if the string is valid. If not, it throws a ParseException. public Object stringToValue(String text) throws ParseException { StringTokenizer tokenizer = new StringTokenizer(text, "."); byte[] a = new byte[4]; for (int i = 0; i < 4; i++) { int b = 0; try { b = Integer.parseInt(tokenizer.nextToken()); } catch (NumberFormatException e) { throw new ParseException("Not an integer", 0); } if (b < 0 || b >= 256) throw new ParseException("Byte out of range", 0); a[i] = (byte) b; } return a; } Try out the IP address field in the sample program. If you enter an invalid address, the field reverts to the last valid address. The program in Example 9-3 shows various formatted text fields in action (see Figure 9-13). Click the Ok button to retrieve the current values from the fields. Figure 9-13. The FormatTest programNOTE
Example 9-3. FormatTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.lang.reflect.*; 4. import java.net.*; 5. import java.text.*; 6. import java.util.*; 7. import javax.swing.*; 8. import javax.swing.text.*; 9. 10. /** 11. A program to test formatted text fields 12. */ 13. public class FormatTest 14. { 15. public static void main(String[] args) 16. { 17. FormatTestFrame frame = new FormatTestFrame(); 18. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19. frame.setVisible(true); 20. } 21. } 22. 23. /** 24. A frame with a collection of formatted text fields and 25. a button that displays the field values. 26. */ 27. class FormatTestFrame extends JFrame 28. { 29. public FormatTestFrame() 30. { 31. setTitle("FormatTest"); 32. setSize(WIDTH, HEIGHT); 33. 34. JPanel buttonPanel = new JPanel(); 35. okButton = new JButton("Ok"); 36. buttonPanel.add(okButton); 37. add(buttonPanel, BorderLayout.SOUTH); 38. 39. mainPanel = new JPanel(); 40. mainPanel.setLayout(new GridLayout(0, 3)); 41. add(mainPanel, BorderLayout.CENTER); 42. 43. JFormattedTextField intField = new JFormattedTextField(NumberFormat .getIntegerInstance()); 44. intField.setValue(new Integer(100)); 45. addRow("Number:", intField); 46. 47. JFormattedTextField intField2 = new JFormattedTextField(NumberFormat .getIntegerInstance()); 48. intField2.setValue(new Integer(100)); 49. intField2.setFocusLostBehavior(JFormattedTextField.COMMIT); 50. addRow("Number (Commit behavior):", intField2); 51. 52. JFormattedTextField intField3 53. = new JFormattedTextField(new 54. InternationalFormatter(NumberFormat.getIntegerInstance()) 55. { 56. protected DocumentFilter getDocumentFilter() 57. { 58. return filter; 59. } 60. private DocumentFilter filter = new IntFilter(); 61. }); 62. intField3.setValue(new Integer(100)); 63. addRow("Filtered Number", intField3); 64. 65. JFormattedTextField intField4 = new JFormattedTextField(NumberFormat .getIntegerInstance()); 66. intField4.setValue(new Integer(100)); 67. intField4.setInputVerifier(new FormattedTextFieldVerifier()); 68. addRow("Verified Number:", intField4); 69. 70. JFormattedTextField currencyField 71. = new JFormattedTextField(NumberFormat.getCurrencyInstance()); 72. currencyField.setValue(new Double(10)); 73. addRow("Currency:", currencyField); 74. 75. JFormattedTextField dateField = new JFormattedTextField(DateFormat .getDateInstance()); 76. dateField.setValue(new Date()); 77. addRow("Date (default):", dateField); 78. 79. DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT); 80. format.setLenient(false); 81. JFormattedTextField dateField2 = new JFormattedTextField(format); 82. dateField2.setValue(new Date()); 83. addRow("Date (short, not lenient):", dateField2); 84. 85. try 86. { 87. DefaultFormatter formatter = new DefaultFormatter(); 88. formatter.setOverwriteMode(false); 89. JFormattedTextField urlField = new JFormattedTextField(formatter); 90. urlField.setValue(new URL("http://java.sun.com")); 91. addRow("URL:", urlField); 92. } 93. catch (MalformedURLException e) 94. { 95. e.printStackTrace(); 96. } 97. 98. try 99. { 100. MaskFormatter formatter = new MaskFormatter("###-##-####"); 101. formatter.setPlaceholderCharacter('0'); 102. JFormattedTextField ssnField = new JFormattedTextField(formatter); 103. ssnField.setValue("078-05-1120"); 104. addRow("SSN Mask:", ssnField); 105. } 106. catch (ParseException exception) 107. { 108. exception.printStackTrace(); 109. } 110. 111. JFormattedTextField ipField = new JFormattedTextField(new IPAddressFormatter()); 112. ipField.setValue(new byte[] { (byte) 130, 65, 86, 66 }); 113. addRow("IP Address:", ipField); 114. } 115. 116. /** 117. Adds a row to the main panel. 118. @param labelText the label of the field 119. @param field the sample field 120. */ 121. public void addRow(String labelText, final JFormattedTextField field) 122. { 123. mainPanel.add(new JLabel(labelText)); 124. mainPanel.add(field); 125. final JLabel valueLabel = new JLabel(); 126. mainPanel.add(valueLabel); 127. okButton.addActionListener(new 128. ActionListener() 129. { 130. public void actionPerformed(ActionEvent event) 131. { 132. Object value = field.getValue(); 133. if (value.getClass().isArray()) 134. { 135. StringBuilder builder = new StringBuilder(); 136. builder.append('{'); 137. for (int i = 0; i < Array.getLength(value); i++) 138. { 139. if (i > 0) builder.append(','); 140. builder.append(Array.get(value, i).toString()); 141. } 142. builder.append('}'); 143. valueLabel.setText(builder.toString()); 144. } 145. else 146. valueLabel.setText(value.toString()); 147. } 148. }); 149. } 150. 151. public static final int WIDTH = 500; 152. public static final int HEIGHT = 250; 153. 154. private JButton okButton; 155. private JPanel mainPanel; 156. } 157. 158. /** 159. A filter that restricts input to digits and a '-' sign. 160. */ 161. class IntFilter extends DocumentFilter 162. { 163. public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) 164. throws BadLocationException 165. { 166. StringBuilder builder = new StringBuilder(string); 167. for (int i = builder.length() - 1; i >= 0; i--) 168. { 169. int cp = builder.codePointAt(i); 170. if (!Character.isDigit(cp) && cp != '-') 171. { 172. builder.deleteCharAt(i); 173. if (Character.isSupplementaryCodePoint(cp)) 174. { 175. i--; 176. builder.deleteCharAt(i); 177. } 178. } 179. } 180. super.insertString(fb, offset, builder.toString(), attr); 181. } 182. 183. public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) 184. throws BadLocationException 185. { 186. if (string != null) 187. { 188. StringBuilder builder = new StringBuilder(string); 189. for (int i = builder.length() - 1; i >= 0; i--) 190. { 191. int cp = builder.codePointAt(i); 192. if (!Character.isDigit(cp) && cp != '-') 193. { 194. builder.deleteCharAt(i); 195. if (Character.isSupplementaryCodePoint(cp)) 196. { 197. i--; 198. builder.deleteCharAt(i); 199. } 200. } 201. } 202. string = builder.toString(); 203. } 204. super.replace(fb, offset, length, string, attr); 205. } 206. } 207. 208. /** 209. A verifier that checks whether the content of 210. a formatted text field is valid. 211. */ 212. class FormattedTextFieldVerifier extends InputVerifier 213. { 214. public boolean verify(JComponent component) 215. { 216. JFormattedTextField field = (JFormattedTextField) component; 217. return field.isEditValid(); 218. } 219. } 220. 221. /** 222. A formatter for 4-byte IP addresses of the form a.b.c.d 223. */ 224. class IPAddressFormatter extends DefaultFormatter 225. { 226. public String valueToString(Object value) 227. throws ParseException 228. { 229. if (!(value instanceof byte[])) 230. throw new ParseException("Not a byte[]", 0); 231. byte[] a = (byte[]) value; 232. if (a.length != 4) 233. throw new ParseException("Length != 4", 0); 234. StringBuilder builder = new StringBuilder(); 235. for (int i = 0; i < 4; i++) 236. { 237. int b = a[i]; 238. if (b < 0) b += 256; 239. builder.append(String.valueOf(b)); 240. if (i < 3) builder.append('.'); 241. } 242. return builder.toString(); 243. } 244. 245. public Object stringToValue(String text) throws ParseException 246. { 247. StringTokenizer tokenizer = new StringTokenizer(text, "."); 248. byte[] a = new byte[4]; 249. for (int i = 0; i < 4; i++) 250. { 251. int b = 0; 252. if (!tokenizer.hasMoreTokens()) 253. throw new ParseException("Too few bytes", 0); 254. try 255. { 256. b = Integer.parseInt(tokenizer.nextToken()); 257. } 258. catch (NumberFormatException e) 259. { 260. throw new ParseException("Not an integer", 0); 261. } 262. if (b < 0 || b >= 256) 263. throw new ParseException("Byte out of range", 0); 264. a[i] = (byte) b; 265. } 266. if (tokenizer.hasMoreTokens()) 267. throw new ParseException("Too many bytes", 0); 268. return a; 269. } 270. } javax.swing.JFormattedTextField 1.4
java.text.DateFormat 1.1\
javax.swing.JFormattedTextField.AbstractFormatter 1.4
javax.swing.text.DefaultFormatter 1.3
javax.swing.text.DocumentFilter 1.4
javax.swing.text.MaskFormatter 1.4
Text AreasSometimes, you need to collect user input that is more than one line long. As mentioned earlier, you use the JTextArea component for this collection. When you place a text area component in your program, a user can enter any number of lines of text, using the ENTER key to separate them. Each line ends with a '\n'. If you need to break up the user's entry into separate lines, you can use the StringTokenizer class (see Chapter 12). Figure 9-14 shows a text area at work. Figure 9-14. A text areaIn the constructor for the JTextArea component, you specify the number of rows and columns for the text area. For example: textArea = new JTextArea(8, 40); // 8 lines of 40 columns each where the columns parameter works as before and you still need to add a few more columns for safety's sake. Also, as before, the user is not restricted to the number of rows and columns; the text simply scrolls when the user inputs too much. You can also use the setColumns method to change the number of columns, and the setRows method to change the number of rows. These numbers only indicate the preferred size the layout manager can still grow or shrink the text area. If there is more text than the text area can display, then the remaining text is simply clipped. You can avoid clipping long lines by turning on line wrapping: textArea.setLineWrap(true); // long lines are wrapped This wrapping is a visual effect only; the text in the document is not changed no '\n' characters are inserted into the text. In Swing, a text area does not have scrollbars. If you want scrollbars, you have to insert the text area inside a scroll pane. textArea = new JTextArea(8, 40); JScrollPane scrollPane = new JScrollPane(textArea); The scroll pane now manages the view of the text area. Scrollbars automatically appear if there is more text than the text area can display, and they vanish again if text is deleted and the remaining text fits inside the area. The scrolling is handled internally in the scroll pane your program does not need to process scroll events. TIP
Example 9-4 is the complete code for the text area demo. This program simply lets you edit text in a text area. Click on "Insert" to insert a sentence at the end of the text. Click the second button to turn line wrapping on and off. (Its name toggles between "Wrap" and "No wrap".) Of course, you can simply use the keyboard to edit the text in the text area. Note how you can highlight a section of text, and how you can cut, copy, and paste with the CTRL+X, CTRL+C, and CTRL+V keys. (Keyboard shortcuts are specific to the look and feel. These particular key combinations work for the Metal and Windows look and feel.) NOTE
Example 9-4. TextAreaTest.java1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. 5. public class TextAreaTest 6. { 7. public static void main(String[] args) 8. { 9. TextAreaFrame frame = new TextAreaFrame(); 10. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11. frame.setVisible(true); 12. } 13. } 14. 15. /** 16. A frame with a text area and buttons for text editing 17. */ 18. class TextAreaFrame extends JFrame 19. { 20. public TextAreaFrame() 21. { 22. setTitle("TextAreaTest"); 23. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25. buttonPanel = new JPanel(); 26. 27. // add button to append text into the text area 28. 29. JButton insertButton = new JButton("Insert"); 30. buttonPanel.add(insertButton); 31. insertButton.addActionListener(new 32. ActionListener() 33. { 34. public void actionPerformed(ActionEvent event) 35. { 36. textArea.append("The quick brown fox jumps over the lazy dog. "); 37. } 38. }); 39. 40. // add button to turn line wrapping on and off 41. 42. wrapButton = new JButton("Wrap"); 43. buttonPanel.add(wrapButton); 44. wrapButton.addActionListener(new 45. ActionListener() 46. { 47. public void actionPerformed(ActionEvent event) 48. { 49. boolean wrap = !textArea.getLineWrap(); 50. textArea.setLineWrap(wrap); 51. scrollPane.revalidate(); 52. wrapButton.setText(wrap ? "No Wrap" : "Wrap"); 53. } 54. }); 55. 56. add(buttonPanel, BorderLayout.SOUTH); 57. 58. // add a text area with scrollbars 59. 60. textArea = new JTextArea(8, 40); 61. scrollPane = new JScrollPane(textArea); 62. 63. add(scrollPane, BorderLayout.CENTER); 64. } 65. 66. public static final int DEFAULT_WIDTH = 300; 67. public static final int DEFAULT_HEIGHT = 300; 68. 69. private JTextArea textArea; 70. private JScrollPane scrollPane; 71. private JPanel buttonPanel; 72. private JButton wrapButton; 73. } javax.swing.JTextArea 1.2
javax.swing.JScrollPane 1.2
|