Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 19. (Advanced topics) Inside Text Components. Easy for reading, Click here!

Custom Search
Swing Chapter 19. (Advanced topics) Inside Text Components. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 2/2 



Previous Page Previous Page (1/2)
Subpages: 1. Text package overview
2. Best Date and time editor

19.2  Date and time editor

...by David M. Karr of Best Consulting and TCSI Corporation

The DateTimeEditor class is a panel containing a text field that allows display and editing of a date, time, or date/time value. It doesn't use direct entry of text, but uses the Up and Down arrow keys or mouse clicks on "spinner" buttons to increment and decrement field values (e.g. day, month, year, or hour, minute, second). The mouse can also be used to select particular subfields. The Left and Right arrow keys move the caret between fields.

This class is designed to be internationalized, although it assumes some conventions, such as a left-to-right reading direction. It doesn't have any locale-specific code, it just uses the locale framework integrated into Java 2. If the VM used doesn't support a particular locale, neither will this component. The Locale class encapsulates a "language", "country", and optional "variant". Each of these are strings. The possible values of "language" and "country" are defined in the ISO 639 and ISO 3166 standards, respectively. The variants are not standardized. For instance, the language codes for English, French, Chinese, and Japanese are "en", "fr", "zh", and "ja". The country codes for the USA, France, and Canada are "US", "FR", and "CA".

The current Locale setting is used to qualify the variety of resource class or properties file to obtain. For instance, a  class name with a suffix of "_fr_FR" indicates resources for french in France. The suffix of "_fr_CA" indicates resources for french in Canada.

Java 2 has specific resource settings for most of the known locales, including currency formats, date formats, number formats, common text strings, etcetera. This is the information that the DateTimeEditor class uses indirectly, without having to manually encode locale-specific information.

DateTimeEditor uses several Swing classes including: JTextField, Keymap, AbstractAction, TextAction, and Caret. It also uses several non-Swing classes including: Collections, Calendar (both in package java.util), FieldPosition, and DateFormat (both in package java.text). The Collections class is used to sort a list of FieldPosition objects by the beginIndex of each FieldPosition. A custom Spinner class, described below, is used to allow incrementing or decrementing of values with the mouse.

DateTimeEditor's text field is an ordinary JTextField, and it uses the methods of JTextComponent to communicate with and manipulate its Caret.

The inner classes UpDownAction, BackwardAction, ForwardAction, BeginAction, and EndAction are subclasses of TextAction and AbstractAction, and are used to handle the arrow keys, and the Home and End keys. All of these inner classes are used in concert with the Keymap class to combine key definitions with action definitions.

The DateTimeEditor text field listens for caret state changes. It does this so it knows exactly which field the caret is in, and also to constrain the caret position to always be at the beginning of the current field.

The most interesting interactions are with DateFormat's fields and its format() method. What DateTimeEditor gains from this is the ability to know what field the caret is in, so it knows how to interpret the "increment" and "decrement" actions.

One of DateFormat's format() methods takes a Date value, a StringBuffer to write the stringified result into, and a single FieldPosition object. This last parameter is the key to the entire DateTimeEditor component. The format() method will update the given FieldPosition object with the begin and end offset for that field in the given date/time string. The DateTimeEditor has a hardcoded list of all fields from DateFormat. In a loop, it plugs in each of those constants into the format function, and then stores the resulting FieldPosition object. It then uses the Collections.sort() method to sort the list of FieldPositions by the beginning index of each. Using this sorted list and a given caret position, we can easily determine what field the caret resides in.

The Calendar class is used to fill in some functionality that the Date.setTime() method doesn't provide. In  particular, in the code which increments or decrements the current field value, there are four DateFormat fields which cannot be set using Date.setTime(). Those fields are MONTH, WEEK_OF_MONTH, WEEK_OF_YEAR, and YEAR. For these fields, DateTimeEditor manipulates a Calendar instance and then calls Calendar.getTime() to get a new Date value.

