将选项卡添加到jtabbedpane导致线程Awt-eventqueue-0中的arrayindexOutofBoundSexception当标签需要很长时间才能加载时

Adding tabs to JTabbedPane causes ArrayIndexOutOfBoundsException in thread AWT-EventQueue-0 when tabs take a long time to load

我有以下 SSCCE,它采用 JTabbedPane,并向其添加了 500 个包含 CustomJPanel 的选项卡。请注意,组件 CustomJPanel 是要生成的 "long",因为我(故意)向其添加 100 JLabel

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;

public class Class1 {
    public static void main(String[] args) {

        JFrame window = new JFrame();
        window.setTitle("Parent frame");
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setSize(1200, 800);
        window.setLocationRelativeTo(null);

        window.setVisible(true);

        JTabbedPane jtp = new JTabbedPane();
        window.add(jtp);

        //new Thread(new Runnable() {
        //  public void run() {
                for (int i = 0; i < 500; i++) {

                    jtp.addTab("tab"+i, new CustomJPanel());
                }
        //  }
        //}).start();

    }
}

class CustomJPanel extends JPanel {

    public CustomJPanel() {
        for (int i = 0; i < 100; i++) {
            this.add(new JLabel("test"));
        }
    }

}

当我运行这段代码时,我很有可能得到以下异常:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0
    at javax.swing.plaf.basic.BasicTabbedPaneUI.tabForCoordinate(Unknown Source)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.setRolloverTab(Unknown Source)
    at javax.swing.plaf.basic.BasicTabbedPaneUI.access00(Unknown Source)
    at javax.swing.plaf.basic.BasicTabbedPaneUI$Handler.mouseEntered(Unknown Source)
    at java.awt.Component.processMouseEvent(Unknown Source)
    at javax.swing.JComponent.processMouseEvent(Unknown Source)
    at java.awt.Component.processEvent(Unknown Source)
    at java.awt.Container.processEvent(Unknown Source)
    at java.awt.Component.dispatchEventImpl(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.retargetMouseEnterExit(Unknown Source)
    at java.awt.LightweightDispatcher.trackMouseEnterExit(Unknown Source)
    at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
    at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
    at java.awt.Container.dispatchEventImpl(Unknown Source)
    at java.awt.Window.dispatchEventImpl(Unknown Source)
    at java.awt.Component.dispatchEvent(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access0(Unknown Source)
    at java.awt.EventQueue.run(Unknown Source)
    at java.awt.EventQueue.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.run(Unknown Source)
    at java.awt.EventQueue.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

问题是我在JTabbedPane中快速添加需要很长时间生成的组件时引起的。如果我删除 CustomJPanel 中的 for 循环,使其只包含一个 JLabel,则不会发生异常。请注意,虽然在此示例中异常始终为“0”,但在我的应用中它可以是任何数字。

如果在单独的线程中添加选项卡,则似乎更有可能发生异常。

我发现降低出现此异常的可能性的唯一方法是在添加每个选项卡之间添加几十毫秒的延迟(Thread.Sleep())。不过,还是时有发生。

请问有什么办法可以避免这个异常的发生,或者在控制台中不显示吗?注意我抓不到,因为它在我的代码中没有指向任何东西,都是"unknown source".

编辑: 我已经在 EDT 上将 Class1 的代码更改为 运行:

public class Class1 {
    public static JFrame window;
    public static JTabbedPane jtp;
    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                window = new JFrame();
                window.setTitle("Parent frame");
                window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                window.setSize(1200, 800);
                window.setLocationRelativeTo(null);

                window.setVisible(true);

                jtp = new JTabbedPane();
                window.add(jtp);

                new SwingWorker<Void, Void>() {
                    @Override
                    public Void doInBackground() {
                        for (int i = 0; i < 500; i++) {
                            jtp.addTab("tab"+i, new CustomJPanel());

                        }
                        return null;
                    }

                    @Override
                    public void done() {}

                }.execute();
            }
        });
    }
}

但是,异常还是发生了。我做错了什么吗?

您使用的 doInBackground() 不正确。当您 "in the background" 时,您不在美国东部时间。所以调用 addTab() 修改 UI 是禁止的。

要正确使用 SwingWorker,您必须 publish() 中间结果。 process() 方法在 EDT 上下文中接收发布的结果,它可以在其中安全地修改 UI.

public class Class1 {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(Class1::new);
    }

    Class1() {
        JFrame window = new JFrame();
        window.setTitle("Parent frame");
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setSize(1200, 800);
        window.setLocationRelativeTo(null);

        JTabbedPane jtp = new JTabbedPane();
        window.add(jtp);

        window.setVisible(true);

        new Builder(jtp).execute();
    }
}

class Builder extends SwingWorker<Void, CustomJPanel> {
    JTabbedPane jtp;

    Builder(JTabbedPane _jtp) {
        jtp = _jtp;
    }

    @Override
    protected Void doInBackground() throws Exception {
        for (int i = 0; i < 500; i++) {
            CustomJPanel panel = new CustomJPanel();
            publish(panel);
        }
        return null;
    }

    @Override
    protected void process(List<CustomJPanel> panels) {
        int i = jtp.getTabCount();
        for (CustomJPanel panel : panels) {
            jtp.addTab("tab" + i, panel);
            i++;
        }
    }
}

注意:创建 CustomJPanel extends JPanel 看起来像是在操纵 UI,但实际上并非如此。 JPanel 尚未实现。只有当 JPanel 被添加到已实现的 UI 项目中时,就像 JTabbedPane 一样,它才会真正实现。所以我们实际上可以安全地创建 JPanel,并在工作线程中完成 JLabel 子级等。但是不能添加到工作线程中已实现的UI项。