This document outlines the Swing implementation of formatted text fields. Formatted text fields provide a way for developers to specify the legal set of characters that can be input into a text component. This document is arranged in the following sections:
JFormattedTextField
will allow formatting of dates, numbers, Strings and arbitrary Objects.
JFormattedTextField will rely on the
java.text.Format
classes to handle formatting of dates and numbers.
To create a JFormattedTextField
to input dates in the current locale-specific format:
new JFormattedTextField(new Date());
If you need to display the dates in a specific format, you can use one of
the
SimpleDateFormat constructors:
new JFormattedTextField(new SimpleDateFormat("MM/dd/yy"));
Numbers will be handled by an instance of
java.text.NumberFormat.
The following shows several ways to create a
JFormattedTextField to edit numbers.
new JFormattedTextField(new Number(1000));
new JFormattedTextField(new DecimalFormat("#,###"));
new JFormattedTextField(new DecimalFormat("0.###E0"));
JFormattedTextField will also support editing of strings given a
mask that specifies what the legal characters are at a given character position.
To create a JFormattedTextField to edit US phone numbers the
following could be used:
new JFormattedTextField(new MaskFormatter("(###) ###-####"));
JFormattedTextField itself exposes a minimal amount of
API in addition to that of its super class, JTextField.
In its simplest terms, JFormattedTextField can be thought of
as a JTextField with an additional value
property (of type Object) and an object to handle the formatting (instance of
AbstractFormatter).
As the value property is of type Object,
it is necessary for the developer to cast the return type based on how the
JFormattedTextField
has been configured. The following shows a typical scenario of using a
JFormattedTextField with dates:
JFormattedTextField ftf = new JFormattedTextField(); ftf.setValue(new Date()); ... Date date = (Date)ftf.getValue();
A typical session for editing numbers looks like:
JFormattedTextField ftf = new JFormattedTextField(); ftf.setValue(new Integer(1000)); ... int intValue = ((Number)ftf.getValue()).intValue();
Constraining the input into a text component previously
required creating a subclass of Document.
This is a rather heavy operation for such a simple, and common, usage.
To make this task easier, we have created a class,
DocumentFilter,
that can be plugged into a Document
(Document
is an interface, which has not changed, instead
AbstractDocument
now has a setter/getter for a DocumentFilter,
and a property is set for Documents that do not descend
from AbstractDocument so that others can support
DocumentFilter should they want to).
When a Document with a DocumentFilter is messaged
to remove or insert content, the Document invokes the
corresponding method on the DocumentFilter.
It is the DocumentFilter's
responsibility to issue a callback if the operation should proceed.
In this manner the DocumentFilter has total control over
how the Document can be mutated.
DocumentFilter is defined by:
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException; public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException; public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException;
If the DocumentFilter wants to mutate the Document
from inside the remove or insertString methods,
it should either invoke super's implementation, or invoke
the method on the FilterBypass. Invoking the method on
super or FilterBypass
provides a way to circumvent the filter so that the caller doesn't get stuck in
stack recursion. The DocumentFilter is not limited to only
invoking one method back on the FilterBypass. It can invoke
any of the methods exposed by FilterBypass, and can invoke them
as many times as it wishes (within the scope
of one of DocumentFilter's methods).
FilterBypass is defined by:
public abstract Document getDocument(); public abstract void remove(int offset, int length) throws BadLocationException; public abstract void insertString(int offset, String string, AttributeSet attr) throws BadLocationException; public abstract void replace(int offset, int length, String string, AttributeSet attr) throws BadLocationException;
The following example illustrates creating a
DocumentFilter that maps lower case to upper case letters:
DocumentFilter upperDF = new DocumentFilter() {
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, string.toUpperCase(), attr);
}
public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException {
if (string != null) {
string = text.toUpperCase();
}
super.insertString(fb, offset, string, attr);
}
};
Similar to DocumentFilter, a new class,
NavigationFilter, allows for filtering where the selection
can be placed. NavigationFilter
is called by the navigation actions (such as left, right, center) to
determine the next position to place the selection from a given position.
NavigationFilter is a property on
JTextComponent, and is defined by:
public void setDot(FilterBypass fb, int dot, Position.Bias bias);
public void moveDot(FilterBypass fb, int dot, Position.Bias bias);
public int getNextVisualPositionFrom(JTextComponent text, int pos, Position.Bias bias, int direction, Position.Bias[] biasRet) throws BadLocationException;
Note that getNextVisualPositionFrom is defined in
View;
for consistency, the method in NavigationFilter
is named the same.
Similar to DocumentFilter, NavigationFilter
is passed a FilterBypass that should be invoked to handle the
actual mutation. NavigationFilter.FilterBypass is defined by:
public abstract Caret getCaret();
public abstract void setDot(int dot, Position.Bias bias);
public abstract void moveDot(int dot, Position.Bias bias);
As previously mentioned, an instance of AbstractFormatter
is used to format a particular Object value. AbstractFormatter
can also impose an editing policy by defining a DocumentFilter;
it can also impose a navigation policy by defining a
NavigationFilter.
AbstractFormatter is defined by:
public void install(JFormattedTextField ftf);
public void uninstall();
public abstract Object stringToValue(String text) throws ParseException;
public abstract String valueToString(Object value) throws ParseException;
protected JFormattedTextField getFormattedTextField();
protected void setEditValid(boolean valid);
protected void invalidEdit();
protected Action[] getActions();
protected DocumentFilter getDocumentFilter();
protected NavigationFilter getNavigationFilter();
Once JFormattedTextField is ready to use an
AbstractFormatter it invokes install.
AbstractFormatter.install performs the following:
JFormattedTextField
to the return value of valueToString (if a
ParseException is thrown, an empty String
is used and setEditValid(false) is invoked).
DocumentFilter returned from
getDocumentFilter onto the
JFormattedTextField's Document.
NavigationFilter returned from
getNavigationFilter onto the
JFormattedTextField.
Actions returned from getActions
onto the JFormattedTextField's ActionMap.
Subclasses will typically only override install
if they need to install additional Listeners beyond the
DocumentFilter and NavigationFilter, or perhaps
place the caret at an initial location.
Some AbstractFormatters allow the
JFormattedTextField to contain an invalid value when
editing. To allow the JFormattedTextField to provide an
indication of this, the AbstractFormatter should invoke
setEditValid(false) when the user enters an invalid value,
and then invoke setEditValid(true) when the value is valid again.
When JFormattedTextField is done with an
AbstractFormatter, it invokes uninstall.
uninstall removes the previously installed Listeners.
JFormattedTextField delegates the creation of
AbstractFormatters to an instance of
AbstractFormatterFactory (a public static inner class of
JFormattedTextField). This makes it easy for developers to provide
different formatters for different states of the
JFormattedTextField.
For example, you could provide a special AbstractFormatter if the
current value is null, or one formatter when editing
and another when displaying. AbstractFormatterFactory
is defined by:
public abstract AbstractFormatter getFormatter(JFormattedTextField ftf);
If the developer has not supplied an AbstractFormatterFactory,
one will be created based on the Class of the value.
DefaultFormatter extends
JFormattedTextField.AbstractFormatter and is
the superclass for all of the formatter implementations we provide.
DefaultFormatter formats Objects using
toString and creates the Object using
the constructor that takes a String.
DefaultFormatter allows a number of
configuration options:
Option |
Description |
| CommitsOnValidEdit |
Determines when edits are published back to the
JFormattedTextField. If true,
commitEdit is invoked on the
JFormattedTextField after every successful
edit, otherwise commitEdit is invoked
only when return is pressed.
|
| OverwriteMode |
Configures the behavior when inserting characters. If
overwriteMode is true (the default),
new characters overwrite existing
characters in the model as they are inserted.
|
| AllowsInvalid | Determines whether the value being edited is allowed to be invalid. It is often convenient to allow the user to enter invalid values until a commit is attempted. |
The following table lists the AbstractFormatter
implementations that we provide, as well as intended use:
AbstractFormatter |
Object Type |
Notes |
| DefaultFormatter | Object |
valueToString uses Object.toString(),
and stringToValue uses the single argument constructor
that takes a String.
|
| MaskFormatter | Strings | Behavior is dictated by a per character mask that specifies legal values (e.g. "###-####"). |
| InternationalFormatter | Objects |
Uses an instance of java.text.Format to handle
valueToString and stringToValue.
|
| NumberFormatter | Numbers |
Uses an instance of NumberFormat to handle formatting,
descends from InternationalFormatter.
|
| DateFormatter | Date |
Uses an instance of DateFormat to handle formatting,
descends from InternationalFormatter.
|
The Swing support for formatted dates and numbers made extensive use of the
Format classes in the java.text
package. The following problems were encountered using the previous API:
format repeatedly.
Format subclasses out there that
we didn't know about. Note that the constants for DateFormat
and NumberFormat already overlap, so a
NumberFormat would have happily interpreted YEAR_FIELD
as FRACTION_FIELD.
The problem was exacerbated by the existence of the polymorphic
Format.format(Object, StringBuffer, FieldPosition) method.
NumberFormat for the MONTH_FIELD
(the actual implementation of this case returned 0 for both begin index and
end index).
Format subclass actually supported
unless it knew the specific subclass. This, along with the fact that not
all characters in a formatted string are part of fields, makes the
implementation of a generic method that returns all fields impossible.
These issues have largely been addressed by adding the following method to
java.text.Format:
public AttributedCharacterIterator formatToCharacterIterator(Object obj);
Each Format class uses a type safe enumeration for the constants it supports.
The following classes are new to the 1.4 release:
The bugtraq report that corresponds to this change is: 4468474.
The following constants have been replaced to conform Java to naming conventions:
CommitValueOnFocusLost has been replaced
with
COMMIT.
CommitOrRevertValueOnFocusLost has been replaced
with
COMMIT_OR_REVERT.
RevertValueOnFocusLost has been replaced
with
REVERT.
PersistValueOnFocusLost has been replaced
with
PERSIST.