Why does this property change event fire when JFrame opens?

1.5k views Asked by At

I have a 2 JFormattedTextFields that input the same information in different forms. I want to have one change when the user change the other. I have implemented something like this before using PropertyChangeListeners, however I have encountered a strange error this time.

When my JFrame opens the PropertyChangeListener event is fired for no apparent reason. The value of getNewValue() on the PropertyChangeEvent is null.

Here is all the code that references my label:

private JFormattedTextField fpsField;

Then later in my JFrame constructor:

fpsField = new JFormattedTextField(NumberFormat.getInstance());
fpsField.addPropertyChangeListener("value", new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent arg0) {
        if(!updatingFPS){
            updatingFPS = true;

            Float fps = (Float) arg0.getNewValue();

            long newDelay = Math.round(1000/fps);
            delayField.setValue(newDelay);

            updatingFPS = false;
        }
    }
});
GridBagConstraints gbc_fpsField = new GridBagConstraints();
gbc_fpsField.insets = new Insets(0, 0, 5, 0);
gbc_fpsField.fill = GridBagConstraints.HORIZONTAL;
gbc_fpsField.gridx = 1;
gbc_fpsField.gridy = 0;
monitorPreferencesPane.add(fpsField, gbc_fpsField);
fpsField.setColumns(10);

As you can see I do not set the value in code and the event is called (and generates a NullPointerException) before I get a chance to type anything in. I have not written the listener for delayField yet.

1

There are 1 answers

3
Jonathan Drapeau On BEST ANSWER

Since the default value is null, the focus event fired when opening the JFrame will fire a property change event because null can not be adequately compared. See the full explanation with code below after the possible solutions.

One solution to get rid of the NPE is to set a default value to your fpsField before adding the PropertyChangeListener since doing that will not fire the PropertyChange event when the JFrame opens.

JFormattedTextField fpsField = new JFormattedTextField(NumberFormat.getInstance());
fpsField.setValue(0);

Another solution, if you can't set the default value, is to check if the old and new values in the event are actually different before updating your delayField. They are both null when the JFrame opens.

The reason the event is fired is because a FocusEvent, where the cause is ACTIVATION is fired and processed by your JFormattedTextField, which calls

    /**
 * Processes any focus events, such as
 * <code>FocusEvent.FOCUS_GAINED</code> or
 * <code>FocusEvent.FOCUS_LOST</code>.
 *
 * @param e the <code>FocusEvent</code>
 * @see FocusEvent
 */
protected void processFocusEvent(FocusEvent e) {
    super.processFocusEvent(e);

// ignore temporary focus event
if (e.isTemporary()) {
    return;
}

    if (isEdited() && e.getID() == FocusEvent.FOCUS_LOST) {
    InputContext ic = getInputContext();
    if (focusLostHandler == null) {
    focusLostHandler = new FocusLostHandler();
    }

    // if there is a composed text, process it first
    if ((ic != null) && composedTextExists) {
    ic.endComposition();
    EventQueue.invokeLater(focusLostHandler);
    } else {
    focusLostHandler.run();
    }
    }
    else if (!isEdited()) {
        // reformat
        setValue(getValue(), true, true);
    }
}

and

    /**
 * Does the setting of the value. If <code>createFormat</code> is true,
 * this will also obtain a new <code>AbstractFormatter</code> from the
 * current factory. The property change event will be fired if
 * <code>firePC</code> is true.
 */
private void setValue(Object value, boolean createFormat, boolean firePC) {

since firePC is true, it fires the PropertyChange event on value.

Finally, since the default value is null, the condition to actually fire the event

    /**
 * Support for reporting bound property changes for Object properties. 
 * This method can be called when a bound property has changed and it will
 * send the appropriate PropertyChangeEvent to any registered
 * PropertyChangeListeners.
 *
 * @param propertyName the property whose value has changed
 * @param oldValue the property's previous value
 * @param newValue the property's new value
 */
protected void firePropertyChange(String propertyName,
                                  Object oldValue, Object newValue) {
    PropertyChangeSupport changeSupport;
    synchronized (getObjectLock()) {
        changeSupport = this.changeSupport;
    }
    if (changeSupport == null ||
        (oldValue != null && newValue != null && oldValue.equals(newValue))) {
        return;
    }
    changeSupport.firePropertyChange(propertyName, oldValue, newValue);
}

will fire it, oldValue and newValue being both null.