Fast replacement for JComboBox / BasicComboBoxUI?

穿精又带淫゛_ 提交于 2019-12-20 03:15:18

问题


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 BasicComboBoxUI extension you care about
  • you have to use reflection to load your UI classes, since (for instance) a subclass of WindowsComboBoxUI won't load on Linux
  • it won't work with L&Fs (e.g. MacOS?) that don't extend BasicComboBoxUI
  • it makes assumptions about the ListCellRenderer that 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!