【问题标题】:How to prevent JComboBox from becoming unresponsive when using a custom ListCellRenderer使用自定义 ListCellRenderer 时如何防止 JComboBox 无响应
【发布时间】:2011-08-19 06:30:46
【问题描述】:

我正在使用JComboBox 和自定义ListCellRenderer 制作字体选择器。我想要 JComboBox 显示所有可用的字体,每个字体名称都以自己的字体显示。我目前使用大约 500 种字体。

提供此功能的ListCellRenerer 示例:

private class ComboBoxRenderer extends JLabel implements ListCellRenderer {

    private JLabel label = new JLabel("Test");

    @Override
    public Component getListCellRendererComponent(JList list, Object value,
            int index, boolean isSelected, boolean cellHasFocus) {

        Font tempFont = label.getFont();
        setFont(new Font((String) value, tempFont.getStyle(),
                tempFont.getSize()));

        setText((String) value);

        return this;
    }
}

问题是,当使用此渲染器时,JComboBox 在程序执行期间变得无响应。第一次单击组合框以显示列表时,加载列表需要几秒钟。第二次单击,列表立即显示。

如果有一条线

setFont(new Font((String) value, tempFont.getStyle(),tempFont.getSize()));

,组合框工作正常。

如何防止这种反应迟钝?

