Reputation: 333
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 JLabel
s 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
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