I have an issue with ComboBox
when I select same item with control key pressed . This is what happens :
as you can see in the gif , combobox
change values normally , but when control key is pressed , the selected item is null if the new value is the same as the previous one .
I am using javafx 23 in openjdk 23.0.1 ( Amazon coretto ) on a linux ubuntu mate 24.04.
This is my minimal reproducible example
public class App extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) throws IOException {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll("one", "two");
comboBox.getSelectionModel().selectFirst();
Scene scene = new Scene(new StackPane(comboBox), 320, 240);
String title = null;
scene.setOnKeyPressed(
keyEvent -> stage.setTitle(keyEvent.isControlDown() ? "Control down" : ""));
scene.setOnKeyReleased(e -> stage.setTitle(""));
stage.setTitle(title);
stage.setScene(scene);
stage.show();
}
}
I think this is intended behavior: to deselect the selection when ctrl key is pressed.
You can modify this behavior in two ways.
Option 1 (event filter):
Include a mouse pressed event filter in the custom ListCell constructor, to consume the event when the shortcut is pressed and if the cell is already selected. The only side effect is you lose any mouse pressed events on the cell.
class MyListCell<T> extends ListCell<T> {
public MyListCell() {
addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (e.isShortcutDown() && isSelected()) {
e.consume();
}
});
}
}
Option 2 (custom behavior):
This is more like specifically tweaking the select functionality rather than blocking all mouse pressed handlers of the cell. The general idea is to create a
custom list cell/skin/behavior classes and tweak the doSelect method to ignore the shortcutDown
parameter when processing the selection. The behavior code will be something like below:
you may need to include the below VM argument
--add-exports javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED
class MyListCellBehavior<T> extends ListCellBehavior<T> {
public MyListCellBehavior(ListCell<T> control) {
super(control);
}
@Override
protected void doSelect(double x, double y, MouseButton button, int clickCount, boolean shiftDown, boolean shortcutDown) {
// We always pass the shortcutDown as false to ignore the inputs.
super.doSelect(x, y, button, clickCount, shiftDown, false);
}
}
The full working demo with both approaches is below:
import com.sun.javafx.scene.control.behavior.ListCellBehavior;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.Skin;
import javafx.scene.control.skin.ListCellSkin;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class CustomListCellBehaviour_Demo extends Application {
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage) {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.getItems().setAll("one", "two");
comboBox.getSelectionModel().selectFirst();
comboBox.setCellFactory(param -> new MyListCell<>());
Scene scene = new Scene(new StackPane(comboBox), 320, 240);
String title = null;
scene.setOnKeyPressed(
keyEvent -> stage.setTitle(keyEvent.isControlDown() ? "Control down" : ""));
scene.setOnKeyReleased(e -> stage.setTitle(""));
stage.setTitle(title);
stage.setScene(scene);
stage.show();
}
class MyListCell<T> extends ListCell<T> {
public MyListCell() {
// UNCOMMENT THE BELOW CODE FOR OPTION 1 APPROACH AND GET RID OF THE SKIN CLASSES
// addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
// if (e.isShortcutDown() && isSelected()) {
// e.consume();
// }
// });
}
@Override
protected Skin<?> createDefaultSkin() {
return new MyListCellSkin<T>(this);
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(item.toString());
} else {
setText(null);
}
}
}
class MyListCellSkin<T> extends ListCellSkin {
private MyListCellBehavior<T> behavior;
public MyListCellSkin(ListCell<T> control) {
super(control);
behavior = new MyListCellBehavior<>(control);
}
@Override
public void dispose() {
super.dispose();
if (behavior != null) {
behavior.dispose();
}
}
}
class MyListCellBehavior<T> extends ListCellBehavior<T> {
public MyListCellBehavior(ListCell<T> control) {
super(control);
}
@Override
protected void doSelect(double x, double y, MouseButton button, int clickCount, boolean shiftDown, boolean shortcutDown) {
// We always pass the shortcutDown as false to ignore the inputs.
super.doSelect(x, y, button, clickCount, shiftDown, false);
}
}
}