6. März 2016

Tutorial – JavaFX Custom Dialog

JavaFX enthält ab Version 8 auch Dialogklassen. Dazu gehören die vorgefertigten Standarddialoge Alert (Warnung), TextInputDialog (Texteingabe) und ChoiceDialog (Auswahl). Über deren Verwendung ist im Internet auch einiges zu finden. Das Erstellen eigener Dialoge wird dagegen kaum beschrieben. Am Beispiel eines Login-Dialogs soll dieser Blog das ändern. Der Beispielcode erstellt die Dialogoberfläche einmal programmatisch und einmal per FXML. Der Beispielcode ist auf Github  zu finden.

Das Basisvorgehen in JavaFX

Für eigene Dialoge bietet sich ein Blick auf die Struktur der vorgefertigten Dialoge an

Klassenstruktur der Dialoge
Klassenstruktur der Java FX Dialoge und der darstellenden DialogPane

Am einfachsten leitet sich der eigene Dialog – wie bei den vorhandenen Dialogen – von der Klasse javafx.scene.control.Dialog<R> ab. <R> ist der Rückgabetyp. Für die Darstellung der Oberfläche inklusive der Buttons ist die DialogPane-Eigenschaft des eigenen Dialogs zu setzen oder zu bestücken. Abschließend benötigt der eigene Dialog noch einen Konverter, um die relvanten Daten aus den Eingabefeldern in ein Rückgabeobjekt zu transferieren.

Der Aufruf einer eigenen Dialogklasse ist dann z.B.

CodedLoginDialog codedDialog = new CodedLoginDialog();
Optional result = codedDialog.showAndWait();
result.ifPresent(login -> System.out.println(login));

Bei Abbruch des Dialogs enthält das Optional keinen Wert, die Ausgabe findet entsprechend nicht statt. Die Javadoc zur Dialog-Klasse zeigt weitere Aufrufvarianten.

Erstellen der Dialogoberfläche

DieDialogPaneeines Dialogs bestimmt das Aussehen.

Dialoglayout
Standardlayout der DialogPane

Das wichtigste dürfte der Content sein. Im Bild ist ihm eine Grid mit Labeln und Eingabefeldern eines Logins zugewiesen.

Diverse optionale Ergänzungen sind möglich:

  • Zusätzlich zum Titel des Dialogs ist noch ein Headertext möglich.
  • Ein Bild/Icon (graphic-Eigenschaft) ist im Bild nicht dargestellt. Dessen Position hängt vom Headertext ab. Ist ein Headertext angegeben, erscheint ein Bild rechts vom Headertext, andernfalls links vom Content.
  • Ein expandierbarer Content sorgt wie im Bild für einen Button, der auf Anforderung den Inhalt des expandable Contents anzeigt.

Programmatische Oberfläche

In der eigenen Dialogklasse ist der Content der DialogPane zu befüllen

this.grid = new GridPane();
...

final DialogPane dialogPane = getDialogPane();
dialogPane.setContent(grid);

...

FXML-Oberfläche

Seit der Version 8 des SceneBuilder bietet dieser die Möglichkeit eine DialogPane grafisch zu erstellen und als FXML-File zu speichern.

Mit Hilfe des FXMLLoader erhält eigenen Dialog die gewünsche DialogPane:

DialogPane dialogPane =
    (DialogPane)FXMLLoader.load(
        getClass().getResource("/fxml/DialogPane.fxml"));

...

setDialogPane(dialogPane);

Buttons ergänzen

Die Buttons eines Dialoges speichert die DialogPane in der Eigenschaft Buttontypes. Es gibt diverse vordefinierte Buttons, die der Buttontype-Collection hinzugefügt werden können. Im Beispiel enthält der Dialog einen Abbruch und einen Ok-Button:

dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);

Obwohl in der DialogPane verborgen, sind die vordefinierten Button im Nachgang noch anpassbar, z.B. der Text des Ok-Buttons

Button okButton = (Button)dialogPane.lookupButton(ButtonType.OK);
    okButton.setText("Login");

Der Konverter

Nach dem Ok soll der Dialog nun auch die Eingaben verpackt in der Ergebnisklasse zurück liefern. Das ist Aufgabe des Konverters, der in eigenen Klassen entsprechend zu definieren ist

setResultConverter((dialogButton) -> {
    ButtonData data =
        dialogButton == null ? null : dialogButton.getButtonData();
    return data == ButtonData.OK_DONE
        ? new Login(usernameField.getText(), passwordField.getText())
        : null;
});

Ist im Beispiel der Dialog mit dem Ok-Button beendet worden, so legt das Lambda einen neues Ergebnisobjekt an und übergibt dem Konstruktur die Daten aus den Eingabefeldern.

Für die Konvertierung benötigt das Lambda referenzen auf die Eingabefelder. Bei der programmatischen Erstellung der Oberfläche sind diese schnell als Attribute der neuen Dialog-Klasse deklariert. Anders verhält es sich, wenn die Oberfläche per FXMLLoader erstellt ist. In diesem Fall muss die Dialogklasse die Referenzen im FXML-Objektbaum suchen. Die gewünschten Elemente können im FXML per Id gekennzeichnet werden

<TextField fx:id="usernameField"...

um anschließend nach dem Laden im Elementbaum der DialogPane gesucht zu werden

final TextField usernameField =
   (TextField)dialogPane.lookup("#usernameField");

Validierung

Die Eingaben eines Dialogs sollten natürlich nur weiter gegeben werden, wenn sie sinnvoll sind. Über Data-Binding könnten nun Hinweise, Markierungen, etc. in den Content eingeblendet werden. Aber auch der Ok-Button sollte nur bei vollständigen Eingaben überhaupt aktiv sein. In Beispiel muss mindestens ein User angegeben sein, bevor ein Login möglich ist.

Zuvor wurde der Text des Ok-Buttons angepasst. Aber auch die übrigen Buttoneigenschaften sind ansprechbar

okButton.setDisable(true);
usernameField.textProperty().addListener(
            (event, oldValue, newValue)
                -> okButton.setDisable(newValue.trim().isEmpty()));

Fazit

Solange das vorgegebene Layout der für die eigenen Zwecke ausreicht, sind eigene Dialoge seit Java 8 recht einfach zu erstellen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.