Focus traversal only seems to work for enabled Swing components (tabbing with TAB or CTRL+TAB). How would one treat both enabled and disabled components as significant and enable keyboard traversal through them?
Why would I want this? I have a form where each textfield, textarea or checkbox may have a set and unset state in addition to its value. Currently users are required to set and unset the components using mouse clicks (left click on unset component to set, CTRL+left click on a set component to unset it), but I'd like to provide keyboard access for doing the same thing too. The reason I chose this mechanism is that empty values may have meaning - an empty text component string is significant and cannot be used for denoting "unsetness". I also didn't want to precede each settable value with a checkbox instead, since that would just look awkward.
Here's an example form to clarify the set/unset state (the checkbox and textarea start off as unset, while the field as set):
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
public class TraverseDisabled extends JFrame {
public static final String VALUE_NOT_SET_MESSAGE = "Click to set this value.";
public static final String VALUE_SET_DEFAULT_MESSAGE = "Edit value or use CTRL + click to unset this value.";
public static final String VALUE_NOT_SET_DEFAULT_VALUE = "<not-set>";
private JPanel panel;
private JLabel label1;
private JTextField textfield1;
private JCheckBox checkbox1;
private JLabel label2;
private JTextArea textarea1;
private JButton button;
public TraverseDisabled() {
setTitle("Form");
initComponents();
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void initComponents() {
panel = new JPanel();
panel.setLayout(new GridBagLayout());
setLayout(new BorderLayout());
add(panel);
GridBagConstraints gbc;
label1 = new JLabel("Field1:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
panel.add(label1, gbc);
textfield1 = new JTextField(20);
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 0;
panel.add(textfield1, gbc);
textfield1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
onJComponentClicked(e, textfield1, null);
}
});
checkbox1 = new JCheckBox("Checkbox1");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.WEST;
panel.add(checkbox1, gbc);
checkbox1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
onJComponentClicked(e, checkbox1, false);
}
});
enableEditingForJCheckBoxComponent(checkbox1, false, false);
label2 = new JLabel("Area1:");
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.NORTHWEST;
panel.add(label2, gbc);
textarea1 = new JTextArea(5, 20);
JScrollPane scrollPane1 = new JScrollPane(textarea1);
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 2;
panel.add(scrollPane1, gbc);
textarea1.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
onJComponentClicked(e, textarea1, null);
}
});
enableEditingForJTextComponent(textarea1, false, VALUE_NOT_SET_DEFAULT_VALUE);
button = new JButton("Apply");
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 3;
gbc.anchor = GridBagConstraints.EAST;
panel.add(button, gbc);
}
private Object onJComponentClicked(java.awt.event.MouseEvent evt, JComponent component, Object previousValue) {
if (!component.isEnabled()) {
if (component instanceof JCheckBox) {
enableEditingForJCheckBoxComponent((JCheckBox)component, true, (Boolean)previousValue);
} else if (component instanceof JTextComponent) {
enableEditingForJTextComponent((JTextComponent)component, true, (String)previousValue);
}
evt.consume();
} else if (evt.isControlDown()) {
if (component instanceof JCheckBox) {
JCheckBox cb = (JCheckBox)component;
previousValue = !cb.isSelected();
enableEditingForJCheckBoxComponent((JCheckBox) component, false, (Boolean)previousValue);
} else if (component instanceof JTextComponent) {
previousValue = ((JTextComponent)component).getText();
enableEditingForJTextComponent((JTextComponent)component, false, VALUE_NOT_SET_DEFAULT_VALUE);
}
evt.consume();
}
return previousValue;
}
private void enableEditingForJTextComponent(JTextComponent textComponent, boolean enable, String text) {
if (!enable) {
textComponent.setEnabled(false);
textComponent.setText(text);
textComponent.setToolTipText(VALUE_NOT_SET_MESSAGE);
} else {
textComponent.setEnabled(true);
textComponent.setText(text);
textComponent.setToolTipText(VALUE_SET_DEFAULT_MESSAGE);
textComponent.requestFocusInWindow();
}
}
private void enableEditingForJCheckBoxComponent(JCheckBox checkBox, boolean enable, boolean value) {
if (!enable) {
checkBox.setEnabled(false);
checkBox.setSelected(value);
checkBox.setToolTipText(VALUE_NOT_SET_MESSAGE);
} else {
checkBox.setEnabled(true);
checkBox.setSelected(value);
checkBox.setToolTipText(VALUE_SET_DEFAULT_MESSAGE);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TraverseDisabled().setVisible(true);
}
});
}
}