wrslatz
wrslatz

Reputation: 422

JavaFX ListView selectedItemProperty not firing on clearSelection(index)

I am using JavaFX for the GUI of an application I am writing. I have a ListView and a Button in my GUI.

I want the ListView to maintain multiple selection on mouse clicks and only remove selections when clicked again (this works).

I also need to validate that something has been selected in the ListView and disable the button if nothing is enabled (works on selections but NOT on deselections).

Here is my code for the button enabling and disabling (this is not working):

//Binding used to enable and disable the button
    BooleanBinding validEntriesBinding = new BooleanBinding(){
        {
            super.bind(listView.getSelectionModel().selectedItemProperty());
        }
        @Override
        protected boolean computeValue() {
            log.info("No individual item selected? " + 
                    (listView.getSelectionModel().getSelectedItems().isEmpty()));
            log.info("Selected items: " + listView.getSelectionModel().getSelectedItems().toString());
            return (listView.getSelectionModel().getSelectedItems().isEmpty());
        }
    };

    button.disableProperty().bind(validEntriesBinding);

Here is my code for capturing the mouse selections (this is working, solution developed from this SO answer by fabian):

//Example sourced from stackoverflow.com/questions/40900478/mimicking-ctrlclick-multiple-selection-in-listview-using-javafx
        //Answer by fabian
        //Must use MouseEvent.MOUSE_PRESSED and not MOUSE_CLICKED, otherwise the selection does not work
        listView.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
            Node node = event.getPickResult().getIntersectedNode();

            while(node != null && node != listView && !(node instanceof ListCell)){
                node = node.getParent();
            }

            if(node instanceof ListCell){
                event.consume();
                listView.requestFocus();

                ListCell cell = (ListCell) node;
                if(!cell.isEmpty()){
                    if(cell.isSelected()){
                        log.info("Clear selection...");
                          listView.getSelectionModel().clearSelection(cell.getIndex());
                              log.info("Selection cleared");
                    }
                    else{
                          log.info("Selecting...");
                        listView.getSelectionModel().select(cell.getIndex());
                        log.info("Selected");
                    }
                }
                else{
                    log.warn("Cell is empty... cannot select");
                }
            }
            else{
                log.warn("Unable to handle event for " + node.getId() + " " + node.getClass());
            }
        });

Here is what the output looks like when I run my app:

    //Before click, button disabled
    No individual item selected? true
    Selected items: []

    //First click selecting a cell, button enabled
    Selecting...
    No individual item selected? false
    Selected items: [SV124]
    Selected

    //Second click deselecting the first selected cell, button stays
    //enabled as the BooleanBinding never fires
    Clear selection...
    Selection cleared

Why does the BooleanBinding not fire on listView.getSelectionModel().clearSelection(cell.getIndex());? Or, more directly, why does the listView.getSelectionModel().selectedItemProperty() not fire when selections are empty? According to the Javadoc it should return null when empty, so shouldn't that be captured?

Upvotes: 0

Views: 1059

Answers (1)

James_D
James_D

Reputation: 209418

It seems that removing a selected index from the list view's selection model fails to invalidate the selectedItemProperty(). (That would appear to be a bug.)

It works if you bind to the selectedIndexProperty instead of the selectedItemProperty:

BooleanBinding validEntriesBinding = new BooleanBinding(){
    {
        super.bind(listView.getSelectionModel().selectedIndexProperty());
    }
    @Override
    protected boolean computeValue() {
        log.info("No individual item selected? " + 
                (listView.getSelectionModel().getSelectedItems().isEmpty()));
        log.info("Selected items: " + listView.getSelectionModel().getSelectedItems().toString());
        return (listView.getSelectionModel().getSelectedItems().isEmpty());
    }
};

and perhaps, since you have multiple selection enabled, it's better to bind to the list of selected items anyway:

BooleanBinding validEntriesBinding = new BooleanBinding(){
    {
        super.bind(listView.getSelectionModel().getSelectedItems());
    }
    @Override
    protected boolean computeValue() {
        log.info("No individual item selected? " + 
                (listView.getSelectionModel().getSelectedItems().isEmpty()));
        log.info("Selected items: " + listView.getSelectionModel().getSelectedItems().toString());
        return (listView.getSelectionModel().getSelectedItems().isEmpty());
    }
};

Note that if you make validEntriesBinding a local variable, it is prone to accidental garbage collection. You should make this a field instead:

private BooleanBinding validEntriesBinding ;

// ...

validEntriesBinding = new BooleanBinding(){
    {
        super.bind(listView.getSelectionModel().getSelectedItems());
    }
    @Override
    protected boolean computeValue() {
        log.info("No individual item selected? " + 
                (listView.getSelectionModel().getSelectedItems().isEmpty()));
        log.info("Selected items: " + listView.getSelectionModel().getSelectedItems().toString());
        return (listView.getSelectionModel().getSelectedItems().isEmpty());
    }
};

Upvotes: 2

Related Questions