Zauberhafte Dialoge

In Betriebssystemen und Anwendungen leiten den Benutzer oft Wizards oder Assistenten durch Folgen von Dialogen. Auch Swing-Entwicklern steht dafür eine Open-Source-Klassenbibliothek zur Verfügung.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 8 Min.
Von
  • Thomas Künneth
Inhaltsverzeichnis

Assistenten (Wizards) erfragen beim Benutzer in mehreren aufeinander folgenden Dialogschritten Informationen, um eine bestimmte Aktion ausführen zu können. Beispielsweise muss eine E-Mail-Anwendung vor dem Anlegen eines neuen Kontos seine Kennung und sein Passwort sowie die E-Mail-Adresse und den Servernamen ermitteln. Installations- oder Setup-Programme benötigen vor dem Kopieren den Pfad des Zielverzeichnisses sowie Installationsart beziehungsweise -umfang. Außerdem können Anwender oft festlegen, ob Symbole auf dem Desktop erscheinen sollen.

Wizards kommen auf allen aktuellen Plattformen vor. Zwar unterscheiden sich die einzelnen Implementierungen im Detail, doch sind bestimmte Merkmale praktisch immer vorhanden. Das wahrscheinlich wichtigste ist (auch wenn es auf den ersten Blick trivial wirkt) die Navigierbarkeit zwischen Dialogschritten mit „Zurück“ und „Weiter“, verbunden mit der Möglichkeit, den Assistenten vorzeitig abzubrechen. Damit dies klappt, sollte das Durchlaufen seiner Seiten keine unwiderruflichen Aktionen auslösen. Die eigentliche Arbeit findet in der Regel erst statt, wenn der Anwender auf „Fertig(stellen)“ klickt. Außerdem zeichnet Assistenten aus, dass ihre Seitenabfolge zum Zeitpunkt des Aufrufs nicht vollständig festgelegt sein muss. Sie kann abhängig von Benutzereingaben variieren. Beispielsweise muss ein Installationsprogramm nur eine Modul- oder Paketauswahl anzeigen, wenn der Anwender nicht die Standardinstallation gewählt hat. Ein solcher Dialogfluss lässt sich nur mit baumartigen Strukturen abbilden. Schließlich hilft oft eine Übersicht über absolvierte und gegebenenfalls noch folgende Schritte dem Benutzer, während der Arbeit mit dem Assistenten den Überblick zu behalten.

Auf wizard.dev.java.net steht Swing-Entwicklern eine Klassenbibliothek zur Verfügung, die das Erstellen und Anzeigen von Assistenten vereinfachen möchte. Das unter der Common Development and Distribution License veröffentlichte Projekt war ursprünglich als Ersatz für die Wizard API von Netbeans gedacht, was sich noch in den verwendeten Paketnamen zeigt. Die ab Java 1.4 lauffähige Bibliothek lässt sich aber in jeder Swing-Anwendung einsetzen. Damit man sie nutzen kann, muss sich die Datei wizard.jar im Klassenpfad befinden, die auf der Projekt-Homepage zum Herunterladen bereitsteht. Dort liegt auch die Javadoc-Dokumentation in Form des Archivs WizardAPI.zip. Die Quelltexte lassen sich gegebenenfalls aus dem zum Projekt gehörenden CVS-Repository auschecken.

Assistenten definiert die Wizard API als eine Folge von Dialogschritten, die Einstellungen oder Eingaben des Benutzers entgegennehmen und als Schlüssel-Wert-Paare in einem Objekt des Typs java.util.Map speichern. Nachdem der Anwender „Fertig“ angeklickt hat, übergibt der Wizard diese Map an eine finish()-Methode. Sie erzeugt ein (im Prinzip beliebiges) Objekt, das die Einstellungen widerspiegelt, und führt die eigentliche Arbeit aus. Dies kann auf Wunsch in einem eigenständigen Thread, also im Hintergrund, geschehen. Neben fest vorgegebenen Panel-Abfolgen sind Verzweigungen vorgesehen, sodass ein Assistent dynamisch auf Benutzereingaben reagieren kann. Da sich Eingaben prüfen lassen, ist es beispielsweise möglich, Seitenwechsel zu unterbinden, bis der Anwender ein bestimmtes Feld ausgefüllt oder ein Element angewählt hat.

Dialogschritte können Entwickler auf zwei Arten bauen. Am einfachsten ist es, von der Klasse org.netbeans.spi.wizard.WizardPage abzuleiten. Ein solches Panel vereinfacht das Übernehmen von Benutzereingaben in die angesprochene Map. Denn für Standardkomponenten wie JCheckBox oder JTextField genügt es, sie einer WizardPage hinzuzufügen und die Methode setName() der Komponente aufzurufen. Dies könnte folgendermaßen aussehen:

