【问题标题】:Java Swing ImageIcon not showing in JLabelJava Swing ImageIcon 未在 JLabel 中显示
【发布时间】:2020-03-13 14:29:02
【问题描述】:

我发现以下MRE 每次执行时都会重现相同的错误:

import java.awt.Component;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.image.BufferedImage;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class IconDelegate implements Icon {
    private final Icon delegated;

    public IconDelegate(final Icon delegated) {
        this.delegated = Objects.requireNonNull(delegated);
    }

    @Override
    public int getIconWidth() {
        return delegated.getIconWidth();
    }

    @Override
    public int getIconHeight() {
        return delegated.getIconHeight();
    }

    @Override
    public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
        delegated.paintIcon(c, g, x, y);
        //Some other drawing operations would be here...
    }

    public static void main(final String[] args) {
        final ImageIcon ii = new ImageIcon();
        final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
        final GraphicsDevice gdev = genv.getDefaultScreenDevice();
        final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
        final BufferedImage bimg = gcnf.createCompatibleImage(300, 400);
        final Image img = bimg.getScaledInstance(150, 200, Image.SCALE_SMOOTH);
        //img.getWidth(null); //Uncomment this line and the image will be drawn normally!
        ////img.flush(); //Uncomment this line and the image will not be drawn.
        ii.setImage(img);
        System.out.println(ii.getImageLoadStatus() == MediaTracker.ABORTED);
        final JLabel lbl = new JLabel(new IconDelegate(ii));
        final JFrame frame = new JFrame("ImageIcon delegate.");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(lbl);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

我希望任何人都可以重现的错误是ImageIcon 的图像从未绘制在显示的标签/框架中。

当我尝试调试它时,我发现图像加载状态ImageIcon)等于MediaTracker.ABORTED,它委托给目标@987654328 的ImageObserver.ABORT ImageIcon 实例的MediaTracker 的@(实现ImageObserver),而这又与imageUpdate 方法有关(根据ImageObserver 的文档)。

我还注意到,如果我在将图像设置为ImageIcon 之前调用img.getWidth(null);,则图像会正常绘制(即错误停止再现)并且图像加载状态是不是等于MediaTracker.ABORTED了。 在阅读了ImageObserver.imageUpdate 方法的文档后,我看到它指出Image.getWidth(ImageObserver) 方法是异步?...

我还注意到,如果我在调用 img.getWidth(null); 之后调用 img.flush();,则会重现错误。 刷新图像后调用img.getWidth(null); 会阻止错误再现。 这种模式可以重复。 因此,我归结为我在MediaTracker 的文档中找到的可能最重要的信息:

如果在第一帧完成加载后没有ImageObservers 正在观察图像,则图像可能会自行刷新以节省资源(请参阅Image.flush())。

我必须注意,我确实关心事件的顺序。例如,我需要将ImageIcon 创建为空,然后使用setImage 更新其图像。 我这么说是因为,正如我所注意到的,创建ImageIcon 并在构建时为其提供图像似乎可以正确绘制图像而不是重现错误。 在查看了接受ImageImageIcon.setImage 方法的ImageIcon 构造函数的区别后,我看到构造函数还从图像中请求属性'comment',为Image.getProperty 方法提供ImageObserver...所以我也猜它是异步?...

我也调用了System.out.println(img.getClass());,但它最终出现在sun.* 包中的一个类中,带有编译代码并且没有文档。所以我跟不上。

我希望我的调试努力不会误导。

所以我的问题是:

  1. 是什么原因导致图像未在 MRE 中绘制?
  2. 如何在不进行任意调用(例如img.getWidth(null); 之前的setImage)的情况下防止发生此错误?

我还发现了this 相关问题,但我看到ImageIcon 类在内部使用MediaTracker,那么重复代码有什么意义? ImageIcon 中的MediaTracker 中止加载图像(根据其加载状态)的原因我不知道。

编辑 1

