2

I start with the question and then describe the problem more in detail:

Question:

Is there a way to receive events from the listview of a JavaFx ComboBox directly (ComboBox consists of a listview and a textfield as far as I understand)? I would like to find out which element of the list has been clicked by mouse or which element was selected when the user pressed enter on the keyboard (after navigating with Up/Down Arrows in the list). I therefore need to receive the mouse click event or the keypress event on the listview, but all I can get so far are events on the combobox itself which turned out to be only changes in the textfield of the combobox.

Detailed problem description:

I have a JavaFx Application with a combobox where the idea is that the value is directly added to another list when the user clicks on an entry in the combobox list (when the popup of the combobox is open). Additionally, when the user navigates in the combobox list with the arrow keys on the keyboard and he presses enter, the currently selected value should be taken and added to the other list. However so far I have not found out how I can implement this feature with the standard JavaFx combobox.

Tried:

  1. The first solution I tried was to add a change listener on the combobox's valueProperty (this is the value that is displayed in the textfield when the popup is closed). So when the user clicks on a listentry, the popup closes and the clicked entry is put into the value field, changes the value and therefore fires my change listener. This worked well with the mouse, but keyboard navigation did not work correctly because everytime the user navigates down or up on the list, the value field of the combobox is updated with the currently selected listentry and therefore fires my changelistener every time (which in turn adds the value to my other list, while it should only be added on enter keypress).
  2. The next solution was to not add a listener to the valueProperty, but to the showingProperty. Everytime the combobox popup closes, the current value of the value field is taken and added to the other list. This works again well with the mouse, but still has issues with keyboard navigation. While it now correctly handles up/down navigation in the list and enter keypresses, the problem is now when you try to cancel the selection. When you press escape, the value is taken as well and added to the other list like you would have pressed enter. The problem with this approach is that I cannot find out with which keypress the popup has been closed.

So the next idea I had is to add an event filter to the combobox itself with the addEventFilter Method of the ComboBox with code similar to the following (also proposed here StackOverflow):

ComboBox comboBox = new ComboBox();
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, (event) -> {
    // do stuff
});

But the event filter never gets called when the combobox popup is open. It is only called when the popup is closed, which leads me to the assumption that the event is consumed by the listview. So the only solution to the problem seems to be to somehow capture the events from the listview itself. And that leads back to the question at the beginning of this post. Sorry for the long explanation.:-)

Community
  • 1
  • 1
Dude
  • 652
  • 2
  • 10
  • 24
  • Hmm... maybe providing a `cellFactory` and adding a listener to the `selected` property of each `ComboBoxListCell`? Not sure if it'll work, but maybe worth a shot. – Itai Jan 07 '16 at 00:34
  • Interesting question. I can't find a way to do this. The bottom line is that the combo box actually sets the value as you navigate with the keys, before you close the popup. (I guess it is just observing the selected item property of the list view.) Your idea with the `showingProperty` is really the right one, note that the issue with the escape key is basically just due to the fact that the escape key doesn't seem to cancel selection in any sense. (If you open the combo box, navigate with the keys, then press escape, the last item selected by the keys is shown in the combo box.) – James_D Jan 07 '16 at 02:10
  • Can you use a `ChoiceBox` instead of a `ComboBox`? – James_D Jan 07 '16 at 02:13
  • @sillyfly: Good idea. But in the end, I still need to know if the user canceled the input (with the selected property I know only which was the last selected item). – Dude Jan 07 '16 at 09:02
  • @James_D: ChoiceBox is not exactly what I need (it should be possible to have no value selected and show "Select value..." instead). But maybe it can be tweaked to work like this. I think about it. – Dude Jan 07 '16 at 09:04
  • While comparing the ChoiceBox with the ComboBox, I found out that only KEY_PRESSED events are filtered, but KEY_RELEASED events not. So the code above works for keyboard navigation, if you replace `KeyEvent.KEY_PRESSED` with `KeyEvent.KEY_RELEASED`. However, next problem is that mouse events are all filtered, (`MOUSE_CLICKED, MOUSE_PRESSED, MOUSE_RELEASED`). I'll keep you posted when I find a new solution. Thanks for the help anyway! – Dude Jan 07 '16 at 13:56
  • Oh, with Choice box it's way simpler: you can just listen to the selected item or the value. No need for low level events as the selection doesn't change as the user navigates. – James_D Jan 08 '16 at 16:11

2 Answers2

0

Came across this question while looking for something else. It touches on something I've worked on before so assuming I understand what you're trying to accomplish and also that this is still relevant here are a couple of ideas.

To keep the other list (list #2) in sync with the combo box for mouse clicks try:

  1. A ChangeListener on the selectedItem property for the combo box's selectionModel. Clicking an item in the popup (drop-down) list view will change the selectedItem value and close the Window that 'owns' the list view, or
  2. An event handler to the combo box for the event type ComboBoxBase.ON_HIDDEN which gets fired when the list view closes (or better said 'becomes hidden').

The ChangeListener may be problematic inasmuch as the selection models for the combo box and the list view are linked at a low level, so that changes to the selectedItem value for the list view which will be triggered when navigating with key strokes will necessarily be handled by the ChangeListener. Since you want to sync the combo box and list #2 only when the ENTER key is pressed, this will probably not work. But that still leaves the event handler idea to try.

As for ENTER key press, you need to get a handle for the list view itself, and then add whatever behavior (listeners/event handlers) as needed. You do this by getting the skin for the combo box, which should be an instance of ComboBoxListViewSkin, and then get the list view by invoking the getPopupContent() method for the skin; it returns the popup list view as a Node so you'll to recast the returned value but no big deal.

Hope this helps.

jfr
  • 397
  • 2
  • 17
0

The idea of the below code is that ListView is created once the combobox is loaded in the JavaFX scene. Therefore, we add a listener on the combobox to check when it appears on the scene, and then through "lookup" method we get its listview and add a listener to it.

In the example, I have set a MouseEvent listener but you can easily adjust it for keys as well.

private EventHandler<MouseEvent> cboxMouseEventHandler;

private void initComboBox() {
    ComboBox<String> comboBox = new ComboBox<String>();
    comboBox.getItems().add("Item 1");
    comboBox.getItems().add("Item 2");
    comboBox.getItems().add("Item 3");

    comboBox.sceneProperty().addListener((a,oldScene,newScene) -> {
        if(newScene == null || cboxMouseEventHandler != null)
            return;
            
        ListView<?> listView = (ListView<?>) comboBox.lookup(".list-view");
        if(listView != null) {
            cboxMouseEventHandler = (e) -> {
                Platform.runLater(()-> {
                    String selectedValue = (String) listView.getSelectionModel().getSelectedItem();
                    if(selectedValue.equals("Item 1"))
                        System.out.println("Item 1 clicked");
                });
            }; // cboxMouseEventHandler
        
            listView.addEventFilter(MouseEvent.MOUSE_PRESSED, cboxMouseEventHandler); 
        } // if
    });
} // initComboBox
javasuns
  • 1,061
  • 2
  • 11
  • 22