Figure 19.5 DateTimeEditor in the en_US locale

<<file figure19-5.gif>>

Figure 19.6 Spinner

<<file figure19-6.gif>>

The Code: DateTimeEditor.java

see \Chapter19\Karr

import java.awt.event.*;

import java.text.*;

import java.util.*;

import java.awt.*;

import javax.swing.*;

import javax.swing.border.*;

import javax.swing.text.*;

import javax.swing.event.*;

public class DateTimeEditor extends JPanel {

  public static final long ONE_SECOND = 1000;

  public static final long ONE_MINUTE = 60*ONE_SECOND;

  public static final long ONE_HOUR = 60*ONE_MINUTE;

  public static final long ONE_DAY = 24*ONE_HOUR;

  public static final long ONE_WEEK = 7*ONE_DAY;

  public final static int TIME = 0;

  public final static int DATE = 1;

  public final static int DATETIME = 2;

  private int m_timeOrDateType;

  private int m_lengthStyle;

  private DateFormat m_format;

  private Calendar m_calendar = Calendar.getInstance();

  private ArrayList m_fieldPositions = new ArrayList();

  private Date m_lastDate = new Date();

  private Caret m_caret;

  private int m_curField = -1;

  private JTextField m_textField;

  private Spinner m_spinner;

  private AbstractAction m_upAction =

    new UpDownAction(1, "up");

  private AbstractAction m_downAction =

    new UpDownAction(-1, "down");

  private int[] m_fieldTypes = {

    DateFormat.ERA_FIELD,

    DateFormat.YEAR_FIELD,

    DateFormat.MONTH_FIELD,

    DateFormat.DATE_FIELD,

    DateFormat.HOUR_OF_DAY1_FIELD,

    DateFormat.HOUR_OF_DAY0_FIELD,

    DateFormat.MINUTE_FIELD,

    DateFormat.SECOND_FIELD,

    DateFormat.MILLISECOND_FIELD,

    DateFormat.DAY_OF_WEEK_FIELD,

    DateFormat.DAY_OF_YEAR_FIELD,

    DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,

    DateFormat.WEEK_OF_YEAR_FIELD,

    DateFormat.WEEK_OF_MONTH_FIELD,

    DateFormat.AM_PM_FIELD,

    DateFormat.HOUR1_FIELD,

    DateFormat.HOUR0_FIELD

  };

  public DateTimeEditor() {

    m_timeOrDateType = DATETIME;

    m_lengthStyle = DateFormat.SHORT;

    init();

  }

  public DateTimeEditor(int timeOrDateType) {

    m_timeOrDateType = timeOrDateType;

    m_lengthStyle = DateFormat.FULL;

    init();

  }

  public DateTimeEditor(int timeOrDateType, int lengthStyle) {

    m_timeOrDateType = timeOrDateType;

    m_lengthStyle = lengthStyle;

    init();

  }

  private void init() {

    setLayout(new BorderLayout());

    m_textField = new JTextField();

    m_spinner = new Spinner();

    m_spinner.getIncrementButton().addActionListener(m_upAction);

    m_spinner.getDecrementButton().addActionListener(m_downAction);

    add(m_textField, "Center");

    add(m_spinner, "East");

    m_caret = m_textField.getCaret();

    m_caret.addChangeListener(new ChangeListener() {

      public void stateChanged(ChangeEvent evt) {

        setCurField();

      }

    });

    setupKeymap();

    reinit();

  }

  public int getTimeOrDateType() { return m_timeOrDateType; }

  public void setTimeOrDateType(int timeOrDateType) {

    m_timeOrDateType = timeOrDateType;

    reinit();

  }

  public int getLengthStyle() { return m_lengthStyle; }

  public void setLengthStyle(int lengthStyle) {

    m_lengthStyle = lengthStyle;

    reinit();

  }