WizardPage seite = new WizardPage("schritt-1", "Der erste Schritt");
JTextField eingabe = new JTextField(20);
eingabe.setName("eingabe");
seite.add(eingabe);

Eingegebenen Text übernimmt der Wizard automatisch unter dem Schlüssel eingabe in die Map. Damit das klappt, muss der Aufruf von setName() erfolgen, bevor die Komponente der WizardPage hinzugefügt wird. Komponenten müssen übrigens keine unmittelbaren Kinder der WizardPage sein, sondern dürfen sich auch in verschachtelten Containern befinden.

Einen Assistenten, der aus dieser Seite besteht, erzeugt die folgende Anweisung:

Wizard wizard = WizardPage.createWizard(new WizardPage [] {seite});

WizardDisplayer.showWizard() zeigt den Assistenten an. Mit einem einfachen Cast liefert dieselbe Methode die vom Benutzer eingegebenen Werte:

Map ergebnis = (Map) WizardDisplayer.showWizard(wizard);
System.out.println(ergebnis);

WizardPage enthält zahlreiche Methoden. Das Verhalten des Dialogschritts lässt sich beeinflussen, indem man sie überschreibt. Beispielsweise ruft der Wizard für Benutzereingaben validateContents() auf. Dessen Rückgabewert steuert, ob der Assistent auf die Folgeseite wechseln darf oder ob er den Benutzer auf ein bestehendes Problem hinweist. Auch die Methoden allowNext() und allowFinish() beeinflussen mit ihren Rückgabeparametern die Navigierbarkeit innerhalb eines Assistenten.

InstallerDemo (siehe Abbildung oben) simuliert ein Installationsprogramm für die fiktive Bürosoftware Vapor Office XT. Seine vollständigen Quelltexte liegen auf dem iX-Listingserver (siehe iX-Link). Die Klassen Zusammenfassung und Paketauswahl der Anwendung zeigen das Überschreiben der angesprochenen Methoden. Das Beispiel in Listing 1 entfernt vor dem Zurückspringen auf die vorherige Seite des Assistenten zwei Elemente aus der Map, die die Benutzereingaben speichert. Weitere Klassen dieser Anwendung demonstrieren das zweite Vorgehen beim Erzeugen von Dialogschritten (siehe Listing 2): Die Klasse StartseitePanelProvider leitet von org.netbeans.spi.wizard.WizardPanel Provider ab. Ihre Methode createPanel() ordnet durch eine eindeutige Kennung identifizierbaren Dialogschritten eine Komponente zu, die den Schritt repräsentiert.

Welche Dialogschritte einem WizardPanelProvider zugeordnet sind, legt sein Konstruktor fest. Für StartseitePanelProvider sieht dies folgendermaßen aus:

public StartseitePanelProvider() {
super("Installer-Demo", new String[] {
StartseitePanel.STEP_ID,
LizenzbedingungenPanel.STEP_ID,
ZielverzeichnisPanel.STEP_ID,
InstallationsartPanel.STEP_ID },
new String[] {
StartseitePanel.DESCRIPTION,
LizenzbedingungenPanel.DESCRIPTION,
ZielverzeichnisPanel.DESCRIPTION,
InstallationsartPanel.DESCRIPTION }
);
}

Auch WizardPanelProvider definieren also vorgegebene Abfolgen von Dialogschritten. Bei einem Installationsprogramm ergibt sich aber unter Umständen die Notwendigkeit, Seiten zu überspringen. Beispielsweise sollte eine Paketauswahl nur erscheinen, wenn sich der Anwender für eine individuelle Auswahl der zu installierenden Funktionen entschieden hat.

Zwischen Ästen navigieren

Die Wizard API realisiert Verzweigungen mit der Klasse org.netbeans.spi.wizard.WizardBranchController. Ihre Methode getWizardForStep() entscheidet anhand der gerade angezeigten Seite sowie der bisherigen Benutzereingaben, welche Instanz der Klasse org.netbeans.spi.wizard.Wizard als Nächstes auszuführen ist. Verzweigungen basieren also darauf, Dialogschritte zu Teilassistenten zusammenzufassen und diese zu verschachteln.

In Listing 3 prüft der Code, ob der aktuelle Dialogschritt der Seite Installationsart entspricht. In diesem Fall wählt der Wizard auf Basis der Benutzereingaben den Teilassistenten ZWEIG_PAKETAUSWAHL. Er wurde auf die bekannte Weise erzeugt:

private static final Wizard
ZWEIG_PAKETAUSWAHL = WizardPage.createWizard(new Class[] {
Paketauswahl.class, Zusammenfassung.class },
ERGEBNIS);

