Channels ▼
RSS

Tools

Java: Better Interfaces via JFormattedTextField


Custom Formatters

If you have a data type that is variable in length and does not fit well with one of the existing formatter classes, or requires special rules for conversion, then you can create your own formatter class based on the DefaultFormatter class or any of the other Formatter classes (e.g. NumberFormatter). You will need to override the stringToValue and valueToString methods to provide the behavior you desire. stringToValue accepts a String as an input, parses it, and returns an Object. If the parsing is fails, then a ParseException should be thrown. The type of Object returned should match the results of getValueClass. The code necessary to parse the String will depend on the data you are parsing. If you are parsing a proper name, then your method may just check to see that the String is not empty and force the first letter to be capitalized. If you are parsing a URL or email address, then you'll want to employ a Pattern object to test the validity and sequence of the characters.

valueToString accepts an Object and converts it to a String. If the conversion fails, then a ParseException is thrown. The conversion may be as simple as calling the toString() method of the Object or the conversion may adjust the data before returning it. For example, a field that holds a percentage may want to store 5.3% as .053, but display it as 5.3%. In this case the valueToString method would multiply the value by 100, format it with the desired number of decimal places, then tag on the percent sign.

The EmailFormatter class extends the DefaultFormatter class and uses a precompiled pattern to check an email address.For more information on the email format and pattern matcher, see "How to Validate Email, SSN, Phone Numbers in Java Using Regular Expressions" by Zaheer Paracha.

The code below creates a JFormattedTextField for entering an email address using EmailFormatter.java class which you can download here.)


 JFormattedTextField emailField = new JFormattedTextField();
 EmailFormatter emailFormatter = new EmailFormatter();
 DefaultFormatterFactory emailFormatterFactory = 
                new DefaultFormatterFactory(emailFormatter);
 emailField.setFormatterFactory(emailFormatterFactory);

If you want a custom formatter that handles numbers, then you should create a class that extends NumberFormatter. The PercentFormatter class extends NumberFormatter to create a class to handle percentage data. The constructor accepts a NumberFormat object to handle formatting and parsing. The valueToString method first multiplies the value by 100, then calls super class's valueToString method, finally a percent is added and the String returned. The stringToValue first strips off any extra white space or percent signs, then converts to a double using the super class's stringToValue method, divides by 100, then returns a Double object. In this way the value is stored in a form amenable to using in calculations, but displayed in a manner the user expects.

The code below creates a JFormattedTextField for entering an percent using PercentFormatter.java which you can download here.


    JFormattedTextField percentField = new JFormattedTextField();
    PercentFormatter percentFormatter = 
               new PercentFormatter(new DecimalFormat("#.##"));
    DefaultFormatterFactory percentFormatterFactory = 
               new DefaultFormatterFactory(percentFormatter);
    percentField.setFormatterFactory(percentFormatterFactory);

InputVerifiers

By default, when the focus is lost on a JFormattedTextField the rules for COMMIT_OR_REVERT are followed. That is, if the text typed in is legal, the value is updated and the new value is displayed using the display formatter; if the text is not legal, then the text being edited is discarded and the old value is displayed. A JFormattedTextField can also be set to use the rules for COMMIT, under those rules when an invalid string is entered, it is left displayed, but the value is not changed. When this happens the text shown on the screen does not match the value stored in the JFormattedTextField. So it seems the choice is to lose the edit or have a mismatch in values. Neither of these options is desirable. To correct this, all you need to do is to attach an InputVerifier to the JFormattedTextField. An InputVerifier provides the capability to force the control to retain the focus if it has an invalid value. Further, the formatters attached to a JFormattedTextField usually only check the format of the data; an InputVerifier can perform range checks and other validity checks as well.

An InputVerifier is a class that inherits the InputVerifier class. These classes will contain a constructor and must define the body of an abstract method named "verify". This method is called whenever the focus is about to be lost on a control. If verify returns true, this means all is well and the focus can be moved. If verify returns false, then the focus remains on the control. The verify method accepts a JComponent as input (the JFormattedTextField) and then can do whatever manipulations it needs before returning true or false. The example below is a reusable InputVerifier to handle double values.

