The JFC Swing Tutorial: A Guide to Constructing GUIs (2nd Edition)
< Day Day Up > |
Release 1.4 introduced a subclass of JTextField called JFormattedTextField . [55] Formatted text fields provide a way for developers to specify the legal set of characters that can be entered into a text field. Specifically, JFormattedTextField adds a formatter and an object value to the features inherited from JTextField . The formatter performs the translation from the field's value into the text it displays, and vice versa. [55] JFormattedTextField API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JFormattedTextField.html. Using the formatters Swing provides, you can set up formatted text fields for easy input of dates and numbers in localized formats. Another kind of formatter lets you use a character mask to specify the set of characters that can be entered at each position in the field. For example, you can specify a mask for entering phone numbers in a particular format, such as ( XX ) X-XX-XX-XX-XX .
Version Note: Before v1.4, text field formatting required more effort. You could check the field's value when the user pressed Enter by putting format-checking code in your action listener, but you couldn't do any checking before the action event was generated unless you implemented a custom model ( Document ) for the text field. Version 1.3 introduced input verification, but that isn't specialized for text fields and is tied to focus changes. For details, see Validating Input (page 587) in Chapter 9.
If the possible values of a formatted text field have an obvious order, consider using a spinner instead. A spinner uses a formatted text field, by default, but adds two buttons that let the user step through a sequence of values. Another alternative or adjunct to using a formatted text field is installing an input verifier on the field. A component's input verifier is called when the component is about to lose the keyboard focus. It lets you check whether the value of the component is legal and, optionally , change it or stop the focus from being transferred. Figure 19 shows a picture of a GUI that uses formatted text fields to display numbers in four different formats. Figure 19. The FormattedTextFieldDemo application.
Try This:
Here's the code that creates the first field in FormattedTextFieldDemo : amountField = new JFormattedTextField(amountFormat); amountField.setValue(new Double(amount)); amountField.setColumns(10); amountField.addPropertyChangeListener("value", this); ... amountFormat = NumberFormat.getNumberInstance(); The constructor used to create amountField takes a java.text.Format argument. The Format object is used by the field's formatter to translate between the field's text and value. The rest of the code sets up amountField . The setValue method sets the field's value property to a floating-point number represented as a Double object. The setColumns method, inherited from JTextField , provides a hint as to the preferred size of the field. Finally, the call to addPropertyChangeListener registers a listener for the value property of the field so that the program can update the Monthly Payment field whenever the user changes the Loan Amount field.
Note: This section doesn't explain the API inherited from JTextField ; that API is described in How to Use Text Fields (page 423).
Creating and Initializing Formatted Text Fields
The following code creates and initializes the remaining three fields in FormattedTextFieldDemo . rateField = new JFormattedTextField(percentFormat); rateField.setValue(new Double(rate)); rateField.setColumns(10); rateField.addPropertyChangeListener("value", this); numPeriodsField = new JFormattedTextField(); numPeriodsField.setValue(new Integer(numPeriods)); numPeriodsField.setColumns(10); numPeriodsField.addPropertyChangeListener("value", this); paymentField = new JFormattedTextField(paymentFormat); paymentField.setValue(new Double(payment)); paymentField.setColumns(10); paymentField.setEditable(false); paymentField.setForeground(Color.red); ... percentFormat = NumberFormat.getNumberInstance(); percentFormat.setMinimumFractionDigits(2); paymentFormat = NumberFormat.getCurrencyInstance(); The code for setting up numPeriodsField is almost identical to the code you saw before. The only difference is that the format is slightly different, thanks to the code percent- Format.setMinimumFractionDigits(2) . The code that creates the numPeriodsField doesn't explicitly set a format or formatter. Instead, it sets the value to an Integer and lets the field use the default formatter for Integer s. We couldn't do this in the previous two fields because we didn't want to use the default formatter for Double s; the result didn't look exactly like we wanted it to. We'll discuss how to specify formats and formatters a little later. The payment field is different from the other fields because it's uneditable, uses a different color for its text, and doesn't happen to have a property-change listener. However, it's otherwise the same as the other fields. We could have chosen to use a text field or label instead. Whatever the component, we could still use paymentFormat to parse the payment amount into the text to be displayed. Setting and Getting the Field's Value
Keep this in mind when using a formatted text field: A formatted text field's text and its value are two different properties, and the value often lags behind the text. The text property is defined by JTextField ; it always reflects what the field displays. The value property, defined by JFormattedTextField , might not reflect the latest text displayed in the field. While the user is typing, the text property changes, but the value property doesn't until the changes are committed . To be more precise, the value of a formatted text field can be set using either the setValue method or the commitEdit method. The setValue method sets the value to the specified argument. Although the argument can technically be any Object , it needs to be something that the formatter can convert into a string. Otherwise, the text field won't display anything useful. The commitEdit method sets the value to whatever object the formatter determines is represented by the field's text. The commitEdit method is automatically called when either of the following happens:
Note: Some formatters might update the value constantly, making the focus-lost behavior moot since the value will always be the same as what the text specifies.
When you set the value of a formatted text field, the field's text is updated to reflect the value. Exactly how the value is represented as text depends on the field's formatter. Note that although JFormattedTextField inherits the setText method from JTextField , you don't usually invoke setText on a formatted text field. If you do, the field's display will change accordingly but the value will not be updated (unless the field's formatter updates it constantly). To get a formatted text field's current value, use the getValue method. If necessary, you can ensure that the value reflects the text by calling commitEdit before getValue . Because getValue returns an Object , you need to cast it to the type used for your field's value. For example, see the following: Date enteredDate = (Date)dateField.getValue(); To detect changes in a formatted text field's value, you can register a property change listener on the formatted text field to listen for changes to the value property. Here's the property change listener from FormattedTextFieldDemo : //The property change listener is registered on each //field using code like this: // someField.addPropertyChangeListener("value", this); /** Called when a field's "value" property changes. */ public void propertyChange(PropertyChangeEvent e) { Object source = e.getSource(); if (source == amountField) { amount = ((Number)amountField.getValue()).doubleValue(); } else if (source == rateField) { rate = ((Number)rateField.getValue()).doubleValue(); } else if (source == numPeriodsField) { numPeriods = ((Number)numPeriodsField.getValue()).intValue(); } double payment = computePayment(amount, rate, numPeriods); paymentField.setValue(new Double(payment)); } Specifying Formats
The Format [57] class provides a way to format locale-sensitive information such as dates and numbers. Formatters that descend from InternationalFormatter , [58] such as DateFormatter and NumberFormatter , use Format objects to translate between the field's text and value. You can get a Format object by invoking one of the factory methods in DateFormat or NumberFormat , or by using one of the SimpleDateFormat constructors. [57] Format API documentation: http://java.sun.com/j2se/1.4.2/docs/api/java/text/Format.html. [58] InternationalFormatter.html API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/InternationalFormatter.html.
Note: A third commonly used formatter class, known as MaskFormatter , does not descend from InternationalFormatter and does not use formats. It's discussed in Using MaskFormatter (page 227).
You can customize certain format aspects when you create the Format , and others through a format-specific API. For example, DecimalFormat objects, which inherit from Number- Format and are often returned by its factory methods, can be customized using the methods setMaximumFractionDigits and setNegativePrefix . For information about using Format s, see the "Formatting" lesson of The Java Tutorial's "Internationalization" trail available on the CD and online at: http://java.sun.com/docs/books/tutorial/i18n/index.html. The easiest way to associate a customized format with a formatted text field is to create the field using the JFormattedTextField constructor that takes a Format as an argument. You can see this in the preceding code snippets that create amountField and rateField . Using MaskFormatter
The MaskFormatter [59] class implements a formatter that specifies exactly which characters are legal in each position of the field's text. For example, the following code creates a MaskFormatter that lets the user enter a 5-digit zip code: [59] MaskFormatter API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/MaskFormatter.html. zipField = new JFormattedTextField( createFormatter("#####")); ... protected MaskFormatter createFormatter(String s) { MaskFormatter formatter = null; try { formatter = new MaskFormatter(s); } catch (java.text.ParseException exc) { System.err.println("formatter is bad: " + exc.getMessage()); System.exit(-1); } return formatter; } You can try out the results of the preceding code by running TextInputDemo . Figure 21 shows the program's GUI. Table 25 shows the characters you can use in the formatting mask. Figure 21. The TextInputDemo GUI.
Table 25. Characters to Use in the Formatting Mask
Specifying Formatters and Using Formatter Factories
When specifying formatters, keep in mind that each formatter object can be used by at most one formatted text field at a time. Each field should have at least one formatter associated with it, of which exactly one is used at any time. You can specify the formatters to be used by a formatted text field in several ways: Use the JFormattedTextField constructor that takes a Format argument.
A formatter for the field that uses the specified format is automatically created. Use the JFormattedTextField constructor that takes a JFormattedTextField. AbstractFormatter argument.
The specified formatter is used for the field. Set the value of a formatted text field that has no format, formatter, or formatter factory specified.
A formatter is assigned to the field by the default formatter factory, using the type of the field's value as a guide. If the value is a Date , the formatter is a DateFormatter . If the value is a Number , the formatter is a NumberFormatter . Other types result in an instance of DefaultFormatter . Make the formatted text field use a formatter factory that returns customized formatter objects.
This is the most flexible approach. It's useful when you want to associate more than one formatter with a field or add a new kind of formatter to be used for multiple fields. As an example of the former use, you might have a field that should interpret user typing in a certain way but display the value (when the user isn't typing) another way. As an example of the latter use, you might have several fields that have values of a custom classsay, PhoneNumber . You could set up the fields to use a formatter factory that can return specialized formatters for phone numbers. You can set a field's formatter factory either by creating the field using a constructor that takes a formatter factory argument or by invoking the setFormatterFactory method on the field. To create a formatter factory, you can often use an instance of DefaultFormatter-Factory . [60] A DefaultFormatterFactory object lets you specify the formatters returned when a value is being edited, not being edited, or has a null value. [60] DefaultFormatterFactory API documentation: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/DefaultFormatterFactory.html. The pictures in Figure 22 show an application based on FormattedTextFieldDemo that uses formatter factories to set multiple editors for the Loan Amount and APR fields. While the user is editing the Loan Amount, the $ character is not used so that the user isn't forced to enter it. Similarly, while the user is editing the APR field, the % sign is not required. Figure 22. In the first FormatterFactoryDemo GUI, the percent symbol (%) is required in the APR field. In the second GUI, the dollar sign ($) is required in the Loan Amount field.
Here's the code that creates the formatters and sets them up using instances of Default-FormatterFactory : private double rate = .075; //7.5 % ... amountField = new JFormattedTextField( new DefaultFormatterFactory( new NumberFormatter(amountDisplayFormat), new NumberFormatter(amountDisplayFormat), new NumberFormatter(amountEditFormat)) ); ... NumberFormatter percentEditFormatter = new NumberFormatter(percentEditFormat) { public String valueToString(Object o) throws ParseException { Number number = (Number)o; if (number != null) { double d = number.doubleValue() * 100.0; number = new Double(d); } return super.valueToString(number); } public Object stringToValue(String s) throws ParseException { Number number = (Number)super.stringToValue(s); if (number != null) { double d = number.doubleValue() / 100.0; number = new Double(d); } return number; } }; rateField = new JFormattedTextField( new DefaultFormatterFactory( new NumberFormatter(percentDisplayFormat), new NumberFormatter(percentDisplayFormat), percentEditFormatter) ); ... amountDisplayFormat = NumberFormat.getCurrencyInstance(); amountDisplayFormat.setMinimumFractionDigits(0); amountEditFormat = NumberFormat.getNumberInstance(); percentDisplayFormat = NumberFormat.getPercentInstance(); percentDisplayFormat.setMinimumFractionDigits(2); percentEditFormat = NumberFormat.getNumberInstance(); percentEditFormat.setMinimumFractionDigits(2); The boldface code highlights the calls to DefaultFormatterFactory constructors. The first argument to the constructor specifies the default formatter to use for the formatted text field. The second specifies the display formatter, which is used when the field doesn't have the focus. The third specifies the edit formatter, used when the field has the focus. The code doesn't use a fourth argument, but if it did it would specify the null formatter, which is used when the field's value is null. Because no null formatter is specified, the default formatter is used when the value is null. The code customizes the formatter that uses percentEditFormat by creating a subclass of NumberFormatter . This subclass overrides the valueToString and stringToValue methods of NumberFormatter so that they convert the displayed number to the value actually used in calculations, and vice versa. Specifically, the displayed number is 100 times the actual value. The reason is that the percent format used by the display formatter automatically displays the text as 100 times the value, so the corresponding editor formatter must do so as well. The first demo, FormattedTextFieldDemo , doesn't need to worry about this conversion because it uses only one format for both display and editing. The Formatted Text Field API
Tables 26 through 28 list some of the commonly used API for using formatted text fields. You can find the relevant API doc at: http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JFormattedTextField.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JFormattedTextField.AbstractFormatter.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JFormattedTextField.AbstractFormatterFactory.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/DefaultFormatterFactory.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/DefaultFormatter.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/MaskFormatter.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/InternationalFormatter.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/NumberFormatter.html http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/DateFormatter.html Table 26. Classes Related to Formatted Text Fields
Table 27. JFormattedTextField Methods
Table 28. DefaultFormatter Options
Examples That Use Formatted Text Fields
A few of our examples use formatted text fields.
|
< Day Day Up > |