Einige häufig verwendete Controls

1. Button, Label, etc. mit Text und Grafik

Text und Grafik können frei plaziert werden (siehe im folgenden Link):
How to add an image to a button (and position it) in JavaFX – Eden Coding

2. CheckBox

Deklaration:
CheckBox cbxEditable = new CheckBox("Editable");

Zustand abfragen:
if (cbxEditable.isSelected()) { …​ }

Zustand setzen:
cbxEditable.setSelected(true);

Reagieren auf Klicken (bzw. Selektionsänderungen):
cbxEditable.setOnAction(event -> { …​ });

3. RadioButton

Diese werden fast immer in Gruppen (ToggleGroup) verwendet, wodurch immer nur ein einziger RadioButton selektiert sein kann (Bei Selektion eines anderen der Gruppe verliert der bisherige diesen Status). Sie haben (optional) einen Text, der üblicherweise rechts daneben angezeigt wird.

Die ToggleGroup hat keinerlei Bezug zu eventuellen Containern, in denen die RadioButtons zusammengefasst sind, die RadioButttons sind also beliebig "verschaltbar"!

Alle Nodes, also auch die RadioButtons, können disabled/enabled und sichtbar/unsichtbar geesetzt werden:
setDisable(trueOrFalse) bzw. setVisible(trueOrFalse).
Abfrage mit isDisable() bzw. isVisible().

    public HBox demoRadioButtons() {
        RadioButton rbtWorkday = new RadioButton("Workday");
        RadioButton rbtWeekend = new RadioButton("Weekend");
        RadioButton rbtHoliday = new RadioButton("Holiday");

        ToggleGroup tggDayKinds = new ToggleGroup();

        rbtWorkday.setToggleGroup(tggDayKinds);
        rbtWeekend.setToggleGroup(tggDayKinds);
        rbtHoliday.setToggleGroup(tggDayKinds);

        for (Toggle toggle : tggDayKinds.getToggles()) {  // iterieren über alle Mitglieder
            RadioButton rbt = (RadioButton) toggle;      // casten von Toggle auf RadioButton
            if (rbt.isSelected()) {                     // check of selektiert,
                System.out.format("--> RadioButton '%s' IS selected%n",
                        rbt.getText());               // Beschriftung auslesen
            } else {
                System.out.format("RadioButton '%s' NOT selected%n", rbt.getText());
            }
        }
        RadioButton rbt = ((RadioButton) tggDayKinds.getSelectedToggle()); // selektiertes Toggle
        if (rbt == null) {                          // kann null sein, wenn gar nichts selektiert
            System.out.println("NO Radiobutton is selected, selecting 1st!");
            tggDayKinds.getToggles().get(0).setSelected(true);  // ersten selektieren
        } else {
            System.out.format("Direct way: RadioButton '%s' is selected%n", rbt.getText());
        }
        // Selektieren im Code (gültigen Mitglied-RadioButton oder null: keine Selektion):
        tggDayKinds.selectToggle(null);  // oder  tggDayKinds.selectToggle(rbtHoliday);

        // Reagieren auf Änderungen:
        tggDayKinds.selectedToggleProperty().addListener((observable, oldSelRb, newSelRb) -> {
            String txtOldSelRb = oldSelRb == null ? "NULL" : ((RadioButton) oldSelRb).getText();
            String txtNewSelRb = newSelRb == null ? "NULL" : ((RadioButton) newSelRb).getText();
            System.out.format("Selektion geändert von '%s' auf '%s'%n", txtOldSelRb, txtNewSelRb);
        });

        // Reagieren einzelner RadioButtons mit setOnAction(...) (nur wenn neu selektiert!):
        rbtHoliday.setOnAction(event -> {
            System.out.format("Hurra, Feiertag!%n");
        });

        return new HBox(10, rbtWorkday, rbtWeekend, rbtHoliday);
    }

4. TextArea

4.1. Häufig genutzte Attribute und Methoden

  • aTextArea.setEditable(boolVal1)

  • aTextArea.setWrapText(boolVal2) …​ dynamischer Zeilenumbruch

4.1.1. Selektieren von Text (im Code)

Hier ein Ausschnitt der Demo-App Lab12Rx. txaFigures ist eine TextArea. In dieser soll der übergebene Text toSelect lokalisiert und wenn vorhanden selektiert werden.

    public void selectInTxaFigures(String toSelect) {
        String content = guiBase.getTxaFigures().getText();
        TextArea txaFigures = guiBase.getTxaFigures();
        if (toSelect == null) {
            System.out.println("nothing to select");
            txaFigures.deselect();
        } else {
            int startIdx = content.indexOf(toSelect);
            if (startIdx >= 0) {
                int endIdx = startIdx + toSelect.length();
                txaFigures.selectRange(startIdx, endIdx);
            } else {
                txaFigures.deselect();
            }
            txaFigures.requestFocus();
        }
    }

Bei Textinhalt "Es ist ein schöner Tag" Mit txaFigures.deleteText(3,6); würde der der Bereich "ist" entfernt werden.

Mit txaFigures.insertText(3, "war"); würde der Inhalt zu "Es war ein schöner Tag".

5. ListView

ListView benötigt eine Datenquelle. Diese muss vom Typ ObservableList<E> sein.

ObservableList<E> implementiert u.a. das Interface List<E>, hat also alle Möglichkeiten einer "normalen" Liste. Sie wird z.B. in getChildren() aller Pane-Subklassen, in der TabPane, in MenuBar, und fast überall, wo in JavaFX Listen erforderlich sind, verwendet.

Zum Erstellen wird eine der vielen Factory-Methoden von FXCollections genutzt.

Klasse FXCollections. kann viele Arten von ObservableList liefern. Zwei wichtige davon sind:

  • FXCollections.observableList(myList) …​ liefert eine "wrapping" ObservableList – eine observable Hülle um die übergebene Klasse.

  • FXCollections.observableArrayList(myList) …​ liefert eine neue Liste, die eine Kopie aller Einträge enthält.

Ein wichtiger Aspekt dabei ist, dass im ersten Fall (wrapping) Listenänderungen automatisch auf beiden Seiten vorhanden sind.
Der Nachteil: die "Engine", die zugrundeliegende Liste (myList) verwaltet, hat keine Kontrollmöglichkeiten wie z.B. Vermeidung von Doubletten, invaliden Elementen, etc. .

Allerdings gibt es dieses Problem in jedem Fall, da in beiden Listenarten die Elemente die SELBEN sind, d.h. eine Attribut-Änderung ist automatisch auf "beiden Seiten" wirksam, wodurch ebenfalls eine Doublette oder invalide Eigenschaftswerte entstehen können, ohne dass die Engine Kontrolle darüber hat.

Einen Ausweg bietet die Verwendung sogenannter DTOs (Data Transfer Objects), die eine minimalistische Kopie der Datenobjekte darstellen, die zwischen Model und GUI zum Transfer eingesetzt wird. Seit Java 14 gibt es als sehr effizienten Ansatz dafür Record Classes.

package my.scribble01;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.ArrayList;
import java.util.List;

public class ListViewScribble1 {

    public static void main(String[] args) {
        demoWrappingObsList();
        System.out.println();
        demoCopyingObsList();
    }

    public static void demoWrappingObsList() {
        List<String> myList = new ArrayList<>();
        ObservableList<String> obsList = FXCollections.observableList(myList);
        System.out.println("Demo 'WRAPPING obsList': 'FXCollections.observableList(myList)'");
        System.out.format("at start: myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
        myList.add("Hi");
        System.out.format("after add by 'myList': myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
        obsList.add("there");
        System.out.format("after add by 'obsList': myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
    }

    public static void demoCopyingObsList() {
        List<String> myList = new ArrayList<>();
        ObservableList<String> obsList = FXCollections.observableArrayList(myList);
        System.out.println("Demo 'COPYING obsList': 'FXCollections.observableArrayList(myList)'");
        System.out.format("at start: myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
        myList.add("Hi");
        System.out.format("after add by 'myList': myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
        obsList.add("there");
        System.out.format("after add by 'obsList': myList-size=%d, obsList-size=%d%n",
                myList.size(), obsList.size());
    }
}

Die Ausgabe ist:

Demo 'WRAPPING obsList': 'FXCollections.observableList(myList)'
at start: myList-size=0, obsList-size=0
after add by 'myList': myList-size=1, obsList-size=1
after add by 'obsList': myList-size=2, obsList-size=2

Demo 'COPYING obsList': 'FXCollections.observableArrayList(myList)'
at start: myList-size=0, obsList-size=0
after add by 'myList': myList-size=1, obsList-size=0
after add by 'obsList': myList-size=1, obsList-size=1

5.1. Darstellung der Listen-Elemente

Normalerweise wird die toString()-Methode des jeweiligen Objekts aufgerufen.

Wenn andere Darstellung gewollt ist, kann eine eigene CellFactory beigesteuert werden mit
myListView.setCellFactory(..).
Diese liefert auf Anforderung (Methode call(…​)) die darzustellenden Elemente der Klasse ListCell.

ListCell-Objekte können sowohl Test als auch Graphik enthalten.

Am einfachsten verständlich ist die Darstellung als Lambda-Ausdruck:

    private ListView<Person> lsvPers;

    public void demoMethode() {
        lsvPers.setCellFactory(param -> {
            return new ListCell<>();
        });
    }

Allerdings entstehen durch obigen Code nur leere Einträge. Zur Änderung muss eine Subklasse von ListCell gebildet werden (hier als anonyme innere Klasse). Die erste Version unten zeigt das Prinzip, darunter eine sinnvolle Implementation, die auch den oft nicht verwendeten Lambda-Parameter lsvPerson nutzt, um jede Person im ListView zu lokalisieren und ihren Index zur Nummerierung zu verwenden:

        lsvPers.setCellFactory((ListView<Person> lsvPerson) -> {
            return new ListCell<>() {
                @Override
                protected void updateItem(Person person, boolean empty) {
                    super.updateItem(person, empty);
                    setText("DUMMY: " + LocalTime.now());
                }
            };
        });

        //--------  hier eine sinnvolle Implementation: --------

        lsvPers.setCellFactory((ListView<Person> lsvPerson) -> {
            return new ListCell<>() {
                @Override
                protected void updateItem(Person person, boolean empty) {
                    super.updateItem(person, empty);
                    if (empty || person == null) {
                        setText(null);
                    } else {
                        List<Person> persList = lsvPerson.getItems(); //often lsvPers not used at all!
                        int persIdx = persList.indexOf(person);    // to number the entries!
                        setText(String.format("%2d. %s, geb. %s", persIdx+1,
                                person.getName(), person.getBirthDate()));
                    }
                    // auch möglich: setGraphic(anyNode);
                }
            };
        });

5.2. Selektieren eines Eintrags, alle deselektieren

angenommen, wir haben ListView<Person> lsvPers = new ListView<>();
und Person person1:

Selektieren von person1: lsvPers.getSelectionModel().select(person1);

Deselektieren aller Einträge: …​select(null);

5.3. Mehrfachselektion, Focus

TODO: folgt

5.4. Beispiel mit häufig genutzten Funktionalitäten

    // im View (z.B. GuiRoot):
    private ListView<Person> lsvPersons;

    public ListView<Person> setupListView() {
        lsvPersons = new ListView();
        lsvPersons.setId("lsvPersons");  // für CSS etc.
        lsvPersons.setPrefSize(250, 400);

        somePane.add(lsvPersons);
    }

    // im Controller (z.B. GuiRootController):
    public void smartifyListView() {
        ListView<Person> lsvPersons = guiRoot.getLsvPersons();  // wir sind im Controller!
        lsvPersons.setPrefSize(250, 400);
        ObservableList<Person> obsvPersonList = FXCollections.observableList(engine.getPersons());
        lsvPersons.setItems(obsvPersonList);

        lsvPersons.getSelectionModel().selectedItemProperty().addListener(
                (ChangeListener<Person>) (observable, oldPerson, newPerson) -> {
                    System.out.println("ListView selection changed from oldPerson = "
                            + oldPerson + " to newPerson = " + newPerson);
                    updatePersonDetailsForm(newPerson);
                });

        // Optional: setzen eigene CellFactory (z.B. stat. innere Klasse wie nachfolgend):
        lsvPers.setCellFactory(new PersonCellFactory());   // comment out to see the difference

    }

    // Optional: Definition eigene "CellFactory" - Beliebige Person-Ausgabe statt nur toString()
    public static class PersonCellFactory implements Callback<ListView<Person>, ListCell<Person>> {

        // could also have constructor with parameters, several fields, etc.!

        @Override
        public ListCell<Person> call(ListView<Person> lsvPers) {
            return new ListCell<>() {  // anonymous inner class
                @Override
                public void updateItem(Person person, boolean empty) {
                    super.updateItem(person, empty);
                    if (empty || person == null) {
                        setText(null);
                    } else {
                        List<Person> persList = lsvPers.getItems(); //often lsvPers not used at all!
                        int persIdx = persList.indexOf(person);  // to number the entries!
                        setText(String.format("%2d. %s, geb. %s", persIdx+1,
                                person.getName(), person.getBirthDate()));
                    }
                }
            };
        }
    }

Anmerkungen:

  • lsvPersons.setItems(…​) benötigt eine oben erläuterte, JavaFX-spezielle ObservableList<>.

5.5. Selection und Focus

https://programmingtipsandtraps.blogspot.com/2014/10/setting-preferred-width-of-listview-to.html [Programming Tips and Traps: Setting the preferred width of a ListView to fit its items in JavaFX] …​ 2023-05-20

https://www.turais.de/how-to-custom-listview-cell-in-javafx/ [Custom ListCell in a JavaFX ListView] …​ 2023-05-23 (RX: with FXML !) https://github.com/turais/TuraisJavaFxExamples [turais/TuraisJavaFxExamples: Just some small JavaFx-Examples] …​ 2023-05-23

6. TextArea - Spezielles

Zum Anpassen des Text-Hintergrundes muss man wissen, dass der eigentliche Text in einer eingebetteten ScrollPane liegt. Da die entsprechenden Objekte nur recht mühsam erreichbar sind, ist CSS die beste Wahl. Man muss mit einem externen CSS-File arbeiten, da sonst ebenfalls das mühsame Erreichen der einzelnen Objekte anfällt:

#my-text-area .content {
   -fx-background-color: lightyellow;
}

Zur Erreichung von Transparenz (Sichtbareit z.B. des Container-Hintergrundes) müssen ALLE verschachtelten Objekte transparent gemacht werden:

#my-text-area {
    -fx-background-color: rgba(53,89,119,0.4);
}

#my-text-area .scroll-pane {
    -fx-background-color: transparent;
}

#my-text-area .scroll-pane .viewport{
    -fx-background-color: transparent;
}


#my-text-area .scroll-pane .content{
    -fx-background-color: transparent;
}

/// === Selection