Wie lässt sich ein Splashscreen mit Fortschrittsanzeige realisieren?

Ab Java 1.6 lässt sich ein Splashscreen durch ein JVM-Argument und mit Hilfe der Klasse SplashScreen erzeugen. Das Beispiel demonstriert, wie das auch auf konventionelle Weise ohne gesonderten Startparameter und mit älteren Java-Versionen gelingt.

Das Beispiel besteht aus insgesamt drei Klassen:

class SplashDemo
Erzeugt als GUI einen einfachen, leeren JFrame und enthält main().
class Splash
Erzeugt das Fenster des Splashscreens
class Timer
Hilfsklasse zur zeitlichen Steuerung des Splashscreens

In den Quelltexten der folgenden Einzeldarstellungen wird aus Gründen der Übersichtlichkeit auf die Angabe der Imports verzichtet. Der ungekürzte Gesamt-Quelltext ist unten zu finden.

public class SplashDemo extends JFrame {

    private static final int SHOW_FOR = 3000;

    public SplashDemo() {
        new Splash("img/rot300x200.png", SHOW_FOR, this);
        this.setVisible(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(500, 400);
        this.setLocationRelativeTo(null);
        this.setTitle("Splash-Demo");
    }

    public static void main(String[] args) {
        new SplashDemo();
    }
}

In der von JFrame abgeleiteten GUI-Klasse der Anwendung wird ein Objekt vom Typ Splash erzeugt. Es liefert das Fensters des Splashscreens und bekommt die folgenden Parameter übergeben:

Neben dieser Instanzierung werden lediglich die üblichen grundlegenden Eigenschaften eines GUI-Fensters, wie Größe, Beenden des Programms bei Schließen des Fensters, Titel, etc. festgelegt. Wichtig: das Fenster muss unsichbar bleiben, da es erst nach Beenden des Splashs sichbar gesetzt werden soll.

class Splash extends JWindow {
    private int min = 0, max = 100;
    private boolean show = true;
    final JProgressBar progressBar = new JProgressBar(min, max);

    public Splash(final String imgPath, final int showFor, final JFrame frame) {

        final Timer timer = new Timer(showFor);
        Thread wRunner = new Thread() {
            public void run() {
                timer.start();
                while (show && timer.counter <= showFor) {
                    Splash.this.setVisible(true);
                }
                Splash.this.setVisible(false);
                Splash.this.dispose();
                frame.setVisible(true);
            }
        };

        final Runnable pbRunner = new Runnable() {
            public void run() {
                System.out.println("running progress bar");
                for (int i = min; i <= max; i++) {
                    try {
                        Thread.sleep(showFor / max);
                    } catch (InterruptedException e) {
                    }
                    progressBar.setValue(i);
                }
            }
        };

        JPanel contentPane = new JPanel();
        this.setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout());
        ImageIcon icon = new ImageIcon(ClassLoader.getSystemResource(imgPath));
        this.setSize(icon.getIconWidth(), icon.getIconHeight());
        contentPane.add(new JLabel(icon, JLabel.CENTER), BorderLayout.CENTER);
        contentPane.add(progressBar, BorderLayout.SOUTH);

        this.setBackground(Color.WHITE);
        this.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                show = false;
                setVisible(false);
                dispose();
            }
        });
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        new Thread(wRunner).start();
        new Thread(pbRunner).start();
    }
}

Die Klasse Splash ist von JWindow abgeleitet, einem rahmenlosen 'undecorated' Fenster. In ihr wird eine Hilfsvariable show auf true gesetzt. Sie bestimmt, dass das Fenster des Splashs sichtbar ist. Zusätzlich wird eine JProgressBar mit einem Start- und einem Endwert initialisiert. Im Konstruktor wird als erstes die zeitliche Steuerung des Splashscreens geregelt. Hierzu wird ein Objekt der Hilfsklasse Timer gebildet. Es bekommt die Dauer der Splash-Darstellung übergeben. In einem eigenen Thread wird in dessen run()-Methode zunächst der Timer gestartet. Solange dessen Zählvariable counter kleiner als die Darstellungszeit ist und die Variable show true ist bleibt der Splashscreen sichtbar. Wird eine dieser Bedingungen false, so bricht die Darstellung des Splash ab und das Hauptfenster wird sichtbar.
In einem weiteren Thread wird dann der Fortschritt der Progressbar ausgeführt. Der Fortschrittsbalken läuft bis zum Ende der Splash-Darstellung.