  public Date getDate() { return (m_lastDate); }

  public void setDate(Date date) {

    m_lastDate = date;

    m_calendar.setTime(m_lastDate);

    m_textField.setText(m_format.format(m_lastDate));

    getFieldPositions();

  }

  private int getFieldBeginIndex(int fieldNum) {

    int beginIndex = -1;

    for (Iterator iter = m_fieldPositions.iterator();

     iter.hasNext(); )

    {

      FieldPosition fieldPos = (FieldPosition) iter.next();

      if (fieldPos.getField() == fieldNum) {

        beginIndex = fieldPos.getBeginIndex();

        break;

      }

    }

    return (beginIndex);

  }

  private FieldPosition getFieldPosition(int fieldNum) {

    FieldPosition result = null;

    for (Iterator iter = m_fieldPositions.iterator();

     iter.hasNext(); )

    {

      FieldPosition fieldPosition = (FieldPosition) iter.next();

      if (fieldPosition.getField() == fieldNum) {

        result = fieldPosition;

        break;

      }

    }

    return (result);

  }

  private void reinit() {

    setupFormat();

    setDate(m_lastDate);

    m_caret.setDot(0);

    setCurField();

    repaint();

  }

  protected void setupFormat() {

    switch (m_timeOrDateType) {

      case TIME:

        m_format = DateFormat.getTimeInstance(m_lengthStyle);

        break;

      case DATE:

        m_format = DateFormat.getDateInstance(m_lengthStyle);

        break;

      case DATETIME:

        m_format = DateFormat.getDateTimeInstance(m_lengthStyle,

        m_lengthStyle);

        break;

    }

  }

  protected class UpDownAction extends AbstractAction

  {

    int m_direction; // +1 = up; -1 = down

    public UpDownAction(int direction, String name) {

      super(name);

      m_direction = direction;

    }

    public void actionPerformed(ActionEvent evt) {

      if (!this.isEnabled())

        return;

      boolean dateSet  = true;

      switch (m_curField) {

        case DateFormat.AM_PM_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * 12*ONE_HOUR));

          break;

        case DateFormat.DATE_FIELD:

        case DateFormat.DAY_OF_WEEK_FIELD:

        case DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD:

