为什么这个 JLabel 的 ImageIcon 没有更新?

Why is this JLabel's ImageIcon not updating?

SSCCE,在保持所有逻辑顺序相同的情况下尽可能小:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    BufferedImage img = null; // <-- needs this scope
    JFrame mainWindow = new JFrame();
    JLabel mainImage = new JLabel();

    public Test() {
        mainWindow.add(mainImage);
        mainWindow.setVisible(true);
        mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// step 5
        mainImage.addMouseListener(new MouseListener() {
            @Override public void mouseClicked(MouseEvent e) {
                dostuff();
            }
            @Override public void mouseEntered(MouseEvent e) {}
            @Override public void mouseExited(MouseEvent e) {}
            @Override public void mousePressed(MouseEvent e) {}
            @Override public void mouseReleased(MouseEvent e) {}
        });
        dostuff();
    }

    private void dostuff() {
// step 1
        try {
            JFileChooser fc = new JFileChooser();
            fc.showOpenDialog(null);
            File file = fc.getSelectedFile();
            img = ImageIO.read(file);
        } catch (Exception e) {
            e.printStackTrace(); 
            System.exit(1);
        }
//step 2
        mainImage.setIcon(new ImageIcon(img));
        mainWindow.pack();
        mainWindow.setLocationRelativeTo(null);
        Graphics2D g = img.createGraphics();
        g.setColor(new Color(0xFFFF0000));
        g.drawOval(10, 10, 10, 10);
        try{Thread.sleep(2000);}catch(Exception e){}
// step 3
        BufferedImage img2 = new BufferedImage(400,300,BufferedImage.TYPE_INT_RGB);
        for (int i = 10 ; i < 20 ; i++) {
            for (int j = 10 ; j < 20 ; j++) {
                img2.setRGB(i,j,0xFF0000FF);
            }
        }
// step 4
        mainImage.setIcon(new ImageIcon(img2));
        mainWindow.pack();
        mainWindow.setLocationRelativeTo(null);
    }

}

我想做什么应该很明显,编译会显示它没有这样做。但是我希望这个 post 有一个问号所以这里是问题描述和问题:

我想要发生的事情:

  1. 程序加载,并提示用户select一个文件(一张图片)。

  2. 在 selecting 图像时,该图像显示在 JFrame 中,并且在其上绘制一些 Graphics2D 绘图。 (我包含了 sleep() 因为这些绘图在实际程序中需要一段时间)

  3. 绘图完成后,将创建一个新图像并在其上绘制。

  4. 新图片替换JFrame中的旧图片

  5. 当用户点击图片时,会提示他们select一张新图片,我们从第一步开始重复。

发生了什么:

第一步到第四步工作正常。在第五步,在 select 图像之后,JFrame 由一个黑色矩形填充,该矩形大小与用户 select 编辑的图像相同,左上角覆盖了第三步中的图像,步骤第二步没有发生,第三步到第五步似乎工作正常。

更重要的是,在我的实际程序中,我可以通过第三步产生的输出告诉新用户-selected-image 正在很好地完成它的工作。

所以问题是,我如何才能修复第二步的后续重复,以便在 JFrame 中显示适当的图像?

编辑:这张 imgur 相册逐步显示了我得到的结果。 http://imgur.com/a/xW051

EDIT2:更新后没有 Thread.sleep()

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    BufferedImage img = null; // <-- needs this scope
    JFrame mainWindow = new JFrame();
    JLabel mainImage = new JLabel();

    public Test() {
        mainWindow.add(mainImage);
        mainWindow.setVisible(true);
        mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// step 5
        mainImage.addMouseListener(new MouseListener() {
            @Override public void mouseClicked(MouseEvent e) {
                dostuff();
            }
            @Override public void mouseEntered(MouseEvent e) {}
            @Override public void mouseExited(MouseEvent e) {}
            @Override public void mousePressed(MouseEvent e) {}
            @Override public void mouseReleased(MouseEvent e) {}
        });
        dostuff();
    }

    private void dostuff() {
// step 1
        try {
            JFileChooser fc = new JFileChooser();
            fc.showOpenDialog(null);
            File file = fc.getSelectedFile();
            img = ImageIO.read(file);
        } catch (Exception e) {
            e.printStackTrace(); 
            System.exit(1);
        }
//step 2
        mainImage.setIcon(new ImageIcon(img));
        mainWindow.pack();
        mainWindow.setLocationRelativeTo(null);
        Graphics2D g = img.createGraphics();
        g.setColor(new Color(0xFFFF0000));
        for (int h = 0 ; h < 0xFF ; h++) {
            for (int i = 0 ; i < img.getWidth() ; i++) {
                for (int j = 0 ; j < img.getHeight()/2 ; j++) {
                    img.setRGB(i,j,0x88FF0000 + h);
                }
            }
            mainImage.repaint();
        }
// step 3
        BufferedImage img2 = new BufferedImage(400,300,BufferedImage.TYPE_INT_RGB);
        for (int i = 10 ; i < 20 ; i++) {
            for (int j = 10 ; j < 20 ; j++) {
                img2.setRGB(i,j,0xFF0000FF);
            }
        }
// step 4
        mainImage.setIcon(new ImageIcon(img2));
        mainWindow.pack();
        mainWindow.setLocationRelativeTo(null);
    }

}

编辑:

If I then load a 1000x1000 image, I see a 1000x1000 black square...

我猜黑色图像是因为您导致事件调度线程 (EDT) 休眠。因此,尽管框架正在使用 pack() 语句调整大小,但新加载的图像并未被绘制。

不要在 EDT 上执行的代码中使用 Thread.sleep()。从侦听器执行的所有代码都在 EDT 上执行。相反,您应该使用 Swing Timer 来安排更新标签图像的事件。

编辑 2:

阅读 Concurrency 上的 Swing 教程部分。通常,所有事件代码都在 EDT 上执行。 EDT被阻塞时,Swing组件无法重绘

  mainImage.setIcon(new ImageIcon(img));

上述语句将导致在 "mainImage" 标签上调用 repaint()。 repaint() 请求被传递给 RepaintManager 并在 EDT 的末尾添加一个绘画请求。

因此标签将在 "doStuff()" 方法中的所有代码执行完毕后绘制。

然而你也调用

 mainImage.setIcon(new ImageIcon(img2));

在方法的最后。所以当图像实际绘制时,它的图标已经第二次更改,所以只绘制了第二个图标。

在这两个语句之间,在读取图像后调用 pack() 方法时调整框架的大小。我相信这是因为框架的打包导致 OS 级别绘制,因为框架是 OS 小部件而不是 Swing 组件。换句话说,即使框架上的组件没有重新绘制,框架也可以调整大小。

如果您想要响应式 GUI,则不能在 EDT 上执行长 运行ning 代码。在第二个 SSCCE 的情况下,您添加的 "for loop" 需要很长时间 到 运行 这有效地阻止了 EDT 并防止绘制新读取的图标。

因此长 运行ning 任务应该在单独的 Thread 上执行。并发教程将解释如何使用 SwingWorker 执行长 运行ning 任务。