Il valore di modifica di ComboBox JavaFX causa IndexOutOfBoundsException

Voglio includere controlli per la mia casella combinata per limitare “l’accesso” ad alcuni valori. Potrei semplicemente rimuovere quegli elementi non accessibili dalla lista, sì, ma vorrei che l’utente vedesse le altre opzioni, anche se non è in grado di selezionarle (ancora).

Problema: la selezione di un altro valore all’interno di un changelistener causa una IndexOutOfBoundsException e non ho idea del perché.

Ecco un SSCCE. Crea un ComboBox con valori Integer e il primo è selezionato come predefinito. Poi ho cercato di mantenerlo molto semplice: ogni modifica del valore è considerata “errata” e cambio la selezione al primo elemento. Ma ancora, IndexOutOfBounds:

import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.ComboBox; import javafx.stage.Stage; public class Tester extends Application{ public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { ComboBox box = new ComboBox(); ObservableList vals= FXCollections.observableArrayList(0,1,2,3); box.setItems(vals); box.getSelectionModel().select(0); /*box.valueProperty().addListener((observable, oldValue, newValue) -> { box.getSelectionModel().select(0); });*/ /*box.getSelectionModel().selectedItemProperty().addListener((observable,oldValue,newValue)->{ System.out.println(oldValue+","+newValue); box.getSelectionModel().select(0); });*/ box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{ System.out.println(oldValue+","+newValue); box.getSelectionModel().select(0); }); Scene scene = new Scene(new Group(box),500,500); stage.setScene(scene); stage.show(); } } 

L’ho provato con valueProperty, selectedItemProperty e selectedIndexProperty, oltre a tutti questi:

 box.getSelectionModel().select(0); box.getSelectionModel().selectFirst(); box.getSelectionModel().selectPrevious(); box.setValue(0); if (oldValue.intValue() < newValue.intValue()) box.getSelectionModel().select(oldValue.intValue()); 

L’unico pensiero che funziona è impostare il valore stesso:

     box.getSelectionModel().select(box.getSelectionModel().getSelectedIndex()); box.setValue(box.getValue)); 

    Ecco l’eccezione:

     Exception in thread "JavaFX Application Thread" java.lang.IndexOutOfBoundsException at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.subList(Unknown Source) at javafx.collections.ListChangeListener$Change.getAddedSubList(Unknown Source) at com.sun.javafx.scene.control.behavior.ListViewBehavior.lambda$new$178(Unknown Source) at com.sun.javafx.scene.control.behavior.ListViewBehavior$$Lambda$126/644961012.onChanged(Unknown Source) at javafx.collections.WeakListChangeListener.onChanged(Unknown Source) at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(Unknown Source) at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(Unknown Source) at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(Unknown Source) at javafx.scene.control.MultipleSelectionModelBase.clearAndSelect(Unknown Source) at javafx.scene.control.ListView$ListViewBitSetSelectionModel.clearAndSelect(Unknown Source) at com.sun.javafx.scene.control.behavior.CellBehaviorBase.simpleSelect(Unknown Source) at com.sun.javafx.scene.control.behavior.CellBehaviorBase.doSelect(Unknown Source) at com.sun.javafx.scene.control.behavior.CellBehaviorBase.mousePressed(Unknown Source) at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source) at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(Unknown Source) at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(Unknown Source) at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(Unknown Source) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source) at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(Unknown Source) at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(Unknown Source) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source) at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(Unknown Source) at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(Unknown Source) at com.sun.javafx.event.EventUtil.fireEventImpl(Unknown Source) at com.sun.javafx.event.EventUtil.fireEvent(Unknown Source) at javafx.event.Event.fireEvent(Unknown Source) at javafx.scene.Scene$MouseHandler.process(Unknown Source) at javafx.scene.Scene$MouseHandler.access$1500(Unknown Source) at javafx.scene.Scene.impl_processMouseEvent(Unknown Source) at javafx.scene.Scene$ScenePeerListener.mouseEvent(Unknown Source) at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source) at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$350(Unknown Source) at com.sun.javafx.tk.quantum.GlassViewEventHandler$$Lambda$172/2037973250.get(Unknown Source) at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(Unknown Source) at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(Unknown Source) at com.sun.glass.ui.View.handleMouseEvent(Unknown Source) at com.sun.glass.ui.View.notifyMouse(Unknown Source) at com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at com.sun.glass.ui.win.WinApplication.lambda$null$145(Unknown Source) at com.sun.glass.ui.win.WinApplication$$Lambda$36/2117255219.run(Unknown Source) at java.lang.Thread.run(Unknown Source) 

    Che cosa sto facendo di sbagliato?

    In JavaFX, non è ansible modificare il contenuto di una ObservableList mentre è già in corso una modifica. Quello che sta accadendo qui è che i tuoi ascoltatori (quelli che provi) vengono licenziati come parte di box.getSelctionModel().getSelectedItems() ObservableList cambia. Quindi, in pratica, non è ansible modificare la selezione mentre viene elaborata una modifica di selezione.

    La tua soluzione è comunque un po ‘ingombrante. Se avevi un altro listener sull’elemento selezionato (o sul valore della casella combinata), anche se il tuo metodo funzionasse, vedrebbe temporaneamente la casella combinata con una selezione “illegale”. Ad esempio nell’esempio sopra, se l’utente tenta di selezionare “1”, un altro listener vedrebbe la modifica della selezione al valore non consentito “1”, quindi torna a “0”. Affrontare i valori che non dovrebbero essere consentiti in questo listener probabilmente renderà la logica del tuo programma piuttosto complessa.

    Un approccio migliore, imho, è quello di impedire all’utente di selezionare i valori non consentiti. Puoi farlo con una cella che imposta la proprietà disable della cella:

      box.setCellFactory(lv -> new ListCell() { @Override public void updateItem(Integer item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); } else { setText(item.toString()); setDisable(item.intValue() != 0); } } }); 

    Includere quanto segue in un foglio di stile esterno darà all’utente il solito suggerimento visivo che gli elementi non siano selezionabili:

     .combo-box-popup .list-cell:disabled { -fx-opacity: 0.4 ; } 

    So che il thread è abbastanza vecchio ma ho avuto un problema simile e l’ho risolto in un modo diverso. Ho provato a cambiare l’elemento selezionato di ComboBox nel suo metodo onAction quando l’elemento non era disponibile in quel momento (ad esempio a causa della condizione data). Come @James_D ha detto nella sua risposta, il problema è impostare l’object che viene attualmente modificato.

    Basta aggiungere il codice all’interno del metodo Platform.runLater() :

    Platform.runLater(() -> box.getSelectionModel().select(0));

    Nel mio caso ha funzionato, spero che funzionerà anche negli altri.