Formatted Text Fields

This document outlines a proposal for formatted text fields, that is, 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 will allow formatting of dates, numbers, Strings and abitrary Objects. JFormattedTextField will rely on the java.text.Format classes to handle formatting of dates and numbers. To configure a JFormattedTextField to input dates in the current locale specific format can be done with:

    new JFormattedTextField(new Date());

If you need to display the dates in a specific format, you can use one of the SimpleDateFormat constructors, eg:

    new JFormattedTextField(new SimpleDateFormat("MM/dd/yy"));

Numbers will be handled by an instance of java.text.NumberFormat. The following shows a couple of varitions in creating a JFormattedTextField to edit numbers. Refer to the javadoc of NumberFormat for details on what the constructors are doing:

    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 will expose 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 will be 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();

And 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

Filtering the text that can be input into a text component currently requries creating a subclass of Document. This is a rather heavy operation for such a simple, and common, usage. To make this task easier, we will create a class, DocumentFilter, that can be plugged into a Document (Document is an interface, which we will not change, instead AbstractDocument will get a setter/getter for a DocumentFilter, and a property will be 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 in turn invokes the corresponding method on the DocumentFilter. It is the DocumentFilters responsibility to callback if the operation should proceed. In this manner the DocumentFilter will have 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;

If the DocumentFilter wants to mutate the Document from inside the remove or insertString methods it should either invoke supers implementation, or invoke the method on the FilterBypass. Invoking the method on super or FilterBypass provides a way to circument 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 DocumentFilters 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;

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

Filtering Navigation

Similar to DocumentFilter, a new class, NavigationFilter, will be created that allows for filtering where the selection can be placed. NavigationFilter will also be called by the navigation actions (such as left, right, center) to determine the next position to place the selection from a given position. NavigationFilter will be 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 currently defined in View, for consistancy, 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 having a DocumentFilter and impose a navigation policy by having 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 will invoke install. AbstractFormatter.install will do the following:

  1. Set 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. Install the DocumentFilter returned from getDocumentFilter onto the JFormattedTextField's Document.
  3. Install the NavigationFilter returned from getNavigationFilter onto the JFormattedTextField.
  4. Install 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 will allow the JFormattedTextField to contain an invalid value when editing. To allow the JFormattedTextField to provide indication of this, the AbstractFormatter should invoke setEditValid(false) when the user enters a bogus value, and then invoke setEditValid(true) when the value is valid again.

When JFormattedTextField is done with an AbstractFormatter, it will invoke uninstall. uninstall will remove the previously installed Listeners.

JFormattedTextField.AbstractFormatterFactory

JFormattedTextField will delegate the creation of AbstractFormatters to an instance of AbstractFormatterFactory (a public static inner class of JFormattedTextField). This will make it trivial for developers to provide different formatters for different states of the JFormattedTextField. For example, you could provide a different AbstractFormatter if the current value was null, or a different formatter when editing vs 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 will be the superclass for all the formatter implementations we provide. DefaultFormatter formats Objects using toString and creates the Object using the constructor that takes a String. DefaultFormatter will allow a number of various configuration options:

Option Description
CommitsOnValidEdit Sets when edits are published back to the JFormattedTextField. If true, commitEdit is invoked on the JFormattedTextField after every succesful edit, otherwise commitEdit will only be invoked 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 Sets whether or not 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 will 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 makes extensive use of the Format classes in the java.text package. The following problems were encountered in using the existing api:

These issues have largely been addressed by adding the following method to java.text.Format:

    /**
     * Formats an Object producing an AttributedCharacterIterator.
     * You can use the returned AttributedCharacterIterator
     * to build the resulting String, as well as to determine information
     * about the resulting String.
     * 

* Each attribute key of the AttributedCharacterIterator will be of type * Field. It is up to each Format implementation * to define what the legal values are for each attribute in the * AttributedCharacterIterator, but typically the attribute * key is also used as the attribute value. *

The default implementation creates an * AttributedCharacterIterator with no attributes. Subclasses * that support fields should override this and create an * AttributedCharacterIterator with meaningful attributes. * * @exception NullPointerException if obj is null. * @exception IllegalArgumentException when the Format cannot format the * given object. * @param obj The object to format * @return AttributedCharacterIterator describing the formatted value. * @since 1.4 */ public AttributedCharacterIterator formatToCharacterIterator(Object obj);

And then having each Format class use a type safe enumeration for the constants it supports.

JavaDoc

The following classes are being proposed:


Last modified: Wed Dec 13 14:01:59 PST 2000