【问题标题】:ImageIcon slow in ListCellRendererListCellRenderer 中的 ImageIcon 缓慢
【发布时间】:2011-09-11 17:15:58
【问题描述】:

我有一个class GridPanel extends JPanel,带有一个静态内部类ToolSelectComboBox extends JComboBox,它又具有两个静态内部类ToolSelectComboBoxModel implements ComboBoxModelToolSelectComboBoxRenderer implements ListCellRenderer。面板显示ToolSelectComboBox (TSCB),其构造函数将其模型和渲染器设置为我创建的自定义模型。盒子创建正确,其模型和渲染器工作正常。

但是,渲染器的getListCellRendererComponent(...) 方法在它返回的JLabel 上使用ImageIcon。图标已正确加载,但是,当我第一次单击组合框(每次运行时)时,图像需要 完全(或至少非常接近) 超过一秒的时间来加载.我会假设这是加载文件的一些延迟,除了

  • 这是我本地文件系统上的一个 4kB 文件
  • 当我在result.setIcon(...) 命令之前和之后添加System.out.println 命令时,它们几乎立即相互跟随。

我注意到的奇怪之处在于println 命令被触发两次,一次是在我单击框时触发,另一次是在图标加载时触发。

还可能值得注意的是,由于它旨在与覆盖父抽象类的单个方法的多个类一起使用(以生成图标的路径),当我注意到它工作缓慢时,我改变了代码从简单地使用 getIcon 命令检索图标到将各种大小的图标(16、32 和 64 px 平方)存储在 TreeMap<Tool.ImageSize, ImageIcon> 中(其中 Tool 是我创建的具有 ImageIcon getIcon() 的界面方法。

我所有的导入都井井有条。

任何帮助将不胜感激!

如果我发布了太多代码,我深表歉意,但我想确保它是可以理解的。另一方面,如果您需要更多代码才能理解,请不要犹豫。

代码(所有以“*”开头并带有类似注释文本的行都是折叠的 JavaDoc 标记,而不仅仅是乱码):

public class GridPanel extends JPanel {

    public static class ToolSelectComboBox extends JComboBox {

         // Combo box model `ToolSelectComboBoxModel` snipped

         * A renderer for the {@link ToolSelectComboBoxModel}. This may
        public static class ToolSelectComboBoxRenderer implements
                ListCellRenderer {

             * The default renderer. Only the icon and text are modified.
            protected DefaultListCellRenderer d = new DefaultListCellRenderer();

            @Override
            public Component getListCellRendererComponent(final JList list,
                    final Object value, final int index,
                    final boolean isSelected, final boolean cellHasFocus) {
                if (!ToolSelectComboBoxModel.class.isInstance(list.getModel())) {
                    throw new IllegalStateException(
                            "Cannot use a ToolSelectComboBoxRenderer on any list model type other than ToolSelectComboBoxModel.");
                }
                final JLabel result = (JLabel) d.getListCellRendererComponent(
                        list, value, index, isSelected, cellHasFocus);
                result.setText(null);
                if (value != null) {
                    result.setIcon(((Tool) value)
                            .getIcon(Tool.IconSize.SIZE_32PX));
                }
                return result;
            }
        }

        public ToolSelectComboBox() {
            setModel(new ToolSelectComboBoxModel());
            ((ToolSelectComboBoxModel) getModel()).add(new CircleTool()); // shown below
            setRenderer(new ToolSelectComboBoxRenderer());
        }
    }

     * Create the panel.
    public GridPanel() {
        setLayout(new BorderLayout(0, 0));

        final ToolSelectComboBox toolSelectComboBox = new ToolSelectComboBox();
        add(toolSelectComboBox, BorderLayout.NORTH);

        final SquareGrid squareGrid = new SquareGrid(); // another class; this works fine
        add(squareGrid, BorderLayout.CENTER); // irrelevant to problem

    }

}

CircleTool 类只有一种方法(覆盖AbstractTool 的抽象方法以获取图像path),并且由于该方法有效(它可以很好地获取路径,它只是加载缓慢的图标),我没有包含这个类。

AbstractTool 类:

public abstract class AbstractTool implements Tool {

    /**
     * A {@link TreeMap} to map the icon sizes to their icons.
     */
    protected final TreeMap<Tool.IconSize, ImageIcon> map = new TreeMap<Tool.IconSize, ImageIcon>();

    /**
     * Constructs the tool and sets up the {@linkplain #map}.
     */
    public AbstractTool() {
        for (final Tool.IconSize size : Tool.IconSize.values()) {
            System.out.println("Putting value for " + size);
            map.put(size,
                    new ImageIcon(Tool.class.getResource(getImagePath(size))));
        }
    }

    @Override
    public ImageIcon getIcon(final IconSize size) {
        return map.get(size);
    }

    /**
     * Gets the image path for the given image size.
     * 
     * @param size
     *            the size
     * @return the image path
     */
    protected abstract String getImagePath(Tool.IconSize size);

}

【问题讨论】:

    标签: java performance jcombobox imageicon listcellrenderer


    【解决方案1】:

    但是,当我第一次单击组合框(每次运行)时,加载图像需要一秒钟多一点的时间。我认为这是加载文件的一些滞后

    这也是我的猜测。

    除了我在 result.setIcon(...) 命令之前和之后添加 System.out.println 命令时,它们几乎立即相互跟随

    当您单击组合框时,所有代码都会在 EDT 上运行,这意味着每个图标将按顺序加载。

    但是 System.out.println() 在单独的线程上运行,因此会立即显示。

    解决方案是在程序启动时加载图标。也就是说,每当您在地图中定义/添加图标时,您都应该在那时阅读它们。您可能希望在单独的线程上执行此操作,以免阻止 GUI 显示。

    编辑:

    这是一个在组合框中显示图标的简单 SSCCE:

    import java.awt.*;
    import javax.swing.*;
    
    public class ComboBoxIcon extends JFrame
    {
        JComboBox comboBox;
    
        public ComboBoxIcon()
        {
            Object[] items =
            {
                new ImageIcon("about16.gif"),
                new ImageIcon("add16.gif"),
                new ImageIcon("copy16.gif")
            };
            comboBox = new JComboBox( items );
            getContentPane().add( comboBox, BorderLayout.NORTH );
        }
    
        public static void main(String[] args)
        {
            JFrame frame = new ComboBoxIcon();
            frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
            frame.pack();
            frame.setLocationRelativeTo( null );
            frame.setVisible( true );
        }
    }
    

    如果您需要更多帮助,则需要发布您的 SSCCE 来证明问题。

    【讨论】:

    • 感谢您的回复!您所说的关于在单独线程上运行的 println 命令是有道理的;我没有想到这一点。但是,事件调度线程是什么?维基百科的文章对我来说并没有说清楚,而且我对 Java 还很陌生(并且习惯于 C++,我听说从中转移很麻烦),所以请原谅我的无知 :)。我假设通过将ImageIcons 放入我的TreeMap,图标会自动加载;不是这样吗?如果没有,我该怎么做? loadImage() 受保护;我应该使用getImage()吗?
    • 我尝试了getImage(),但在新的Thread 和默认的Thread 中仍然存在延迟时间。你会建议我做什么?我应该只创建一个图形对象并为其绘制图标吗?
    • 是的,创建 ImageIcon 应该会导致图像被加载。从您的代码中看不出您何时或如何执行此操作。所以我不知道这是否在您创建和显示 GUI 时完成,或者是否在单击组合框时发生。看来是后者。 EDT 是事件调度线程。对 GUI 的所有更新都应在此线程上执行。阅读Concurrency in Swing 了解更多信息。我包含了我的 SSCCE,它显示预加载时立即呈现的图标。
    • 这真的很奇怪;我使用基本相同的代码:new ImageIcon(Tool.class.getResource(getImagePath(size))),其中getImagePath(...) 返回一个字符串,所以我们都将URL 传递给ImageIcon 构造函数。我阅读了SSCCE.org 网站的内容,但我真的不知道如何为这个问题制作 SSCCE。整个代码是here
    • @kleopatra,我的意思是,如果您有在 EDT 上执行并更新标签文本然后调用 Thread.sleep() 的循环代码,您将只会看到最后一个值集在标签上,并不是所有的中间值,因为你导致 EDT 休眠。但是,如果您调用 System.out.println() 来打印相同的文本,您将在循环的每次迭代中看到输出,因为它在单独的线程上执行。
    猜你喜欢
    • 2014-01-20
    • 2012-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-27
    • 1970-01-01
    • 2020-07-21
    • 2010-09-20
    相关资源
    最近更新 更多