Добавление вкладок в JTabbedPane вызывает ArrayIndexOutOfBoundsException в потоке AWT-EventQueue-0, когда вкладки занимают много времени для загрузки - PullRequest
0 голосов
/ 26 апреля 2018

У меня есть следующий SSCCE, который берет JTabbedPane и добавляет к нему 500 вкладок, содержащих CustomJPanel. Обратите внимание, что компонент CustomJPanel генерируется «долго», потому что я (намеренно) добавляю к нему 100 JLabel s.

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 так, чтобы он содержал только один 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();
            }
        });
    }
}

Однако, исключение все еще происходит. Что-то я делаю не так?

1 Ответ

0 голосов
/ 26 апреля 2018

Вы используете doInBackground() неправильно. Когда вы находитесь «на заднем плане», вы не находитесь на EDT. Поэтому вызов addTab(), который изменяет пользовательский интерфейс, является верботеном.

Чтобы правильно использовать SwingWorker, необходимо publish() промежуточные результаты. Метод process() получает опубликованные результаты в контексте EDT, где он может безопасно изменять пользовательский интерфейс.

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 может выглядеть так, будто вы манипулируете пользовательским интерфейсом, но на самом деле это не так. JPanel еще не реализован. Только когда JPanel добавлен к реализованному элементу пользовательского интерфейса, например JTabbedPane, он действительно будет реализован. Таким образом, мы можем действительно безопасно создать JPanel, вместе с JLabel children и т. Д. В рабочем потоке. Но его нельзя добавить к реализованным элементам пользовательского интерфейса в рабочем потоке.

...