        case DateFormat.DAY_OF_YEAR_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * ONE_DAY));

          break;

        case DateFormat.ERA_FIELD:

          dateSet = false;

          break;

        case DateFormat.HOUR0_FIELD:

        case DateFormat.HOUR1_FIELD:

        case DateFormat.HOUR_OF_DAY0_FIELD:

        case DateFormat.HOUR_OF_DAY1_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * ONE_HOUR));

          break;

        case DateFormat.MILLISECOND_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * 1));

          break;

        case DateFormat.MINUTE_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * ONE_MINUTE));

          break;

        case DateFormat.MONTH_FIELD:

          m_calendar.set(Calendar.MONTH,

            m_calendar.get(Calendar.MONTH) + m_direction);

          m_lastDate = m_calendar.getTime();

          break;

        case DateFormat.SECOND_FIELD:

          m_lastDate.setTime(m_lastDate.getTime() +

            (m_direction * ONE_SECOND));

          break;

        case DateFormat.WEEK_OF_MONTH_FIELD:

          m_calendar.set(Calendar.WEEK_OF_MONTH,

            m_calendar.get(Calendar.WEEK_OF_MONTH) +

            m_direction);

          m_lastDate = m_calendar.getTime();

          break;

        case DateFormat.WEEK_OF_YEAR_FIELD:

          m_calendar.set(Calendar.WEEK_OF_MONTH,

            m_calendar.get(Calendar.WEEK_OF_MONTH) +

            m_direction);

          m_lastDate = m_calendar.getTime();

          break;

        case DateFormat.YEAR_FIELD:

          m_calendar.set(Calendar.YEAR,

            m_calendar.get(Calendar.YEAR) + m_direction);

          m_lastDate = m_calendar.getTime();

          break;

        default:

          dateSet = false;

      }

      if (dateSet) {
        int fieldId = m_curField;
        setDate(m_lastDate); 
        FieldPosition fieldPosition = getFieldPosition(fieldId);
        m_caret.setDot(fieldPosition.getBeginIndex());

        m_textField.requestFocus();
        repaint();
      }

    }

  }

  protected class BackwardAction extends TextAction

  {

    BackwardAction(String name) { super(name); }

    public void actionPerformed(ActionEvent e) {

      JTextComponent target = getTextComponent(e);

      if (target != null) {

        int dot = target.getCaretPosition();

        if (dot > 0) {

          FieldPosition position = getPrevField(dot);

          if (position != null)

            target.setCaretPosition(

              position.getBeginIndex());

          else {

            position = getFirstField();

            if (position != null)

              target.setCaretPosition(

                position.getBeginIndex());

          }

        }

        else

          target.getToolkit().beep();

        target.getCaret().setMagicCaretPosition(null);

      }

    }

  }

  protected class ForwardAction extends TextAction

  {

    ForwardAction(String name) { super(name); }

    public void actionPerformed(ActionEvent e) {

      JTextComponent target = getTextComponent(e);

      if (target != null) {

        FieldPosition position = getNextField(

          target.getCaretPosition());

        if (position != null)

          target.setCaretPosition(position.getBeginIndex());

        else {

          position = getLastField();

          if (position != null)

            target.setCaretPosition(

              position.getBeginIndex());

        }

        target.getCaret().setMagicCaretPosition(null);

      }

    }

  }

  protected class BeginAction extends TextAction

  {

    BeginAction(String name) { super(name); }

    public void actionPerformed(ActionEvent e) {

      JTextComponent target = getTextComponent(e);

      if (target != null) {

        FieldPosition position = getFirstField();

        if (position != null)

          target.setCaretPosition(position.getBeginIndex());

      }

    }

  }

  protected class EndAction extends TextAction

  {

    EndAction(String name) { super(name); }

    public void actionPerformed(ActionEvent e) {

      JTextComponent target = getTextComponent(e);

      if (target != null) {

        FieldPosition position = getLastField();

        if (position != null)

          target.setCaretPosition(position.getBeginIndex());

      }

    }

  }

  protected void setupKeymap() {

    Keymap keymap = m_textField.addKeymap("DateTimeKeymap", null);

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_UP, 0), m_upAction);

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_DOWN, 0), m_downAction);

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_LEFT, 0), new BackwardAction(DefaultEditorKit.

      backwardAction));

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_RIGHT, 0), new ForwardAction(DefaultEditorKit.

      forwardAction));

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_HOME, 0), new BeginAction(DefaultEditorKit.

      beginAction));

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(

      KeyEvent.VK_END, 0), new EndAction(DefaultEditorKit.

      endAction));

    m_textField.setKeymap(keymap);

  }

  private void getFieldPositions() {

    m_fieldPositions.clear();

    for (int ctr = 0; ctr < m_fieldTypes.length; ++ ctr) {

      int fieldId = m_fieldTypes[ctr];

      FieldPosition fieldPosition = new FieldPosition(fieldId);

      StringBuffer formattedField = new StringBuffer();

      m_format.format(m_lastDate, formattedField, fieldPosition);

      if (fieldPosition.getEndIndex() > 0)

        m_fieldPositions.add(fieldPosition);

    }

    m_fieldPositions.trimToSize();

    Collections.sort(m_fieldPositions,

      new Comparator() {

        public int compare(Object o1, Object o2) {

          return (((FieldPosition) o1).getBeginIndex() -

           ((FieldPosition) o2).getBeginIndex());

        }

      }

    );

  }

  private FieldPosition getField(int caretLoc) {

    FieldPosition fieldPosition = null;

    for (Iterator iter = m_fieldPositions.iterator();

     iter.hasNext(); )

    {

      FieldPosition chkFieldPosition =

        (FieldPosition) iter.next();

      if ((chkFieldPosition.getBeginIndex() <= caretLoc) &&

       (chkFieldPosition.getEndIndex() > caretLoc))

      {

        fieldPosition = chkFieldPosition;

        break;

      }

    }

    return (fieldPosition);

  }

  private FieldPosition getPrevField(int caretLoc) {

    FieldPosition fieldPosition = null;

    for (int ctr = m_fieldPositions.size() - 1; ctr > -1; -- ctr) {

      FieldPosition chkFieldPosition =

        (FieldPosition) m_fieldPositions.get(ctr);

      if (chkFieldPosition.getEndIndex() <= caretLoc) {

        fieldPosition = chkFieldPosition;

        break;

      }

    }

    return (fieldPosition);

  }

  private FieldPosition getNextField(int caretLoc) {

    FieldPosition  fieldPosition = null;

    for (Iterator iter = m_fieldPositions.iterator();

     iter.hasNext(); )

    {

      FieldPosition chkFieldPosition =

        (FieldPosition) iter.next();

      if (chkFieldPosition.getBeginIndex() > caretLoc) {

        fieldPosition = chkFieldPosition;

        break;

      }

    }

    return (fieldPosition);

  }

  private FieldPosition getFirstField() {

    FieldPosition result = null;

    try { result = ((FieldPosition) m_fieldPositions.get(0)); }

    catch (NoSuchElementException ex) {}

    return (result);

  }

  private FieldPosition getLastField() {

    FieldPosition result = null;

    try {

      result =

        ((FieldPosition) m_fieldPositions.get(

          m_fieldPositions.size() - 1));

    }

    catch (NoSuchElementException ex) {}

    return (result);

  }

  private void setCurField() {

    FieldPosition fieldPosition = getField(m_caret.getDot());

    if (fieldPosition != null) {

      if (m_caret.getDot() != fieldPosition.getBeginIndex())

        m_caret.setDot(fieldPosition.getBeginIndex());

    }

    else {

      fieldPosition = getPrevField(m_caret.getDot());

      if (fieldPosition != null)

        m_caret.setDot(fieldPosition.getBeginIndex());

      else {

        fieldPosition = getFirstField();

        if (fieldPosition != null)

          m_caret.setDot(fieldPosition.getBeginIndex());

      }

    }

    if (fieldPosition != null)

      m_curField = fieldPosition.getField();

    else

      m_curField = -1;

  }

  public void setEnabled(boolean enable) {

    m_textField.setEnabled(enable);

    m_spinner.setEnabled(enable);

  }

  public boolean isEnabled() {

    return (m_textField.isEnabled() && m_spinner.isEnabled());

  }

  public static void main (String[] args) {

    JFrame frame = new JFrame();

    frame.addWindowListener(new WindowAdapter() {

      public void windowClosing(WindowEvent evt)

      { System.exit(0); }

    });

    JPanel panel = new JPanel(new BorderLayout());

    panel.setBorder(new EmptyBorder(5, 5, 5, 5));

    frame.setContentPane(panel);

    final DateTimeEditor field =

      new DateTimeEditor(DateTimeEditor.DATETIME,

      DateFormat.FULL);

    panel.add(field, "North");

    JPanel buttonBox = new JPanel(new GridLayout(2, 2));

    JButton showDateButton = new JButton("Show Date");

    buttonBox.add(showDateButton);

    final JComboBox timeDateChoice = new JComboBox();

    timeDateChoice.addItem("Time");

    timeDateChoice.addItem("Date");

    timeDateChoice.addItem("Date/Time");

    timeDateChoice.setSelectedIndex(2);

    timeDateChoice.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent evt) {

        field.setTimeOrDateType(timeDateChoice.

        getSelectedIndex());

      }

    });

    buttonBox.add(timeDateChoice);

    JButton toggleButton = new JButton("Toggle Enable");

    buttonBox.add(toggleButton);

    showDateButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent evt)

      { System.out.println(field.getDate()); }

    });

    toggleButton.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent evt)

      { field.setEnabled(!field.isEnabled());}

    });

    panel.add(buttonBox, "South");

    final JComboBox lengthStyleChoice = new JComboBox();

    lengthStyleChoice.addItem("Full");

    lengthStyleChoice.addItem("Long");

    lengthStyleChoice.addItem("Medium");

    lengthStyleChoice.addItem("Short");

    lengthStyleChoice.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent evt) {

        field.setLengthStyle(lengthStyleChoice.

          getSelectedIndex());

      }

    });

    buttonBox.add(lengthStyleChoice);

    frame.pack();

    Dimension dim = frame.getToolkit().getScreenSize();

    frame.setLocation(dim.width/2 - frame.getWidth()/2,

      dim.height/2 - frame.getHeight()/2);

    frame.show();

  }

}

