Formatted Text Fields

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:

Using JFormattedTextField

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("(###) ###-####"));

How JFormattedTextField Works

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();

Filtering Input

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.replace(fb, offset, length, string, attr);
      }
   };

Filtering Navigation

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);

JFormattedTextField.AbstractFormatter

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:

  1. Sets the text of the JFormattedTextField to the return value of valueToString (if a ParseException is thrown, an empty String is used and setEditValid(false) is invoked).
  2. Installs the DocumentFilter returned from getDocumentFilter onto the JFormattedTextField's Document.
  3. Installs the NavigationFilter returned from getNavigationFilter onto the JFormattedTextField.
  4. Installs the 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.AbstractFormatterFactory

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

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.

AbstractFormatter implementations

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.

java.text.Format changes

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:

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.

JavaDoc

New Classes

The following classes are new to the 1.4 release:

Constants are renamed to conform to Java naming conventions

The bugtraq report that corresponds to this change is: 4468474.

The following constants have been replaced to conform Java to naming conventions: