7

I'm using a ListView control in a JavaFX application. It is set for MULTIPLE selection mode. I know as a user, I can Ctrl-Click an item to deselect it, but this is not intuitive enough for my users. I want a way to click a second time to deselect it. In other words click once - select; click selected item and it becomes unselected.

I've tried using both a ChangeListener and an onMouseClicked event. Neither works very well. Below are code snippets of each.

ChangeListener:

effect - first item in the list is NEVER selected. I click on it and it stays unclicked. No effect on items 2..n

listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<SpecificAlgorithmData>() {
    @Override
    public void changed(final ObservableValue observableValue, final SpecificAlgorithmData oldData, final SpecificAlgorithmData newData) {

        //if already selected then deselect it
        int selectedIndex = listView.getSelectionModel().getSelectedIndex();
        System.out.println("selected " + selectedIndex);
        System.out.println("all selected" + listView.getSelectionModel().getSelectedIndices());
        if (!selecting && !listView.getSelectionModel().getSelectedIndices().contains(selectedIndex)){


            Iterator <Integer> iterator = listView.getSelectionModel().getSelectedIndices().iterator();
            selecting = true;
            listView.getSelectionModel().select(-1);//deselect all

            while (iterator.hasNext()){
                int index = iterator.next();
                if (index!= selectedIndex){
                    listView.getSelectionModel().select(index);
                }
            }
            selecting = false;
        }
    }
}

onClick:

No effect, since I'm not sure how to get the index of the one I just clicked. Being hard coded, this simply disallows of ever selecting item 2.

listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(final MouseEvent mouseEvent) {
        int selectedItem = 2; //FIXME: How to I get the index of clicked item?
        if (listView.getSelectionModel().isSelected(selectedItem)){
            listView.getSelectionModel().clearSelection(selectedItem);
        }
    }
});
Domenic D.
  • 5,276
  • 4
  • 30
  • 41
  • 1
    How do the users select multiple items? – Uluk Biy May 13 '14 at 05:48
  • Right now they don't know that they can with Ctrl-click which is the problem. I'd like them to be able to select and deselect with just a click (no Ctrl key). – Domenic D. May 13 '14 at 18:50
  • 2
    Use of "Ctrl + Click" is a common user experience on many GUI Web/Desktop applications. You may kindly introduce this to your users. Alternatively you can put checkboxes on list cells, which could be more intuitive. – Uluk Biy May 14 '14 at 04:59
  • Checkboxes on the list boxes, genius. Why did I not think of that!!! – Domenic D. May 14 '14 at 11:14

1 Answers1

7

Changing the behavior of controls in JavaFX is pretty difficult - there are really no hooks currently in the API into the behavior classes.

The following seems to work, by registering an event filter with the cells in the list, implementing the selection behavior directly, and consuming the event.

It feels a bit fragile though (what if a future release decided to implement the default behavior on mouse clicked, instead of mouse pressed, for example; or perhaps better, what if a future release decided to add additional functionality handled by mouse events). So use this solution with a bit of a "buyer beware" notice attached.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class DeselectableList extends Application {

    @Override
    public void start(Stage primaryStage) {
        ListView<String> list = new ListView<>();
        MultipleSelectionModel<String> selectionModel = list.getSelectionModel();
        selectionModel.setSelectionMode(SelectionMode.MULTIPLE);
        for (int i=1; i<=20; i++) {
            list.getItems().addAll("Item "+i);
        }
        list.setCellFactory(lv -> {
            ListCell<String> cell = new ListCell<>();
            cell.textProperty().bind(cell.itemProperty());
            cell.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
                list.requestFocus();
                if (! cell.isEmpty()) {
                    int index = cell.getIndex();
                    if (selectionModel.getSelectedIndices().contains(index)) {
                        selectionModel.clearSelection(index);
                    } else {
                        selectionModel.select(index);
                    }
                    event.consume();
                }
            });
            return cell ;
        });
        BorderPane root = new BorderPane(list);
        Scene scene = new Scene(root, 150, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Obviously you know your users better than I, but I might prefer just to have a nice tooltip on the ListView explaining to them how to use it...

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks so much. I will try this out. But as Uluk points out I may just use a list box with checkboxes. In the end it's up to the client... – Domenic D. May 14 '14 at 11:16
  • Yes... checkboxes are probably a better way to go. That's about as obvious as you can make it for your users ;). – James_D May 14 '14 at 18:25
  • Nice! The requestFocus(); made the trick. I am "going forward" through a couple of lists -> when the user changes his selection (he first made) and selects a previous list (like navigating backwards) the selection, which keeps in an inactive state should be invalid/thrown (the inactive selection made the GUI incorrect). Thanks! – Martin Pfeffer Apr 07 '15 at 09:50
  • I had some issues with this solution as the first item always got selected after deselecting any item. However, a workaround can be found [here](http://stackoverflow.com/a/29004979/2161059). – Michael Stauffer Jun 09 '15 at 19:11