The data fields define the minimum and maximum permissible values. The constructor and the included mutator methods can set these. Two Color values are also present, one for invalid values, the other for valid values. The verify method first retrieves the text from the JComponent and tries to parse it to a double. If an exception is thrown, then the foreground color is set to INVALID_COLOR and false is returned. If an exception is not thrown, then the value is tested to see if is in a valid range, if it is, the foreground color is set to the VALID_COLOR and true is returned. If the value is out of range then the foreground color is set to INVALID_COLOR and false is returned. (Also see DoubleVerifier.java which you can download here.)

To use this InputVerifier, first create an instance of the verifier, then attach it to the JFormattedTextField using the setInputVerifier method.


  JFormattedTextField salaryField = new JFormattedTextField();
 DoubleVerifier salaryVerifier = new DoubleVerifier(0,1000000);    
 salaryField.setInputVerifier(salaryVerifier);  // attach the verifier

Putting It All Together

Creating a JFormattedTextField that does it all requires you to do the following steps:

  1. Create an instance of a formatter for display mode (when the control does not have the focus)
  2. Create an instance formatter for edit mode (when the control does have the focus)
  3. Create an instance DefaultFormatterFactory based on the two formatters
  4. Attach the DefaultFormatterFactory to the JFormattedTextField using the setFormatterFactory method
  5. Create an instance of a InputVerifier
  6. Attach the InputVerifier to the JFormattedTextField using the setInputVerifier method


// create the JFormattedTextField
   JFormattedTextField salaryField = new JFormattedTextField();  
// create the formatters, default, display, edit
   NumberFormatter defaultFormatter = new NumberFormatter(new DecimalFormat("#.##"));
   NumberFormatter displayFormatter = 
                 new NumberFormatter(new DecimalFormat("$ #,###.00"));
   NumberFormatter editFormatter = new NumberFormatter(new DecimalFormat("#.##")); 
// set their value classes
   defaultFormatter.setValueClass(Double.class);
   displayFormatter.setValueClass(Double.class);
   editFormatter.setValueClass(Double.class);   
// create and set the DefaultFormatterFactory
   DefaultFormatterFactory salaryFactory = 
       new DefaultFormatterFactory(defaultFormatter,displayFormatter,editFormatter);
   salaryField.setFormatterFactory(salaryFactory);
// create and attach an InputVerifier
  DoubleVerifier salaryVerifier = new DoubleVerifier(0,1000000);// salaries 0 to 1 mil 
  salaryField.setInputVerifier(salaryVerifier);  // attach the verifier

As you can see, this is quite a bit of code to create a fancy equivalent of a JTextField. Doubtless this is one of the reasons why the JFormattedTextField is not covered in many Java texts or used as frequently by programmers. Fortunately, object-oriented programming can come to your aid. Instead of repeating the sequence of code for every field in every program, just create a few reusable classes for common data types. Adding PropertySupport to the classes makes them easy to integrate into the Netbeans GUI designer palette, allowing you to add them to your program graphically like any other control.

The code project attached to this article (and available for download here) provides examples via the following reusable classes:

  • JFormattedTextFields
    • FormattedCurrencyField
    • FormattedDateField
    • FormattedDoubleField
    • FormattedEmailField
    • FormattedIntegerField
    • FormattedPercentField
    • FormattedPhoneField
    • FormattedSSNField

  • Custom verifiers
    • DateVerifier
    • DoubleVerifier
    • EmailVerifier
    • IntegerVerifier
    • MaskVerifier (used by JFormattedPhoneField and JFormattedSSNField)
    • PercentVerifier

  • Custom formatters
    • EmailFormatter
    • PercentFormatter

Figure 2 shows the use of these classes. I will cover FormattedDoubleField and FormattedDateField as examples:


Figure 2: Sample application

FormattedDoubleField extends JFormattedTextField. (FormattedDoubleField.java can be downloaded here.) It has properties for the number of decimal places to display (decimalPlaces), the minimum and maximum valid values (minValue, maxValue). Four constructors are provided, a no-arg constructor that uses the default values, a constructor that accepts the number of decimal places, a constructor that accepts the min and max values, and a constructor that accepts the min value, max value, the current value, and number of decimal places. Accessor and mutator methods are provided to change the maxValue, minValue, and decimalPlaces.

