Экран-заставка Swing с индикатором выполнения

Это мой код заставки,

public class SplashScreen extends JWindow {

private static final long serialVersionUID = 1L;
private BorderLayout borderLayout = new BorderLayout();
private JLabel imageLabel = new JLabel();
private JProgressBar progressBar = new JProgressBar(0, 100);

public SplashScreen(ImageIcon imageIcon) {
    imageLabel.setIcon(imageIcon);
    setLayout(borderLayout);
    add(imageLabel, BorderLayout.CENTER);
    add(progressBar, BorderLayout.SOUTH);
    pack();
    setLocationRelativeTo(null);
}

public void showScreen() {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            setVisible(true);
        }
    });
}

public void close() {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            setVisible(false);
            dispose();
        }
    });
}

public void setProgress(final String message, final int progress) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            progressBar.setValue(progress);
            if (message == null) {
                progressBar.setStringPainted(false);
            } else {
                progressBar.setStringPainted(true);
            }
            progressBar.setString("Loading " + message + "...");
        }
    });
}
}

Из основного метода я вызываю вот так,

public static void main(String[] args) {

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            try {
                UIManager.setLookAndFeel(UIManager
                        .getSystemLookAndFeelClassName());

                SplashScreen splashScreen = new SplashScreen(new ImageIcon("images/splash.jpg"));
                splashScreen.showScreen();

                AppFrame frame = new AppFrame(splashScreen);

            } catch (Exception e) {
                appLogger.error(e.getMessage(), e); 
            }
        }
    });
}

В конструкторе AppFrame я вызываю метод splashScreen.setProgress(msg, val) для обновления индикатора выполнения. Но всплеск не отображается. Он отображается только в конце, когда кадр отображается только на долю секунды, хотя загрузка занимает много времени. Но если я поставлю эти три строчки

SplashScreen splashScreen = new SplashScreen(new ImageIcon("images/splash.jpg"));
splashScreen.showScreen();
AppFrame frame = new AppFrame(splashScreen);

за пределами invokeLater() отображается экран-заставка, и индикатор выполнения хорошо обновляется. Я считаю, что обновления графического интерфейса должны быть в invokeLater. В чем может быть проблема?

Кстати, AppFrame загружает различные панели моего приложения.

Изменить: макет моего AppFrame показан ниже.

public class AppFrame extends JFrame {

public AppFrame(SplashScreen splashScreen) {
    JPanel test = new JPanel();
    test.setLayout(new GridLayout(0, 10));

    splashScreen.setProgress("jlabel", 10);
    for(int i = 0; i < 10000; i++) {
        test.add(new JButton("Hi..." + i));
        splashScreen.setProgress("jbutton", (int)(i * 0.1));
    }
    add(new JScrollPane(test));
    setPreferredSize(new Dimension(800, 600));
    pack();
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    setLocationRelativeTo(null);
    splashScreen.setProgress("complete", 100);
    setVisible(true);
}
}

person Praveen    schedule 13.08.2012    source источник
comment
Я считаю, что обновления графического интерфейса должны быть в invokeLater. Или invokeAndWait. Но сделайте запись, я ожидаю, что вы обнаружите, что время между завершением splashScreen.showScreen(); и AppFrame frame = new AppFrame(splashScreen); намного меньше, чем вы ожидаете. Также обратите внимание, что лучшие всплески строго AWT (без Swing).   -  person Andrew Thompson    schedule 13.08.2012
comment
Это означает, что я должен использовать Window или Frame вместо JWindow? Спасибо.   -  person Praveen    schedule 13.08.2012
comment
Да, используйте Window или Frame. Также обратите внимание на MediaTracker/Canvas и EventQueue - не должно быть никакого импорта или классов Swing, или весь пакет Swing загружается первым.   -  person Andrew Thompson    schedule 13.08.2012
comment
Также рассмотрите заставку java-web-start.   -  person trashgod    schedule 13.08.2012
comment
хм... неужели создание пользовательского интерфейса занимает так много времени? Если нет, извлеките реальные временные свиньи (!ui = !EDT) и подготовьте их в doInBackground SwingWorker, опубликовав промежуточный прогресс во всплывающей заставке и закройте приложение заставки/показа в готовом виде.   -  person kleopatra    schedule 14.08.2012
comment
@kleopatra Следует ли использовать EDT для всех работ, связанных со свингом, включая add(comp), new JPanel() и т. д., или только для setVisible(), repaint() и т. д.   -  person Praveen    schedule 14.08.2012
comment
см. docs.oracle.com/javase/tutorial/uiswing/concurrency/index .html - но я сомневаюсь, что создание пользовательского интерфейса действительно является узким местом   -  person kleopatra    schedule 14.08.2012
comment
@kleopatra Спасибо. Но если вы посмотрите на фиктивный код AppFrame, который я предоставил, загрузка занимает некоторое время (около 10000 объектов), а индикатор выполнения не обновляется. Как вы думаете, в чем может быть проблема? Между тем, я также найду время выполнения в моем реальном коде.   -  person Praveen    schedule 14.08.2012
comment
зачем вам десятки тысяч компонентов? Это не обычный реальный сценарий...   -  person kleopatra    schedule 14.08.2012


