如何防止 JComboBox 在使用自定义 ListCellRenderer 时变得无响应

发布于 2024-11-05 09:01:18 字数 971 浏览 0 评论 0原文

我正在使用 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()));

,组合框就可以正常工作。

如何防止这种无反应现象呢?

I am making a font chooser using JComboBox and a custom ListCellRenderer. I want
the JComboBox to display all available fonts, with each font name displayed in its own font. I am currently using around 500 fonts.

An example of a ListCellRenerer that provides this functionality:

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;
    }
}

The problem is that, when using this renderer, the JComboBox becomes unresponsive during program execution. The first time one clicks on the combobox to reveal the list, it takes a couple of seconds for the list to load. The second time one clicks, the list is instantly displayed.

If one comments the line

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

, the combobox works just fine.

How can one prevent this unresponsiveness?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我不是你的备胎 2024-11-12 09:01:18

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

您可以通过设置用于测量的原型值来防止这种情况,然后使用该原型测量一次大小

 comboBox.setPrototypeDisplayValue(sampleFont);

编辑:正如@Boro检测到的那样,这还不够 - 它仅为组合框本身设置原型,而不为弹出窗口中的列表设置原型(如应该的,那是多么疯狂的越野车……可能是)。为了解决这个问题,我们必须手动设置它,这是一个使用

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);
            }
        });
    }

Custom ListCellRenderer 的代码片段(稍作更改,以期望 Font 类型的项目)

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;
    }
}

What happens is that the combo's internals try to find the preferred size dynamically. For doing so, it loops through all items in the list, feeds the renderer with the items to measure the rendering component's preferred size.

You can prevent that by setting a prototypeValue for measuring, then the size is measured once using that prototype

 comboBox.setPrototypeDisplayValue(sampleFont);

Edit: as @Boro detected, that's not enough - it sets the prototype for the comboBox itself only, not for the list in the popup (as it should, how crazily buggy can that ... possibly be). To hack around, we have to manually set it, here's a code snippet to play with

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);
            }
        });
    }

Custom ListCellRenderer (slightly changed, to expect items of type Font)

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;
    }
}
哭了丶谁疼 2024-11-12 09:01:18

@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;
        }
    }
}

@kleopatra than you for notice me but setPrototypeDisplayValue look like as lazy choise, my amedment

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文