msj121
msj121

Reputation: 2842

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.

Upvotes: 4

Views: 567

Answers (2)

msj121
msj121

Reputation: 2842

The correct and proper answer is from James_D, but if you REALLY want to use ChoiceBox, then try adding and removing:

    int selected = productsChoiceBox.getSelectionModel().getSelectedIndex();
    products.remove(selected);
    products.add(selected, prod);

I do NOT believe this is the right way, but I tested it, and it does work. The ChoiceBox stays on the the removed and selected index and looks like it updates.

Upvotes: 2

James_D
James_D

Reputation: 209724

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);
    }
}

Upvotes: 4

Related Questions