仍然重现错误的更现实和更少最小化的 MRE 如下:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Objects;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ImageThumbnail implements Icon {
    private final ImageIcon delegated;
    private final int w, h;
    private String text;

    public ImageThumbnail(final int width, final int height, final String text) {
        w = width;
        h = height;
        this.text = Objects.toString(text).trim();
        delegated = new ImageIcon();
    }

    @Override
    public int getIconWidth() {
        return w;
    }

    @Override
    public int getIconHeight() {
        return h;
    }

    public void setText(final String text) {
        this.text = Objects.toString(text).trim();
    }

    //Scales the given image to fit this icon's width and height maintaining aspect ratio:
    public void setImage(final BufferedImage bimg) {
        final int bimgw = bimg.getWidth(),
                  bimgh = bimg.getHeight();
        if (bimgw > bimgh)
            delegated.setImage(bimg.getScaledInstance(Math.min(w, bimgw), -1, Image.SCALE_SMOOTH));
        else
            delegated.setImage(bimg.getScaledInstance(-1, Math.min(h, bimgh), Image.SCALE_SMOOTH));
        //Alternatively you can try the following code if you don't trust the -1 arguments above:
        //delegated.setImage(bimg.getScaledInstance(w, h, Image.SCALE_SMOOTH));
    }

    @Override
    public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
        delegated.paintIcon(c, g, x, y);
        //Other drawing operations here, like drawing a String, at the center, over the ImageIcon:
        g.drawString(text, x + w / 2 - g.getFontMetrics().stringWidth(text) / 2, y + h / 2);
    }

    private static JPanel centered(final Component c) {
        /*Creating a JPanel with GridBagLayout and a single component
        in it, will layout the component in the center of the JPanel.*/
        final JPanel panel = new JPanel(new GridBagLayout());
        panel.add(c);
        return panel;
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(() -> {

            final ImageThumbnail thumb = new ImageThumbnail(200, 200, "Select an image...");

            final JLabel lbl = new JLabel(thumb);

            final JFileChooser chooser = new JFileChooser();
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            chooser.setMultiSelectionEnabled(false);

            final JButton load = new JButton("Load image");
            load.addActionListener(e -> {
                if (chooser.showOpenDialog(load) == JFileChooser.APPROVE_OPTION) {
                    try {
                        thumb.setImage(ImageIO.read(chooser.getSelectedFile()));
                        thumb.setText("Image-001");
                        lbl.setForeground(Color.GREEN.darker());
                        //lbl.repaint(); //Not needed, as I call 'lbl.setForeground(Color.GREEN.darker());' above...
                    }
                    catch (final IOException iox) {
                        iox.printStackTrace();
                    }
                }
            });

            final JPanel contents = new JPanel(new BorderLayout());
            contents.add(centered(lbl), BorderLayout.CENTER);
            contents.add(centered(load), BorderLayout.PAGE_END);

            final JFrame frame = new JFrame("Thumbnail of images.");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(contents);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }
}

我知道我可以在按钮上使用SwingWorker 来加载图像,这样用户就不会遇到延迟,但为了让事情变得更简单,我更愿意阻止 EDT 一段时间。

【问题讨论】:

    标签: java image swing jlabel imageicon


    【解决方案1】:

    如何在不进行任意调用的情况下防止此错误发生

    使用您的原始代码,我得到了 NPE。

    以下似乎有效。我现在看到一个黑色图像:

    //final ImageIcon ii = new ImageIcon();
    …
    //ii.setImage(img);
    ImageIcon ii = new ImageIcon( img );
    

    编辑:

    我尝试了不同的方法,包括确保标签具有 ImageObserver。没有什么对我有用。

    import java.awt.*;
    import javax.swing.*;
    import javax.swing.text.*;
    import java.awt.image.*;
    
    public class IconMRE extends JPanel
    {
        private ImageIcon icon = new ImageIcon( new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB) );
        private JLabel label1 = new JLabel( icon );
    
        public IconMRE()
        {
            setLayout( new BorderLayout(20, 20) );
    
            label1 = new JLabel( icon );
            add(label1, BorderLayout.LINE_START);
    
            JLabel label2 = new JLabel();
            icon.setImageObserver( label2 );
            label2.setIcon( icon );
            add(label2, BorderLayout.LINE_END);
    
    
            JComboBox<Color>comboBox = new JComboBox<Color>();
            comboBox.addItem( Color.RED );
            comboBox.addItem( Color.GREEN );
            comboBox.addItem( Color.BLUE );
            add(comboBox, BorderLayout.PAGE_START);
    
            comboBox.addActionListener( (e) ->
            {
                Color background = (Color)comboBox.getSelectedItem();
                BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = image.createGraphics();
                g2d.setColor( background );
                g2d.fillRect(0, 0, 200, 200);
                g2d.dispose();
    //          image.getWidth(null);
                icon.setImage( image );
    //          label1.repaint();
            });
    
            comboBox.setSelectedIndex(2);
        }
    
        private static void createAndShowUI()
        {
            JFrame frame = new JFrame("IconMRE");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add( new IconMRE() );
            frame.pack();
            frame.setLocationRelativeTo( null );
            frame.setVisible( true );
        }
    
        public static void main(String[] args)
        {
            EventQueue.invokeLater(new Runnable()
            {
                public void run()
                {
                    createAndShowUI();
                }
            });
        }
    }
    

    唯一有效的方法是在标签上强制执行 repaint()。

    【讨论】:

    • 是的,它确实有效。但正如我在问题中所说,我想先分配一个空的 ImageIcon,然后使用setImage。可以这样做吗?我必须这样做,因为在我拥有的真实代码中,我不预先知道将要使用的图像。它是在用户选择文件后从文件中加载的。我也不想为 ImageIcon 使用设置器,以免每次都分配一个新的 ImageIcon 实例。如果我的要求完全可行,请告诉我。
    • 图像应该是黑色/空白是的。在我拥有的真实代码中,它将被文件中加载的图像替换。
    • 您从哪里获得 NPE?我不是。是img.getWidth(null); 声明吗?
    • ...它是在用户选择文件后从文件中加载的。 - 我不明白为什么在加载之前或之后创建 ImageIcon 会有所不同图片。您的“加载方法”应该只返回一个 ImageIcon(而不是 Image),然后在标签上设置图标。当 getWidth() 语句仍被注释掉时,我得到了 NPE。它位于由 delegate.paintIcon(...) 语句调用的 JDK 代码中。当我取消注释 getWidth() 语句时,它可以正常工作。
    • 基本上,您所说的区别在于 ImageIcon 实例将在其整个生命周期中接受许多图像。此外,这就是ImageIcon.setImage 方法的重点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-22
    • 2021-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 2017-05-26
    相关资源
    最近更新 更多