The Code: Spinner.java

see \Chapter19\Karr

import java.util.*;

import java.lang.reflect.*;

import java.awt.*;

import javax.swing.*;

import javax.swing.plaf.*;

import javax.swing.plaf.basic.*;

public class Spinner extends JPanel

{

  private int m_orientation = SwingConstants.VERTICAL;

  private BasicArrowButton m_incrementButton;

  private BasicArrowButton m_decrementButton;

  public Spinner() { createComponents(); }

  public Spinner(int orientation) {

    m_orientation = orientation;

    createComponents();

  }

  public void setEnabled(boolean enable) {

    m_incrementButton.setEnabled(enable);

    m_decrementButton.setEnabled(enable);

  }

  public boolean isEnabled() {

    return (m_incrementButton.isEnabled() &&

      m_decrementButton.isEnabled());

  }

  protected void createComponents() {

    if (m_orientation == SwingConstants.VERTICAL) {

      setLayout(new GridLayout(2, 1));

      m_incrementButton = new BasicArrowButton(

        SwingConstants.NORTH);

      m_decrementButton = new BasicArrowButton(

        SwingConstants.SOUTH);

      add(m_incrementButton);

      add(m_decrementButton);

    }

    else if (m_orientation == SwingConstants.HORIZONTAL) {

      setLayout(new GridLayout(1, 2));

      m_incrementButton = new BasicArrowButton(

        SwingConstants.EAST);

      m_decrementButton = new BasicArrowButton(

        SwingConstants.WEST);

      add(m_decrementButton);

      add(m_incrementButton);

    }

  }

