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.replace(fb, offset, length, 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
.