El valor de cambio de JavaFX ComboBox provoca IndexOutOfBoundsException
integer setvalue (2)
En JavaFX, no puede cambiar el contenido de una
ObservableList
mientras un cambio ya está en progreso.
Lo que está sucediendo aquí es que sus oyentes (cualquiera de los que intente) están siendo despedidos como parte de
box.getSelctionModel().getSelectedItems()
ObservableList
cambiando.
Básicamente, no puede cambiar la selección mientras se procesa un cambio de selección.
Su solución es un poco difícil de manejar de todos modos. Si tuviera otro oyente en el elemento seleccionado (o el valor del cuadro combinado), incluso si su método funcionara, vería temporalmente el cuadro combinado con una selección "ilegal". Por ejemplo, en el ejemplo anterior, si el usuario intenta seleccionar "1", otro oyente vería que la selección cambia al valor no permitido "1", y luego volverá a "0". Tratar con valores que no deberían estar permitidos en este oyente probablemente haría que la lógica de su programa sea bastante compleja.
Un mejor enfoque, en mi opinión, es evitar que el usuario seleccione los valores no permitidos.
Puede hacer esto con una fábrica de celdas que establece la propiedad de
disable
de la celda:
box.setCellFactory(lv -> new ListCell<Integer>() {
@Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
setDisable(item.intValue() != 0);
}
}
});
Incluir lo siguiente en una hoja de estilo externa le dará al usuario la pista visual habitual de que los elementos no son seleccionables:
.combo-box-popup .list-cell:disabled {
-fx-opacity: 0.4 ;
}
Quiero incluir controles para mi cuadro combinado para restringir el "acceso" a algunos de los valores. Podría eliminar esos elementos inaccesibles de la lista, sí, pero me gustaría que el usuario vea las otras opciones, incluso si no puede seleccionarlas (todavía).
Problema: Seleccionar otro valor dentro de un changelistener provoca una excepción IndexOutOfBoundsException, y no tengo idea de por qué.
Aquí hay un SSCCE. Crea un ComboBox con valores enteros, y el primero se selecciona por defecto. Luego traté de mantenerlo muy fácil: cada cambio del valor se considera "incorrecto" y cambio la selección al primer elemento. Pero aún así, 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<Integer> box = new ComboBox<Integer>();
ObservableList<Integer> 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();
}
}
Lo probé con valueProperty, selectedItemProperty y selectedIndexProperty, así como todos estos:
box.getSelectionModel().select(0);
box.getSelectionModel().selectFirst();
box.getSelectionModel().selectPrevious();
box.setValue(0);
if (oldValue.intValue() < newValue.intValue())
box.getSelectionModel().select(oldValue.intValue());
Lo único que funciona es establecer el valor en sí mismo:
box.getSelectionModel().select(box.getSelectionModel().getSelectedIndex());
box.setValue(box.getValue));
Aquí está la excepción:
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)
¿Qué estoy haciendo mal?
Sé que el hilo es bastante antiguo, pero tuve un problema similar y lo resolví de otra manera.
Intenté cambiar el elemento seleccionado de ComboBox en su método
onAction
cuando el elemento no estaba disponible en ese momento (por ejemplo, debido a la condición dada).
Como @James_D dijo en su respuesta, el problema es establecer el objeto que se está modificando actualmente.
Simplemente agregue su código dentro del método
Platform.runLater()
:
Platform.runLater(() -> box.getSelectionModel().select(0));
En mi caso funcionó, espero que también funcione en los demás.