  public JButton getIncrementButton() {

    return (m_incrementButton); }

  public JButton getDecrementButton() {

    return (m_decrementButton); }

  public static void main(String[] args) {

    JFrame frame = new JFrame();

    JPanel panel = (JPanel) frame.getContentPane();

    panel.setLayout(new BorderLayout());

    JTextField  field = new JTextField(20);

    Spinner spinner = new Spinner();

    panel.add(field, "Center");

    panel.add(spinner, "East");

    Dimension dim = frame.getToolkit().getScreenSize();

    frame.setLocation(dim.width/2 - frame.getWidth()/2,

      dim.height/2 - frame.getHeight()/2);

    frame.pack();

    frame.show();

  }

}

Understanding the Code:

Class DateTimeEditor

The m_fieldTypes array contains all of the field alignment constants defined in the DateFormat class. These are all of the pieces of a time or date value that we should expect to see. The order in this list is not important. Each value is plugged into DateFormat.format() to determine where each field is in the stringified date/time value.

The default constructor makes the field display date and time, in a SHORT format, which the DateFormat class describes as "completely numeric", such as "12.13.52" or "3:30pm". The second constructor can specify whether the field will display time, date, or date and time. In addition, it sets it into the FULL format, which the DateFormat class describes as "pretty completely specified", such as "Tuesday, April 12, 1952 AD" or "3:30:42pm PST". The third constructor can specify the time/date type, and the length style, being SHORT, MEDIUM, LONG, or FULL (fields in DateFormat).