The constructor first checks the min and max values, generating an IllegalArgumentException if min is greater than max. A DoubleVerfier (discussed previously) is then created and attached using the setInputVerifier method. Three NumberFormatters are created. The display and edit formatters are created based on the DecimalFormat class. Display includes the grouping character while the edit does not. These formatters are then used to create a DefaultFormatterFactory, which in turn is attached using the setFormatterFactory method. PropertyChange support is provided with the propertySupport variable and implemented using calls to the firePropertyChange method in the mutator methods. The FormattedDoubleField class can be compiled in NetBeans and added to the GUI palette using the Palette Manager.

Program code that uses the FormattedDoubleField just needs to call the getValue and setValue methods. getValue will return an Object that can be cast to a Double, setValue assumes a Double object will be given. A simple adder using three FormattedDoubleFields -- xField, yField, zField -- would look like:


    Double x = (Double)xField.getValue();
    Double y = (Double)yField.getValue();   
    zField.setValue(new Double(x+y));

There is no need to invoke Double.parseDouble or try/catch NumberFormat exceptions. That is all provided along with range checking of the inputs. Implementing similar code using a JTextField would look like:


try{
  double x = Double.parseDouble(xField.getText());
  if(x < min || x > max) {
    JOptionPane.showMessageDialog(this, "invalid value for x");
    return;
  }
}
catch(NumberFormatException exX){
   JOptionPane.showMessageDialog(this, "invalid value for x");
   return;
}

try{
   double y = Double.parseDouble(yField.getText());
   if(y < min || y > max) {
     JOptionPane.showMessageDialog(this, "invalid value for y");
     return;
   }
}
catch(NumberFormatException exY){
   JOptionPane.showMessageDialog(this, "invalid value for y ");
   return;
}

DecimalFormat formatter = new DecimalFormat("#,###.##");
double z = x+y;
xField.setText(formatter.format(z));

FormattedDateField extends JFormattedTextField. (FormattedDateField.java can be downloaded here.) It provides several constructors, a default one that uses the current date, one that accepts a GregorianCalendar object, one that accepts the month, day, year, hour, minute, second as integers, and one that accepts a Date object. The principle constructor first sets the value of the JFormattedTextField to the specified date and time using a SimpleDateFormat object to convert it to a String in the proper format. It then creates a DateVerifier and attaches it using a call to setInputVerifier. A MaskFormatter is created using "##/##/#### ##:##:## UM" as the format string. Recall that the #'s are masks for digits, the U for an uppercase character, and the slashes, spaces, colon, and M are literals. This creates a formatter that allows the user to only change the certain characters leaving the literals in place for guidance. The valid characters for the MaskFormatter are set to the digits 0 to 9 and the characters A and P. Thus the U placeholder can only be replaced with an A or P. A DefaultFormatterFactory is then created with the MaskFormatter and attached using the setFormatterFactory method. A DateFormatter could have been created using the SimpleDateFormat class and used in place of the MaskFormatter. However, the DateFormatter allows editing of the entire text and does not "lock down" literal characters. Giving less guidance for the user and leaving a greater chance for error.

Since a MaskFormatter is being used, the getValue and setValue methods associated with the JFormattedTextField class will return a String. Separate getDate and setDate methods were added to provide support for Date objects. The getDate method uses a SimpleDateFormat object to parse the text value into a Date object; setDate uses the same SimpleDateFormat object to format a Date into a String. A Timer object was added to allow the field to automatically update itself. A boolean variable "ticking" can be turned on/off to control the Timer.

Program code that accesses a FormattedDateField can call the getDate or getValue to retrieve the contents of the field.


Date date1 = dateField.getDate();
String date2 = (String)dateField.getValue();

Conclusion

This article has demonstrated how to use JFormattedTextFields for numeric data, fixed size string data, and custom data. Using JFormattedTextFields in place of JTextFields requires extra upfront coding for initializing the interface, but saves code when accessing and processing the data. JFormattedTextFields also provide a more fool-proof interface and better guidance to the user. Coding efficiency and reusability is further enhanced if custom classes based on JFormattedTextField are created for generic data types.


Mark Pendergast is an Associate Professor of Computer Information System at Florida Gulf Coast University.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video