更改图标不会导致 JTabbedPane 上的重绘

Changing Icon not causing repaint on JTabbedPane

我有一个奇怪的问题。我有一个 JTabbedPane,其中添加了几个 JPanel 作为子项。

其中一个面板有一个 ImageIcon 和一个标签。此 ImageIcon 使用的实际图像会根据关联的 JPanel 中发生的情况定期更改。

这是我的意思的一个例子:

第二个选项卡中的某些事件会导致红色图标发生变化。

当这些事件发生时,我使用以下方法更改图像: http://docs.oracle.com/javase/7/docs/api/javax/swing/ImageIcon.html#setImage(java.awt.Image)

但是在我设置图像之后,实际的选项卡并没有重新绘制。它仅在其他事件(例如鼠标悬停或单击)上重绘。

我原以为更改图标图像会导致它触发重绘任何使用该图标的东西?有什么技巧可以让它发挥作用吗?

我可能会通过创建自定义图标 class 并将 JComponent 传递给它来实现一个 hacky 解决方案,该组件将在图像更改时重新绘制,但是这会带来另一个重新绘制整个 JTabbedPane 的问题每次图标更改或计算图标占用的区域并重新绘制它(这看起来很费力)。

编辑:

这是一个演示我的问题的简单应用程序。假设两个图像位于名为 Image1.png 和 Image2.png.

的 src 根目录中
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

public class JTabbedPaneTest {

    JFrame jFrame;

    JTabbedPane jTabbedPane;
    ImageIcon testIcon;

    BufferedImage image1;
    BufferedImage image2;

    Timer timer;

    public JTabbedPaneTest() throws IOException {

        jFrame = new JFrame();
        jFrame.setMinimumSize(new Dimension(300, 300));

        image1 = ImageIO.read(getClass().getResource("/Image1.png"));
        image2 = ImageIO.read(getClass().getResource("/Image2.png"));

        testIcon = new ImageIcon(image1);

        jTabbedPane = new JTabbedPane();
        jTabbedPane.addTab("Tab 1", testIcon, new JPanel());
        jTabbedPane.addTab("Tab 2", new JPanel());

        jFrame.add(jTabbedPane, BorderLayout.CENTER);

        timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Changing image...");
                if(testIcon.getImage() == image1){
                    testIcon.setImage(image2);
                }
                else {
                    testIcon.setImage(image1);
                }
            }
        });

        timer.setRepeats(true);
        timer.setDelay(1000);
        timer.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTabbedPaneTest application = new JTabbedPaneTest();
                    application.jFrame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.err.println(e.getClass().getSimpleName() + " : " + e.getMessage());
                    System.exit(1);
                }
            }
        });
    }

}

选项卡本身不会自动重新绘制,需要另一个事件来触发绘制。

如果没有绘制,则在更改图标后调用repaint()

    public void actionPerformed(ActionEvent e) {
            System.out.println("Changing image...");
            if (testIcon.getImage() == image1) {
                testIcon.setImage(image2);
            } else {
                testIcon.setImage(image1);
            }
            jTabbedPane.repaint();//calling repaint after icon change
        }

使用JTabbedPane#setIconAt设置新图标。下面是修改后的源代码。

package ant.test;
import javax.imageio.ImageIO;
import javax.swing.*;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

public class JTabbedPaneTest {

    JFrame jFrame;

    JTabbedPane jTabbedPane;
    ImageIcon testIcon1;
    ImageIcon testIcon2;

    BufferedImage image1;
    BufferedImage image2;

    Timer timer;

    public JTabbedPaneTest() throws IOException {

        jFrame = new JFrame();
        jFrame.setMinimumSize(new Dimension(300, 300));

        image1 = ImageIO.read(getClass().getResource("/Image1.png"));
        image2 = ImageIO.read(getClass().getResource("/Image2.png"));

        testIcon1 = new ImageIcon(image1);
        testIcon2 = new ImageIcon(image2);

        jTabbedPane = new JTabbedPane();
        jTabbedPane.addTab("Tab 1", testIcon1, new JPanel());
        jTabbedPane.addTab("Tab 2", new JPanel());

        jFrame.add(jTabbedPane, BorderLayout.CENTER);

        timer = new Timer(0, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Changing image...");
                if(jTabbedPane.getIconAt(0) == testIcon1){
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override public void run() {
                            jTabbedPane.setIconAt(0, testIcon2);
                        }
                    });
                }
                else {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override public void run() {
                            jTabbedPane.setIconAt(0, testIcon1);
                        }
                    });
                }
            }
        });

        timer.setRepeats(true);
        timer.setDelay(1000);
        timer.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTabbedPaneTest application = new JTabbedPaneTest();
                    application.jFrame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.err.println(e.getClass().getSimpleName() + " : " + e.getMessage());
                    System.exit(1);
                }
            }
        });
    }

}

顺便说一下,使用 SwingUtilities#invoke* 方法是一种很好的做法。