Issue
With a JComboBox containing some elements beginning with repeated letters, typing in the character twice will return the character typed, followed by the first character of its type. For example, typing "bb" in a list containing ba, bb, and bc will return ba. However, if this list also contains bbd, continuing to press a "d" will return the bbd option. This is the same with numbers: Typing "33" returns 30, while typing "334" returns 334.
Is there a way to fix this so that a double keyPress really returns what is typed in?
Quick sample program:
String[] range = new String[401];
for (int i = 0; i <= 400; i++) {
range[i] = "" + i;
}
private javax.swing.JComboBox<String> jComboBox1;
jComboBox1 = new javax.swing.JComboBox<>();
getContentPane().setLayout(new java.awt.GridLayout());
jComboBox1.setModel(new javax.swing.DefaultComboBoxModel<>(range));
getContentPane().add(jComboBox1);
pack();
Solution
Item selection via keyboard is controlled by the JComboBox.KeySelectionManager
, which, surprisingly, you can actually implement and change 😱
The one thing that the default implementation does is, it will try and find items which match the key stroke based on the first character of the item (when converted to a String
via toString
). The "neat" thing about this is, it will search from the currently selected item (if not null
), if it can't find another matching item, it will start at the beginning of the model instead.
What this means is, if you type 3
repeatedly, it will, progressively move through all the items that start with 3
. (30
, 31
, 32
... 39
, 300
...)
Buuuut, this isn't what you apparently want, so, instead, you're going to have to supply your own algorithm.
One, very important consideration is, what happens when the user stops typing? If the user has typed 33
, stops, then types 3
again, what should happen? Should it select 3
or 333
?
The following is a VERY basic example, based on the DefaultKeySelectionManager
used by JComoBox
by default. It uses a StringBuilder
to keep track of the key strokes and a Swing Timer
which provides a 250
millisecond timeout, which will clear the StringBuilder
after 250
milliseconds of inactivity (you could pass this value into the constructor to define your own)
When called by the JComboBox
, it will simply do a linear search of the model for a item whose toString
result starts with what's been typed.
This example is a case insensitivity, but you can modify it to meet your particular needs
public class MyKeySelectionManager implements KeySelectionManager {
private Timer timeout;
private StringBuilder pattern = new StringBuilder(32);
public MyKeySelectionManager() {
timeout = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
pattern.delete(0, pattern.length());
}
});
timeout.setRepeats(false);
}
@Override
public int selectionForKey(char aKey, ComboBoxModel<?> model) {
timeout.stop();
pattern.append(Character.toLowerCase(aKey));
String match = pattern.toString();
for (int index = 0; index < model.getSize(); index++) {
String text = model.getElementAt(index).toString().toLowerCase();
if (text.startsWith(match)) {
timeout.start();
return index;
}
}
timeout.start();
return -1;
}
}
Runnable example
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComboBox.KeySelectionManager;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
for (int index = 0; index < 50; index++) {
model.addElement(Integer.toString(index));
}
JComboBox cb = new JComboBox(model);
cb.setKeySelectionManager(new MyKeySelectionManager());
JFrame frame = new JFrame();
JPanel content = new JPanel(new GridBagLayout());
content.setBorder(new EmptyBorder(32, 32, 32, 32));
content.add(cb);
frame.setContentPane(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MyKeySelectionManager implements KeySelectionManager {
private Timer timeout;
private StringBuilder pattern = new StringBuilder(32);
public MyKeySelectionManager() {
timeout = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
pattern.delete(0, pattern.length());
}
});
timeout.setRepeats(false);
}
protected int indexOf(Object item, ComboBoxModel<?> model) {
for (int index = 0; index < model.getSize(); index++) {
if (model.getElementAt(index) == item) {
return index;
}
}
return -1;
}
@Override
public int selectionForKey(char aKey, ComboBoxModel<?> model) {
timeout.stop();
pattern.append(Character.toLowerCase(aKey));
String match = pattern.toString();
for (int index = 0; index < model.getSize(); index++) {
String text = model.getElementAt(index).toString().toLowerCase();
if (text.startsWith(match)) {
timeout.start();
return index;
}
}
timeout.start();
return -1;
}
}
}
Don't forget to take a look at JComboBox#setKeySelectionManager
for more details
Answered By - MadProgrammer
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.