Der Rest des Quelltextes umfasst den Aufbau des Splash-Fensters, das das Image-Objekt auf einem zentral gesetzten JPanel und im südlichen Borderlayout-Bereich die JProgressBar darstellt. Um den Splash frühzeitig schließen zu können, wird das Fenster bei einem MouseListener angemeldet, über den die Hilfsvariable show auf false gesetzt, der Splash abgebrochen und als Folge das Hauptfenster sichtbar gesetzt wird.
Unmittelbar nach sichtbar setzen des Splashscreens werden am Ende des Konstruktors die o.a. Threads gestartet.

class Timer extends Thread {
    int counter = 0, showFor;

    public Timer(int showFor) {
        this.showFor = showFor;
    }

    public void run() {
        while (counter <= showFor) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter++;
        }
    }
}

Die Klasse Timer ist einfach aufgebaut. Sie ist von Thread abgeleitet und enthält neben dem Konstruktor lediglich die run()-Methode, in der eine Zählvariable bis zum Ablauf der Darstellungsdauer hochgezählt wird. Ihr aktueller Inhalt wird wie oben erläutert vom Splashscreen abgefragt.
Nachfolgend der gesamte Quelltext.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JWindow;

public class SplashDemo extends JFrame {

    private static final int SHOW_FOR = 3000;

    public SplashDemo() {
        new Splash("img/rot300x200.png", SHOW_FOR, this);
        this.setVisible(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(500, 400);
        this.setLocationRelativeTo(null);
        this.setTitle("Splash-Demo");
    }

    public static void main(String[] args) {
        new SplashDemo();
    }
}

class Splash extends JWindow {
    private int min = 0, max = 100;
    private boolean show = true;
    final JProgressBar progressBar = new JProgressBar(min, max);

    public Splash(final String imgPath, final int showFor, final JFrame frame) {

        this.setBackground(Color.WHITE);
        // Mausevent setzt bei Klick die Hilfsvariable show auf false, beendet
        // damit den Splashscreen und &ouml;ffnet das Hauptfenster, sofern es
        // geladen ist.
        this.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                show = false;
                setVisible(false);
                dispose();
            }
        });

        // Splash-Hauptsteuerung
        final Timer timer = new Timer(showFor);
        Thread wRunner = new Thread() {
            public void run() {
                timer.start();
                // Splash zeigen solange er nicht unterbrochen wird (show=false)
                // oder das Anzeigeintervall abgelaufen ist.
                while (show && timer.counter <= showFor) {
                    Splash.this.setVisible(true);
                }
                // Splash beenden
                Splash.this.setVisible(false);
                Splash.this.dispose();
                // Hauptfenster anzeigen
                frame.setVisible(true);
            }
        };

        // Progressbar-Thread
        final Runnable pbRunner = new Runnable() {
            public void run() {
                System.out.println("running progress bar");
                for (int i = min; i <= max; i++) {
                    try {
                        Thread.sleep(showFor / max);
                    } catch (InterruptedException e) {
                    }
                    progressBar.setValue(i);
                }
            }
        };

        // Splash-GUI
        JPanel contentPane = new JPanel();
        this.setContentPane(contentPane);
        contentPane.setLayout(new BorderLayout());
        ImageIcon icon = new ImageIcon(ClassLoader.getSystemResource(imgPath));
        this.setSize(icon.getIconWidth(), icon.getIconHeight());
        contentPane.add(new JLabel(icon, JLabel.CENTER), BorderLayout.CENTER);
        contentPane.add(progressBar, BorderLayout.SOUTH);
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        new Thread(wRunner).start();
        new Thread(pbRunner).start();
    }
} // class Splash

class Timer extends Thread {
    int counter = 0, showFor;

    public Timer(int showFor) {
        this.showFor = showFor;
    }

    public void run() {
        System.out.println("timer running");
        while (counter <= showFor) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter++;
        }
    }
} // class Timer