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 Document
s 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
.
Action
s returned from getActions
onto the JFormattedTextField
's ActionMap
.
Subclasses will typically only override install
if they need to install additional Listener
s beyond the
DocumentFilter
and NavigationFilter
, or perhaps
place the caret at an initial location.
Some AbstractFormatter
s 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 Listener
s.
JFormattedTextField
delegates the creation of
AbstractFormatter
s 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
.