I was wondering whether the way I expect the JFormattedTextField
to work in combination with a default button is correct. When editing the value of a JFormattedTextField
, you usually want to commit the value and then use it, this usually happens on focusLost
or sometimes when the ActionListener
is manually triggered. Now, if such a text field is inside of a JDialog
which has a DefaultButton
defined, triggering the default button with Ctrl+Enter will not cause the currently focused JFormattedTextField
to trigger it's focusLost
event.
Now, I see two solutions for this:
SwingUtilities.InvokeLater
in the ActionListener
of the default buttonHowever, both of these seem dirty, especially the first one, as it's not very easy to extend said dialog without forgetting to handle the components. The second one is rather undesirable imo because more invokeLater
s usually mean more code that's harder to debug.
Is there a better way of solving this?
EXAMPLE:
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class DefaultButton
{
public static void main( String[] args )
{
JFrame frame = new JFrame();
JFormattedTextField field = new JFormattedTextField();
field.setValue( "HELLO" );
JButton defaultButton = new JButton( "Default" );
defaultButton.addActionListener( __ ->
{
JOptionPane.showMessageDialog( frame, "You've chosen: " + field.getValue() );
} );
frame.getRootPane().setDefaultButton( defaultButton );
JPanel panel = new JPanel( new BorderLayout() );
panel.add( field, BorderLayout.NORTH );
panel.add( defaultButton, BorderLayout.SOUTH );
frame.add( panel );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
}
Yes, when the JFormattedTextField
has input focus and you activate the defaultButton
, the JFormattedTextField
does not lose focus and therefore the value is not committed.
Refer to How to Use Formatted Text Fields.
Here is a quote:
A formatted text field's text and its value are two different properties, and the value often lags behind the text.
The text property is defined by theJTextField
class. This property always reflects what the field displays. The value property, defined by theJFormattedTextField
class, might not reflect the latest text displayed in the field. While the user is typing, the text property changes, but the value property does not change until the changes are committed.
I see two possible solutions (although I imagine there are more).
In the actionPerformed()
method, either call getText()
rather than getValue()
, or call commitEdit()
before calling getValue()
, i.e.
JOptionPane.showMessageDialog( frame, "You've chosen: " + field.getText() );
or
try {
field.commitEdit();
}
catch (java.text.ParseException x) {
x.printStackTrace();
}
JOptionPane.showMessageDialog(frame, "You've chosen: " + field.getValue());
I believe this is what you are looking for.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.KeyboardFocusManager;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class DefaultButton
{
public static void main( String[] args )
{
JFrame frame = new JFrame();
JFormattedTextField field = new JFormattedTextField();
field.setValue( "HELLO" );
JButton defaultButton = new JButton( "Default" );
defaultButton.addActionListener( __ ->
{
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
if (kfm.getFocusOwner() == field) {
kfm.focusNextComponent();
}
EventQueue.invokeLater(() -> JOptionPane.showMessageDialog( frame, "You've chosen: " + field.getValue() ));
} );
frame.getRootPane().setDefaultButton( defaultButton );
JPanel panel = new JPanel( new BorderLayout() );
panel.add( field, BorderLayout.NORTH );
panel.add( defaultButton, BorderLayout.SOUTH );
frame.add( panel );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
}
Concentrate on method actionPerformed()
. If the JFormattedTextField
currently has the focus then force the focus to move to the next component. Regardless of which component has the keyboard focus, wrap the displaying of the JOptionPane
in a invokeLater()
call.