Reputation: 422
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
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