首页 > 解决方案 > Adding tabs to JTabbedPane causes ArrayIndexOutOfBoundsException in thread AWT-EventQueue-0 when tabs take a long time to load

问题描述

我有以下 SSCCE,它需要一个JTabbedPane,并向其中添加 500 个包含 a 的选项卡CustomJPanel。请注意,组件 ,CustomJPanel是“长”生成的,因为我(故意)JLabel向它添加 100 秒。

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.access$2100(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.access$500(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.awt.EventQueue$3.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.awt.EventQueue$4.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.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需要很长时间才能生成的组件中快速添加组件时会导致问题。如果我删除 for 循环以CustomJPanel使其仅包含 one JLabel,则不会发生异常。请注意,尽管在此示例中异常始终为“0”,但在我的应用程序中它可以是任何数字。

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

我发现减少出现此异常的可能性的唯一方法是Thread.Sleep()在添加每个选项卡之间添加几十毫秒的延迟(使用 )。但是,它仍然不时发生。

有什么办法可以防止这种异常的发生,或者防止它在控制台中显示?请注意,我无法捕捉到它,因为它没有指向我的代码中的任何内容,它都是“未知来源”。

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

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();
            }
        });
    }
}

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

标签: javaswingindexoutofboundsexceptionjtabbedpane

解决方案


您使用doInBackground()不正确。当您在“后台”时,您不在 EDT 上。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带有子等的 , complete 。JLabel但它不能添加到工作线程中实现的 UI 项中。


推荐阅读