最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javafx - ComboBox value is null if select same item with control key pressed - Stack Overflow

programmeradmin1浏览0评论

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 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();
}
}
Share Improve this question asked Feb 6 at 4:49 Giovanni ContrerasGiovanni Contreras 2,5691 gold badge15 silver badges26 bronze badges 1
  • 2 The default skin of ComboBox is displaying a ListView in the popup. When you shortcut+click a selected cell in a list view, that cell is deselected. The skin of the combo box listens to the list view's selection model to know when it needs to update the combo box's value. It apparently interprets clearing the list view's selection as needing to set the combo box's value to null. I don't know if this is intended behavior (I would say it's a bug, as you shouldn't be able to "deselect" items of a combo box). A workaround could be to add an event filter and consume the appropriate mouse events – Slaw Commented Feb 6 at 6:21
Add a comment  | 

1 Answer 1

Reset to default 4

I think this is intended behavior: to deselect the selection when ctrl key is pressed.

You can modify this behavior in two ways.

  • by including an event filter on the cell (as mentioned by @Slaw)
  • by including a custom behavior

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);
        }
    }
}
发布评论

评论列表(0)

  1. 暂无评论