ERGEBNIS referenziert eine Instanz der Klasse InstallerDemoResultProducer. Sie implementiert das Interface org.netbeans.spi.wizard.WizardPage.WizardResultProducer. Ihre beiden Methoden finish() und cancel() aktiviert das Anklicken von „Fertig“ beziehungsweise „Abbrechen“. Innerhalb von finish() kann man aus den Werten der Map mit den Benutzereingaben ein Objekt erzeugen, das der Aufrufer des Assistenten als Rückgabeparameter erhält:

public Object finish(Map wizardData)
throws WizardException {
return new InstallerDemoDeferredWizard Result();
}

Eine besondere Form eines solchen Objekts bilden Instanzen der Klasse DeferredWizardResult. Sie ermöglichen das Ausführen länger laufender Tätigkeiten im Hintergrund. Solange sie andauern, zeigt der Assistent einen Fortschrittsbalken an (s. Listing 4). Die Hauptarbeit findet in der Methode start() statt.

Durch Aufrufen der Methode setProgress() erfährt der Anwender den Status der länger dauernden Tätigkeiten. Sind sie erfolgreich abgeschlossen, muss wie üblich ein Rückgabeobjekt erzeugt und an die Methode finish() übergeben werden.

Ist dieses übergebene Objekt eine Instanz des Typs org.netbeans.spi.wizard.Summary, stellt die Wizard API eine Seite dar, die das Ergebnis des Assistenten zusammenfasst (Listing 5). Die Klasse Summary stellt drei Factory-Methoden zum einfachen Erstellen von Zusammenfassungen zur Verfügung.

Die Wizard API ist eine mächtige Klassenbibliothek, die das Erstellen selbst komplexer Assistenten stark vereinfacht. Die Einflussmöglichkeiten auf die Navigation sind umfassend. Länger andauernde Tätigkeiten, auch beim Wechsel zwischen Seiten, können Hintergrundprozesse übernehmen. Insgesamt ist die Nutzung der Bibliothek wärmstens zu empfehlen.

Thomas Künneth
arbeitet als Spezialist für Client-Technologien im Team Anwendungsarchitektur einer großen Bundesbehörde. Neben zahlreichen Artikeln hat er zwei Bücher über Java und Eclipse veröffentlicht.

iX-Link

Mehr Infos

Listing 1: Auszug aus Zusammenfassung.java

@Overridepublic WizardPanelNavResult allowBack(String stepName,
Map settings,
Wizard wizard) {
settings.remove(InstallationsartPanel.RB_ALLES);
settings.remove(InstallationsartPanel.RB_AUSWAHL);
return WizardPanelNavResult.PROCEED;
}
Mehr Infos

Listing 2: Auszug aus StartseitePanelProvider.java

@Override
protected JComponent createPanel(WizardController controller, String id,
Map settings) {
if (StartseitePanel.STEP_ID.equals(id)) {
return new StartseitePanel();
} else if (LizenzbedingungenPanel.STEP_ID.equals(id)) {
return new LizenzbedingungenPanel(controller);
} else if (ZielverzeichnisPanel.STEP_ID.equals(id)) {
return new ZielverzeichnisPanel(controller);
} else if (InstallationsartPanel.STEP_ID.equals(id)) {
return new InstallationsartPanel(settings);
}
return null;
}
Mehr Infos

Listing 3: Auszug aus InstallerDemoBranchController.java

@Override
protected Wizard getWizardForStep(String step, Map settings) {
if (InstallationsartPanel.STEP_ID.equals(step)) {
if (Boolean.TRUE.equals(settings.get(InstallationsartPanel.RB_AUSWAHL))) {
return ZWEIG_PAKETAUSWAHL;
} else if (Boolean.FALSE.equals(settings.get(InstallationsartPanel.RB_AUSWAHL))) {
return ZWEIG_ZUSAMMENFASSUNG;
}
}
return null;
}
Mehr Infos

Listing 4: Auszug aus InstallerDemoDeferredWizardResult.java

@Override
public void start(Map settings, final ResultProgressHandle progress) {
try {
progress.setProgress("Dateien kopieren", 0, 3);
warten();
progress.setProgress("Leistung optimieren", 1, 3);
warten();
progress.setProgress("Installation abschließen", 2, 3);
warten();
progress.finished(getSummary(settings));
} catch (Throwable thr) {
progress.failed(thr.getMessage(), false);
}
}
Mehr Infos

Listing 5: Auszug aus InstallerDemoDeferredWizardResult.java

private Summary getSummary(Map settings) {
StringBuilder sb = new StringBuilder();
sb.append("Die Installation ist abgeschlossen.\n");
if (settings != null) {
Set set = settings.keySet();
Iterator iter = set.iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
sb.append(key + " = " + settings.get(key) + "\n");
}
}
return Summary.create(sb.toString(), settings);
}

(ck)