Skip to main content
Entwickler Themen
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Nachrichten und UI-Helfer

Neben den FXML-Bindings stellt fx Nachrichten am ViewModel sowie unabhängige Helfer für begrenzte Zahleneingaben und den System-Tray bereit.

Nachrichten am ViewModel

Message enthält Severity, Text und Quelle. ViewModel.addMessage fügt eine Nachricht hinzu:

viewModel.addMessage(new Message(Message.Severity.INFO,
        "Änderungen wurden übernommen.", "desk"));
Severity Verhalten
INFO Speicherung im ViewModel
WARN Speicherung, kein automatischer Log-Eintrag
ERROR Speicherung und SLF4J-Logging
FATAL Speicherung und SLF4J-Logging, kein automatisches Beenden

Der Logger heißt de.neitzel.fx.view.ViewModel. getMessages() liefert eine unveränderliche, aber lebende Liste in Einfügereihenfolge. getLatestMessage() ist die zuletzt hinzugefügte Nachricht. Aktualisiert ein Converter einen vorhandenen Fehler, ändert das dessen Reihenfolge nicht; ERROR/FATAL werden dabei erneut geloggt. Für selbst hinzugefügte Nachrichten gibt es keine öffentliche Remove-/Clear-API. Converter-Fehler werden bei erfolgreicher Konvertierung intern entfernt.

latestMessage ist zusätzlich eine String-Property mit dem letzten Text oder null und kein Model-Attribut:

<DirectBinding control="noticeLabel" property="text"
               path="latestMessage" direction="view"/>

Diese Verwaltung gehört zu ViewModel; beliebige Ersatzobjekte ohne diese Basisklasse erhalten sie nicht automatisch.

Kindnachrichten und Dialoge

Nachrichten komplexer Kindattribute werden auf dem Eltern-ViewModel gespiegelt. Aus street wird beispielsweise home.street. Das ist eine eigene Message-Kopie, die nicht erneut geloggt wird. Änderungen und Entfernen werden nachgeführt. Beim Austausch oder Nullsetzen des Kindes verschwinden dessen Kopien; bereits vorhandene Nachrichten eines neuen Kindes werden übernommen. Zwei Pfade zum selben Kind behalten getrennte Quellenpräfixe; Zyklen kopieren Nachrichten nicht endlos zurück.

Collection-Elemente bilden selbst keinen solchen Nachrichtenpfad. Ein Visit-Fehler erreicht Person beispielsweise dann, wenn dieselbe Address zusätzlich über selectedVisit als komplexes Attribut erreichbar ist.

MessagePopup.showNewMessages(loaded.getViewModel());

Dieser Aufruf registriert einen Listener auf dem JavaFX-Thread. Nur danach neu hinzugefügte Nachrichten öffnen einen nicht blockierenden Dialog über Dialog.show(), mit Text und OK-Button. Bereits vorhandene Nachrichten und reine Textänderungen öffnen keinen neuen Dialog. Eine gespiegelte Kindnachricht zählt als neue Nachricht am Elternobjekt. Der Aufruf sollte einmal erfolgen; es gibt keinen öffentlichen Abmelde-Handle. Ohne diesen Aufruf öffnet die Bibliothek keine Popups von selbst.

Begrenzte Integer-Eingaben

de.neitzel.fx.control.IntegerSpinners.nonNegative(min, max, value) erzeugt einen editierbaren Spinner<Integer> mit inklusiven, nicht negativen Grenzen. Er gehört auf den JavaFX Application Thread.

Spinner<Integer> quantity = IntegerSpinners.nonNegative(0, 100, 1);
quantity.valueProperty().addListener((observable, oldValue, newValue) ->
        System.out.println("Quantity: " + newValue));
container.getChildren().add(quantity);

Das Fragment benötigt javafx.scene.control.Spinner, de.neitzel.fx.control.IntegerSpinners und eine eigene Pane container. Gültige Texteingaben aktualisieren den Wert sofort, ohne Enter oder Fokuswechsel. Nichtziffern und Werte oberhalb der Grenze werden abgewiesen. Leere Eingaben und vorübergehend zu kleine Werte behalten den letzten gültigen Wert und werden beim Fokusverlust zurückgesetzt. Die Pfeiltasten verwenden das normale Spinner-Verhalten.

Negative beziehungsweise vertauschte Grenzen und ein Anfangswert außerhalb des Bereichs werden abgelehnt. getValue() liest den gültigen Wert; programmatisch lässt er sich mit getValueFactory().setValue(...) setzen. Labels, Einheiten und fachliche Prüfung bleiben Aufgabe der Anwendung. Der Helfer erzeugt kein Model-Attribut und kein FXML-Binding.

System-Tray

de.neitzel.fx.tray.TraySupport verbindet eine primäre Stage mit dem nativen AWT-System-Tray. attach und Lebenszyklusmethoden müssen auf dem FX-Thread laufen; die AWT-Initialisierung erfolgt asynchron.

// In Application.start; icon ist ein java.awt.Image.
tray = TraySupport.attach(stage, icon, "My application",
        () -> confirmUnsavedChanges(), Platform::exit);
quitButton.setOnAction(event -> tray.requestQuit());
private TraySupport tray;

@Override
public void stop() {
    if (tray != null) tray.close();
}

Die Fragmente benötigen TraySupport, javafx.application.Platform, ein eigenes Bild, einen Quit-Button und die Bestätigungsfunktion. mayQuit entscheidet über das Beenden; Speichern/Verwerfen/Abbrechen implementiert die Anwendung selbst.

availableProperty() wird erst nach erfolgreicher Installation true; isAvailable() liest diesen Zustand. Solange der Tray verfügbar ist, versteckt Fenster-Schließen die Stage und deaktiviert den impliziten JavaFX-Exit. Normales Minimieren bleibt unverändert. open() zeigt, deminimiert und fokussiert das Fenster.

Das Tray-Menü bietet Open und Quit. requestQuit() stellt zuerst das Fenster wieder her und ruft mayQuit auf. false bricht ab, true entfernt die Registrierung und führt quit aus, typischerweise Platform::exit. close() ist idempotent, entfernt eigene Handler und Icon und stellt den früheren Implicit-Exit-Wert wieder her.

Ist Tray-Unterstützung nicht verfügbar, fehlgeschlagen oder noch ausstehend, versteckt Schließen nicht das einzige Fenster, sondern stellt es wieder her. Deshalb bleibt eine explizite Quit-Aktion in der Anwendung erforderlich. Native Callbacks werden zum FX-Thread vermittelt. Eine erst nach dem Schließen fertiggestellte Registrierung wird unmittelbar entfernt.

Ein Overload nimmt ein eigenes Backend entgegen. Dessen Future liefert eine Registration oder null für nicht unterstützt. Registration.close muss von jedem Thread aus sicher sein. Das ist zugleich der Ansatzpunkt für deterministische Lebenszyklustests; natives Betriebssystemverhalten muss zusätzlich in der Anwendung geprüft werden.

Ein Handle sollte den primären Tray-Lebenszyklus besitzen. Implicit Exit ist global; mehrere unabhängige Handles bilden keine abgestimmte Mehrfensterstrategie. Die API fängt nicht jede betriebssystemspezifische Quit-Aktion ab. Andere Ressourcen bleiben unabhängig davon zu schließen.