【问题讨论】:

  • 您可能想尝试创建Font 对象的缓存。用于缓存 90 年代的 FontFontMetric。您可以为每个Font 创建一个JLabel
  • 但有测试 if (isSelected){cellHasFocus
  • 刚刚注意到你说它只是第一次很慢并且你使用了 500 种字体。我想加载 500 种字体是一项艰巨的任务。
  • @Tom Hawtin - 不要这么认为,因为评论创建 显示字体 的行有帮助

标签: java swing jcombobox listcellrenderer


【解决方案1】:

发生的情况是组合的内部尝试动态地找到首选尺寸。为此,它遍历列表中的所有项目,将项目提供给渲染器以测量渲染组件的首选大小。

您可以通过设置一个原型值进行测量来防止这种情况发生,然后使用该原型测量一次尺寸

 comboBox.setPrototypeDisplayValue(sampleFont);

编辑:正如@Boro 检测到的那样,这还不够——它只为组合框本身设置原型,而不是为弹出窗口中的列表设置原型(应该如此,这可能有多疯狂)。要破解,我们必须手动设置它,这里有一个代码 sn-p 来玩

public class ComboWithPrototype {

    private JComponent createContent() {
        final Font[] systemFonts = GraphicsEnvironment
                .getLocalGraphicsEnvironment().getAllFonts();

        final JComboBox box = new JComboBox();
        box.setRenderer(new ComboBoxRenderer());
        box.setPrototypeDisplayValue(systemFonts[0]);
        Accessible a = box.getUI().getAccessibleChild(box, 0);
        if (a instanceof javax.swing.plaf.basic.ComboPopup) {
            JList popupList = ((javax.swing.plaf.basic.ComboPopup) a).getList();
            // route the comboBox' prototype to the list
            // should happen in BasicComboxBoxUI
            popupList.setPrototypeCellValue(box.getPrototypeDisplayValue());
        }
        Action action = new AbstractAction("set model") {

            @Override
            public void actionPerformed(ActionEvent e) {
                box.setModel(new DefaultComboBoxModel(systemFonts));
            }
        };
        JComponent panel = new JPanel(new BorderLayout());
        panel.add(box);
        panel.add(new JButton(action), BorderLayout.SOUTH);
        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ComboWithPrototype().createContent());
                frame.setLocationRelativeTo(null);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

自定义 ListCellRenderer(略有改动,期望字体类型的项目)

private class ComboBoxRenderer extends DefaultListCellRenderer {

    private Font baseFont = new JLabel("Test").getFont();

    @Override
    public Component getListCellRendererComponent(JList list, Object value,
            int index, boolean isSelected, boolean cellHasFocus) {

        super.getListCellRendererComponent(list, value, index, isSelected,
                cellHasFocus);
        if (value instanceof Font) {

            Font font = (Font) value;
            setFont(new Font(font.getName(), baseFont.getStyle(), baseFont.getSize())); 
            setText(font.getName());
        }

        return this;
    }
}

【讨论】:

  • @kleopatra 你能举例说明它是如何使用的,因为我正在尝试使用它并且没有运气。在安装渲染器之前或之后始终具有独立于设置的延迟。 Font[] opts = new Font[]{....}; JComboBox p = new JComboBox(opts); p.setRenderer(new ComboBoxRenderer()); p.setPrototypeDisplayValue(largestFont); 虽然我更改了渲染的主体,但如果需要,我可以发布 SSCCE。
  • @Boro - f*,你说得对,设置该属性似乎还不够:它可以防止循环遍历所有值以调整 combo 本身的大小,但在显示弹出窗口时仍然循环...
  • @kleopatra 是的,这就是我所经历的。
  • @kleopatra 很好,就是这样(+1)。在列表最初显示之前仍然有非常非常轻微的延迟,但我认为这是因为它是第一次出现。总之,这个解决方案比没有它要快得多。干得好。
  • @Boro - 谢谢 :-) 嗯......没有看到 any 延迟(实际上不应该,它只显示 8 个左右的项目) - 也许是机器-依赖?提醒自己:试试上网本
【解决方案2】:

@kleopatra 比你更注意我,但 setPrototypeDisplayValue 看起来像懒惰的选择,我的修正

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class SystemFontDisplayer extends JFrame {

    private static final long serialVersionUID = 1L;
    private JComboBox fontsBox;

    public SystemFontDisplayer() {

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        String[] fontFamilyNames = ge.getAvailableFontFamilyNames();
        fontsBox = new JComboBox(fontFamilyNames);
        fontsBox.setSelectedItem(0);
        fontsBox.setRenderer(new ComboRenderer(fontsBox));
        fontsBox.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final String fontName = fontsBox.getSelectedItem().toString();
                    fontsBox.setFont(new Font(fontName, Font.PLAIN, 16));
                }
            }
        });
        fontsBox.setSelectedItem(0);
        fontsBox.getEditor().selectAll();
        add(fontsBox, BorderLayout.CENTER);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setPreferredSize(new Dimension(400, 60));
        setLocation(200, 105);
        pack();

        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                fontsBox.setPopupVisible(true);
                fontsBox.setPopupVisible(false);
            }
        });
        setVisible(true);
    }

    public static void main(String arg[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                SystemFontDisplayer systemFontDisplayer = new SystemFontDisplayer();
            }
        });
    }

    private class ComboRenderer extends BasicComboBoxRenderer {

        private static final long serialVersionUID = 1L;
        private JComboBox comboBox;
        final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
        private int row;

        private ComboRenderer(JComboBox fontsBox) {
            comboBox = fontsBox;
        }

        private void manItemInCombo() {
            if (comboBox.getItemCount() > 0) {
                final Object comp = comboBox.getUI().getAccessibleChild(comboBox, 0);
                if ((comp instanceof JPopupMenu)) {
                    final JList list = new JList(comboBox.getModel());
                    final JPopupMenu popup = (JPopupMenu) comp;
                    final JScrollPane scrollPane = (JScrollPane) popup.getComponent(0);
                    final JViewport viewport = scrollPane.getViewport();
                    final Rectangle rect = popup.getVisibleRect();
                    final Point pt = viewport.getViewPosition();
                    row = list.locationToIndex(pt);
                }
            }
        }

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (list.getModel().getSize() > 0) {
                manItemInCombo();
            }
            final JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
            final Object fntObj = value;
            final String fontFamilyName = (String) fntObj;
            setFont(new Font(fontFamilyName, Font.PLAIN, 16));
            return this;
        }
    }
}

【讨论】:

  • -1 表示(未成功)尝试重新发明轮子。阅读 setPrototypeDisplayVaule 的 api 文档,了解它存在的原因正是这里需要它的原因,即 not 循环遍历所有元素只是为了获得尺寸提示
  • 另一个 -1 用于执行完全不摆动的事情,例如(不完整列表):a)让渲染器引用目标组合框 b)在每次调用 getRendererComponent 时创建一个 JList,c)配置每次调用都有一个未使用的渲染器...
  • @kleopatra :-) 这个渲染器在 JList 内没有循环的情况下工作 :-),它是关于我懒惰删除调用 manItemInCombo();
  • @kleopatra - 我正在考虑对 mKorbel 投赞成票... ;-) 因为他的解决方案为字体列表设置了不同的高度,所以您的解决方案设置相同(可能太小,但可以肯定对于某些字体来说太大了)所有条目的高度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
  • 1970-01-01
  • 2017-10-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多