Zezombye
Zezombye

Reputation: 333

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

I have the following SSCCE, which takes a JTabbedPane, and adds 500 tabs containing a CustomJPanel to it. Note that the component, CustomJPanel, is "long" to generate, because I (purposely) add 100 JLabels to it.

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

}

When I run this code, I have a high chance to get the following exception:

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)

The problem is caused when I quickly add components in a JTabbedPane that take a long time to generate. If I remove the for loop in CustomJPanel such that it only holds one JLabel, the exception doesn't happen. Note that, although in this example the exception is always "0", in my app it can be any number.

The exception seems to happen with a higher probability if the tabs are added in a separate thread.

The only way I found to reduce the probability of getting this exception is to add a delay (with Thread.Sleep()) of a few dozen ms between adding each tab. However, it still happens from time to time.

Is there any way to prevent this exception from occuring, or to prevent it from displaying in the console? Note that I can't catch it, because it doesn't point to anything in my code, it's all "unknown source".

Edit: I have changed the code of Class1 to run on the 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();
            }
        });
    }
}

However, the exception still happens. Is there anything I am doing wrong?

Upvotes: 1

Views: 304

Answers (1)

AJNeufeld
AJNeufeld

Reputation: 8705

You are using doInBackground() incorrectly. When you are "in the background" you are not on the EDT. So calling addTab(), which modifies the UI, is verboten.

To use the SwingWorker properly, you must publish() the intermediate results. The process() method receives the published results in the EDT context, where it can safely modify the 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++;
        }
    }
}

Note: Creating the CustomJPanel extends JPanel might look like you are manipulating the UI, but actually you are not. The JPanel is not yet realized. Only when the JPanel is added to a realized UI item, like the JTabbedPane, will it actually be realized. So we can actually safely create the JPanel, complete with the JLabel children etc. in the worker thread. But it cannot be added to the realized UI items in the worker thread.

Upvotes: 1

Related Questions