Each of the constructors calls a common init() method, which initializes the caret, registers a ChangeListener on the caret (to update which field the caret is in), sets up the keymap (up, down, left, and right arrow keys), and calls the reinit() method which does some additional initialization (this method can be called any time, not just during initial construction).

The setupKeymap() method defines the keymap for the Up, Down, Left, and Right arrow keys. It first adds a new keymap with a null parent, so that no other keymaps will be used. It associates Actions with the key strokes we want to allow. Then the setKeymap() method is called to assign this keymap to our text field.

Each time a new date is set, either at initialization or by changing one of the field values, the getFieldPositions() method is called. This method uses the DateFormat.format() method, plugging in the Date value, and each one of the DateFormat fields. A new FieldPosition object is set which specifies the beginning and end indices for each field of the given date. All of the resulting FieldPosition objects are stored into the m_fieldPositions list, and sorted using the beginning index (using the Collections class). It is sorted in this fashion to make it easy to determine the field associated with a particular caret location. The BackwardAction and ForwardAction classes (see below) use this sorted list to quickly move to the previous or next date/time value.

After the m_fieldPositions list is set, several methods search that list, either directly or indirectly, to move to a particular field, or find out what the current field is. The getField(), getPrevField(), and getNextField() methods all take a caret location and return the current, previous, or next field, respectively. The getFirstField() and getLastField() methods return the first and last fields, respectively. And finally, the setCurField() method gets the field the caret is in and adjusts the caret to lie at the beginning of the field. This method is used when a new date is set, or the user uses the mouse to set the caret location.

The setEnabled() and isEnabled() methods allow the component to be disabled or enabled, and to check on the enabled status of the component (which includes both the text field and the custom spinner).

The main() method of this class is used as a demonstration of its capabilities. It presents a DateTimeEditor, a "Show Date" button, and a "Toggle Enable" button. When the "Show Date" button is pressed, it prints the current date value shown in the field to standard output. (The string printed is always in the "english US" locale, irrespective of the current locale being used to display the DateTimeEditor.) When the "Toggle Enable" button is pressed, it will toggle the enabled status of the component which grays out the text field and the spinner buttons when disabled.

As of the first official Java 2 public release there is a bug in the area of distribution of key events. In the method setupKeymap(), we specifically limit the keymap so that only six keystrokes should be recognized in the component, the four arrow keys and the Home and End keys. However, as a result of this bug, some platforms will allow normal characters to be inserted into the field, violating the integrity of the Date value.

To work around this, a small amount of code can be added to this example to avoid the problem. The solution requires two pieces:

1. In the setDate() method, which is the only place where the text of the field should be modified, we toggle a flag just before and after setting the text, indicating that we are trying to set the text of the field.

2. We create a new class, DateTimeDocument, extending PlainDocument, and send an instance of this class to the setDocument method of JTextField. The insertString() method of DateTimeDocument only calls super.insertString() if the flag (from item 1) is true.

The exact changes are the following:

1. Add the declaration of m_settingDateText to the variables section:

    private boolean m_settingDateText = false;

2. Change the setDate method to the following:

    public void setDate(Date date) {

      m_lastDate = date;

      m_calendar.setTime(m_lastDate);

      m_settingDateText = true;

      m_textField.setText(m_format.format(m_lastDate));

      m_settingDateText = false;

      getFieldPositions();

    }

3. In the init method, send an instance of DateTimeDocument to the setDocument method of the JTextField instance to set the Document:

    m_textField.setDocument(new DateTimeDocument());