Ответы (3)


invokedLater вызывается из другого invokeLater. runnable будет выполняться только после завершения выполнения первого.

Вы должны изменить код Splashscreen следующим образом:

...
private void runInEdt(final Runnable runnable) {
    if (SwingUtilities.isEventDispatchThread())
        runnable.run();
    else
        SwingUtilities.invokeLater(runnable);
}

public void showScreen() {
    runInEdt(new Runnable() {
        public void run() {
            setVisible(true);
        }
    });
}

public void close() {
    runInEdt(new Runnable() {
        public void run() {
            setVisible(false);
            dispose();
        }
    });
}

public void setProgress(final String message, final int progress) {
    runInEdt(new Runnable() {
        public void run() {
            progressBar.setValue(progress);
            if (message == null)
                progressBar.setStringPainted(false);
            else
                progressBar.setStringPainted(true);
            progressBar.setString("Loading " + message + "...");
        }
    });
}
person gontard    schedule 13.08.2012
comment
Я попробовал ваше решение. Поскольку он уже находится в EDT, иначе часть «runInEdt» не будет выполняться. В любом случае у меня все та же проблема. Индикатор выполнения не обновляется в EDT. Я использовал это как пример и просто добавлено invokeLater в main(). Без invokeLater работает нормально. - person Praveen; 13.08.2012
comment
Метод runInEDT гарантирует, что ваш экран-заставка является потокобезопасным. Его можно вызывать из любого потока. Я не понимаю, почему мое решение не работает для вас. Возможно, вы могли бы предоставить более подробную информацию о содержимом конструктора AppFrame. - person gontard; 14.08.2012
comment
Пожалуйста, посмотрите редактирование, в котором есть макет моего AppFrame. В основном я загружаю все свои компоненты приложения в этот конструктор. Поэтому я хочу обновить индикатор выполнения во время загрузки компонентов. Но он не отображается, хотя загрузка занимает довольно много времени. - person Praveen; 14.08.2012
comment
Вы уверены, что это притворное поведение эквивалентно реальному? Я имею в виду, что обычно время загрузки не связано с инициализацией графического интерфейса. - person gontard; 14.08.2012
comment
Спасибо, это произошло из-за того, что код дао одного члена команды работал в EDT. - person Praveen; 15.08.2012

Хм, взгляните на этот рабочий образец, который я собрал, который использует класс SplashScreen (использует только простые Timer и ActionListener для увеличения значения ProgressBar до 100, и показан кадр):

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionListener;
import javax.swing.*;

public class SplashScreen extends JWindow {

    private static JProgressBar progressBar = new JProgressBar();
    private static SplashScreen splashScreen;
    private static int count = 1, TIMER_PAUSE = 25,PROGBAR_MAX=100;
    private static Timer progressBarTimer;
    ActionListener al = new ActionListener() {

        @Override
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            progressBar.setValue(count);
            System.out.println(count);
            if (PROGBAR_MAX == count) {
                splashScreen.dispose();//dispose of splashscreen
                progressBarTimer.stop();//stop the timer
                createAndShowFrame();
            }
            count++;//increase counter

        }
    };

    public SplashScreen() {
        createSplash();
    }

    private void createSplash() {
        Container container = getContentPane();

        JPanel panel = new JPanel();
        panel.setBorder(new javax.swing.border.EtchedBorder());
        container.add(panel, BorderLayout.CENTER);

        JLabel label = new JLabel("Hello World!");
        label.setFont(new Font("Verdana", Font.BOLD, 14));
        panel.add(label);

        progressBar.setMaximum(PROGBAR_MAX);
        container.add(progressBar, BorderLayout.SOUTH);


        pack();
        setLocationRelativeTo(null);
        setVisible(true);

        startProgressBar();
    }

    private void startProgressBar() {
        progressBarTimer = new Timer(TIMER_PAUSE, al);
        progressBarTimer.start();
    }

    private void createAndShowFrame() {
        JFrame frame = new JFrame();
        frame.setSize(500, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                splashScreen = new SplashScreen();
            }
        });
    }
}
person David Kroukamp    schedule 13.08.2012

    Thread l_splash_thread = new Thread(
            new SplashScreen ());
    l_splash_thread.start();

    try {
        l_splash_thread.join();
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    }

Я думаю, что таким образом вы можете держать свой экран-заставку.

person Java_Alert    schedule 14.08.2012