I want to include checks for my combobox to restrict "access" to some of the values. I could just remove those unaccessible items from the list, yes, but I'd like the user to see the other options, even if he can't select them (yet).
Problem: Selecting another value inside a changelistener causes an IndexOutOfBoundsException, and I have no idea why.
Here is a SSCCE. It creates a ComboBox with Integer values, and the first one is selected on default. Then I tried to keep it very easy: Every change of the value is considered as "wrong" and I change the selection back to the first element. But still, 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();
}
}
I tested it with valueProperty, selectedItemProperty and selectedIndexProperty, as well as all of these:
box.getSelectionModel().select(0);
box.getSelectionModel().selectFirst();
box.getSelectionModel().selectPrevious();
box.setValue(0);
if (oldValue.intValue() < newValue.intValue())
box.getSelectionModel().select(oldValue.intValue());
The only think that works is setting the value itself:
box.getSelectionModel().select(box.getSelectionModel().getSelectedIndex());
box.setValue(box.getValue));
Here is the exception:
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)
What am I doing wrong?
In JavaFX, you cannot change the contents of an ObservableList
while a change is already in progress. What is happening here is that your listeners (any of the ones you try) are being fired as part of the box.getSelctionModel().getSelectedItems()
ObservableList
changing. So basically, you cannot change the selection while a selection change is being processed.
Your solution is a bit unwieldy anyway. If you had another listener on the selected item (or combo box value), even if your method worked it would temporarily see the combo box with an "illegal" selection. E.g in the example above, if the user tries to select "1", another listener would see the selection change to the disallowed value "1", then back to "0". Dealing with values that are not supposed to be allowed in this listener would likely make your program logic pretty complex.
A better approach, imho, is to prevent the user from selecting the disallowed values. You can do this with a cell factory that sets the disable
property of the cell:
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);
}
}
});
Including the following in an external style sheet will give the user the usual visual hint that the items are not selectable:
.combo-box-popup .list-cell:disabled {
-fx-opacity: 0.4 ;
}
I know the thread is quite old but I had a similar problem and I worked it out in a different way. I tried to change ComboBox' selected item in its onAction
method when the item wasn't available at that moment (for example because of the given condition). As @James_D said in his answer, the problem is setting the object that is being currently modified.
Just add your code inside the Platform.runLater()
method:
Platform.runLater(() -> box.getSelectionModel().select(0));
In my case it worked, hope it will work in the others too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With