问题
I've got a JComboBox that potentially can have thousands of items. They're sorted, and there's find-as-you-type, so in principle it's not completely unusable.
In practice, it's pretty unusable with just a couple of hundred items. I managed to improve the initial display performance using setPrototypeDisplayValue(), but BasicListUI still insists on configuring the list cell renderer for every item in the box (see BasicListUI.updateLayoutState()).
This, or something like it, is apparently a known issue to Sun; it has been for going on eight years now, so I'm not holding my breath.
Short of implementing my own UI, has anyone got a workaround?
回答1:
JList might be a better choice, as it uses a fly-weight approach to rendering and appears to support find-as-you-type.
If you use JComboBox, add entries to the model before the component itself starts listening. This SortedComboBoxModel uses a simple insertion sort that is acceptable for a few thousand entries:
class SortedComboBoxModel extends DefaultComboBoxModel {
/** Add elements by inserting in lexical order. */
@Override
public void addElement(Object element) {
this.insertElementAt(element, 0);
}
/** Insert in lexical order by name; ignore index. */
@Override
public void insertElementAt(Object element, int index) {
String name = element.toString();
for (index = 0; index < this.getSize(); index++) {
String s = getElementAt(index).toString();
if (s.compareTo(name) > 0) {
break;
}
}
super.insertElementAt(element, index);
}
}
回答2:
Here's the hack that I came up with. The drawbacks are:
- if you want to maintain the look and feel, you have to separately subclass each
BasicComboBoxUIextension you care about - you have to use reflection to load your UI classes, since (for instance) a subclass of
WindowsComboBoxUIwon't load on Linux - it won't work with L&Fs (e.g. MacOS?) that don't extend
BasicComboBoxUI - it makes assumptions about the
ListCellRendererthat may not always be warranted
I'm still open to cleaner solutions.
class FastBasicComboBoxUI extends BasicComboBoxUI {
@Override
public void installUI(JComponent c) {
super.installUI(c);
Object prototypeValue = this.comboBox.getPrototypeDisplayValue();
if (prototypeValue != null) {
ListCellRenderer renderer = comboBox.getRenderer();
Component rendererComponent = renderer
.getListCellRendererComponent(this.listBox,
prototypeValue, 0, false, false);
if (rendererComponent instanceof JLabel) {
// Preferred size of the renderer itself is (-1,-1) at this point,
// so we need this hack
Dimension prototypeSize = new JLabel(((JLabel) rendererComponent)
.getText()).getPreferredSize();
this.listBox.setFixedCellHeight(prototypeSize.height);
this.listBox.setFixedCellWidth(prototypeSize.width);
}
}
}
}
I'm still open to cleaner solutions.
Later
Turns out this only solved some of the problems. Initial display of a combo box with a large number of items could still be really slow. I had to make sure the popup list box immediately gets a fixed cell size, by moving the code into the ComboPopup itself, as follows. Note that, as above, this depends on the prototype value.
@Override
protected ComboPopup createPopup() {
return new BasicComboPopup(comboBox) {
@Override
protected JList createList() {
JList list = super.createList();
Object prototypeValue = comboBox.getPrototypeDisplayValue();
if (prototypeValue != null) {
ListCellRenderer renderer = comboBox.getRenderer();
Component rendererComponent = renderer
.getListCellRendererComponent(list, prototypeValue, 0, false, false);
if (rendererComponent instanceof JLabel) {
// Preferred size of the renderer itself is (-1,-1) at this point,
// so we need this hack
Dimension prototypeSize = new JLabel(((JLabel) rendererComponent)
.getText()).getPreferredSize();
list.setFixedCellHeight(prototypeSize.height);
list.setFixedCellWidth(prototypeSize.width);
}
}
return list;
}
};
}
来源:https://stackoverflow.com/questions/3194958/fast-replacement-for-jcombobox-basiccomboboxui