3. Add the DateTimeDocument class:

    protected class DateTimeDocument extends PlainDocument

    {

      public void insertString(int offset,

       String str, AttributeSet a) throws BadLocationException

      {
        if (m_settingDateText)

          super.insertString(offset, str, a);

      }

    }

Class DateTimeEditor.UpDownAction

The UpDownAction class is used as the action for the "up" and "down" arrow keys. When executed, this will increment or decrement the value of the field the caret is in. When values "roll over" (or "roll
down"), like incrementing the day from "31" to "1", then this will change other fields, like the month field, in this example. One instance of this class is used to move in the "up" direction, and one instance is used to move in the "down" direction. for each field, it calculates the new time or date value, and uses Date.setTime() or Calendar.set() to set the new date or time. It will check for all of the field types specified in the DateFormat class (also listed in the m_fieldTypes array), although several would never be seen in certain locales. If the component is presently disabled, no modifications will be performed on the data.

Class DateTimeEditor.BackwardAction

The BackwardAction class is used as the action for the left arrow key. When executed, it will move the text caret from the beginning of one field to the beginning of the previous field. It uses the getPrevField() method to get the field previous to the current one.

Class DateTimeEditor.ForwardAction

The ForwardAction class is used as the action for the right arrow key. When executed, it will move the text caret from the beginning of the current field to the beginning of the next field. It uses the getNextField() method to get the field following the current one.

Class DateTimeEditor.BeginAction & DateTimeEditor.EndAction

The BeginAction and EndAction classes move the text caret to the beginning of the first and last fields, respectively.

Class Spinner

The Spinner class just uses two BasicArrowButtons, in either a vertical or horizontal orientation. It provides an API to get the increment or decrement buttons so you can attach listeners to them.

Running the Code

DateTimeEditor can be compiled and executed as is. By default, it will present a date/time value in the current locale. You can experiment with this by setting the "LANG" environment variable to a legal locale string. It's possible that not all legal locale strings will show any difference in the presentation, or even be correctly recognized. I found only major locales like "es" (spanish), "fr" (french), and "it" (italian) would work.

When you push the "Show Date" button, it will print the english value of the Date to standard output. When you push the "Toggle Enable" button, it will toggle the enabled state of the text field. When it is disabled, the text is slightly grayed out, the up and down arrow keys do nothing, and the spinner buttons are insensitive. Figure 19.5 shows DateTimeEditor in action.

In addition, the Spinner class can be compiled and run as a standalone demonstration. When run, it will present an empty text field with the spinner buttons to the right of it. As presented, it doesn't do much, not showing any behavioral connection between the component (the text field) and the Spinner, but this does show what the Spinner looks like when connected to a component. Figure 19.6 shows what the Spinner class looks like when run.



The html, html.parser, and rtf packages were still under construction at the time of this writing. Due to their complexity, as well as space and time constraints placed on this book, we decided that detailed coverage of these packages would best be left for a future edition.

If, after reading this chapter, you require a more thorough treatment of the text package, we recommend Java Swing, by Robert Eckstein, Marc Loy, and Dave Wood, O'Reilly & Associates, 1998. This book includes roughly 300 pages of detailed class and interface descriptions. In particular, the discussion of Views and EditorKits provides indispensable knowledge for any developer working on support for a custom content type.

We have implemented a short example in a Swing Connection 'Tips and Tricks' article showing how to use a similar custom caret for designating an overwrite mode. In the same article we also show how to customize a PlainDocument model to allow 'insert' and 'overwrite' modes, and how to track caret position with a CaretListener. See http://java.sun.com/products/jfc/tsc/friends/tips_4/tips_4.html.

As of Java 2 FCS, this method simply returns null, and is marked as "TBD" (assumably meaning "to be done").

See p.918, Java Swing, by Robert Eckstein, Marc Loy, and Dave Wood, O'Reilly & Associates, 1998.

We have only included the most commonly used set of text component Views in this list. There are several others responsible for significant text rendering functionality. See the O'Reilly book and API docs for details.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy