Search code examples
javajavafxobservablelist

JavaFX Choiceox change not updating graphics


When I change the underlying observable array list the graphics choice box doesn't update. There must be a newer solution than what I have seen suggested here for example: JavaFX: Update of ListView if an element of ObservableList changes

    int selected = productsChoiceBox.getSelectionModel().getSelectedIndex();
    Product prod = products.get(selected);
    prod.setName(productName.getText());
    prod.setUrl(productUrl.getText());

Any thoughts? I would like to avoid removing and adding.


Solution

  • The "standard" answer is to use an ObservableList with an extractor. However, when I tested this out, it didn't behave as advertised, and it seems like there is a bug (my guess is that ChoiceBox is not correctly handling wasUpdated type changes fired in its ListChangedListener) which I will report at JIRA. Update: filed report at https://javafx-jira.kenai.com/browse/RT-38394

    The factory method FXCollections.observableArrayList(Callback) creates an (empty) observable array list. The provided Callback is a function that maps each element in the list to an array of Observables. The list registers listeners with those observables, and if those properties change, the list fires update notifications to its listeners.

    This produces strange results with a ChoiceBox, however; one possible workaround would be to use a ComboBox which seems to work fine.

    Here's some sample code. Select an item: then type in the text field and press enter to change the name of the selected item. Change ChoiceBox to ComboBox to see the correct behavior:

    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    import javafx.application.Application;
    import javafx.beans.Observable;
    import javafx.beans.binding.Bindings;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.scene.Scene;
    import javafx.scene.control.ChoiceBox;
    import javafx.scene.control.TextField;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    
    public class ChoiceBoxUpdateExample extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            ChoiceBox<Item> choiceBox = new ChoiceBox<>();
            ObservableList<Item> items = FXCollections.observableArrayList(
                    item -> new Observable[] {item.nameProperty()}); // the extractor
            items.addAll(
                    IntStream.rangeClosed(1, 10)
                    .mapToObj(i -> new Item("Item "+i))
                    .collect(Collectors.toList()));
            choiceBox.setItems(items);
    
            TextField changeSelectedField = new TextField();
            changeSelectedField.disableProperty()
                .bind(Bindings.isNull(choiceBox.getSelectionModel().selectedItemProperty()));
            changeSelectedField.setOnAction(event -> 
                choiceBox.getSelectionModel().getSelectedItem().setName(changeSelectedField.getText()));
    
            BorderPane root = new BorderPane();
            root.setTop(choiceBox);
            root.setBottom(changeSelectedField);
            Scene scene = new Scene(root, 250, 150);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static class Item {
            public final StringProperty name = new SimpleStringProperty();
            public StringProperty nameProperty() {
                return name ;
            }
            public final String getName() {
                return nameProperty().get();
            }
            public final void setName(String name) {
                nameProperty().set(name);
            }
            public Item(String name) {
                setName(name);
            }
            @Override
            public String toString() {
                return getName();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }