Search code examples
javafxtableviewselectionchanged

JavaFx table view: block selection events but only the ones coming from the user interaction


I have been trying to create a javafx.scene.control.TableView such that all the selection events are blocked when their origin is user interaction. In other words, it must be possible for me to programmatically alter the selection in a given table view.

I tried solutions from the following questions:

  1. Setting the whole table view as mouse transparent (see article). This approach is unacceptable, because, for instance, user cannot change the width of the columns
  2. Setting the selection model to null (see article). This one is unacceptable, because the currently selected row is not highlighted properly- see image below:

image

Originally, I wanted to decorate the default existing table view selection model with my own. Something like this was created:

private final class TableViewSelectionModelDecorator< S >extends TableViewSelectionModel< S >
{
    private final TableViewSelectionModel< S > delegate;

    private TableViewSelectionModelDecorator( TableViewSelectionModel< S > aDelegate )
    {
        super( aDelegate.getTableView() );
        delegate = Objects.requireNonNull( aDelegate );
    }

   // Overriding the methods and delegating the calls to the delegate
}

The problem with my decorator is that the function getSelectedIndex() from the selection model is marked as final, which means I cannot override it and delegate the call to my decorated selection model. As a result, whenever a client asks for currently selected index the result is -1.

Requirements that I must meet:

  1. Selection change events coming from either the mouse click or the keyboard (or any other input source) is blocked.
  2. User must be able to interact with the table as long as the selection is not modified (e.g. changing the width of the columns)
  3. Selected entry is properly highlighted (instead of just some frame around the selected index)
  4. For now there is no multiselection support involved, but preferably I'd appreciate a solution that does support it.

Last note is I use Java 11.

Thanks for any pointers.


Solution

  • Please do consider the comments mentioned about the xy problem and other alternatives mentioned.

    If you still want to solve this as the way you mentioned, you can give a try as below.

    The idea is to

    • block all KEY_PRESSED events on tableView level and
    • set mouse transparent on tableRow level

    so that we are not tweaking with any default selection logic. This way you can still interact with columns and scrollbar using mouse.

    Below is the quick demo of the implementation:

    enter image description here

    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Insets;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Priority;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TableViewSelectionBlockingDemo extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            ObservableList<Person> persons = FXCollections.observableArrayList();
            for (int i = 1; i < 11; i++) {
                persons.add(new Person(i + "", "A" + i));
            }
    
            TableView<Person> tableView = new TableView<>();
            TableColumn<Person, String> idCol = new TableColumn<>("Id");
            idCol.setCellValueFactory(param -> param.getValue().idProperty());
            idCol.setPrefWidth(100);
            TableColumn<Person, String> nameCol = new TableColumn<>("Name");
            nameCol.setCellValueFactory(param -> param.getValue().nameProperty());
            nameCol.setPrefWidth(150);
            tableView.getColumns().addAll(idCol,nameCol);
            tableView.setItems(persons);
    
            // Selection Blocking logic
            tableView.addEventFilter(KeyEvent.KEY_PRESSED, e->e.consume());
            tableView.setRowFactory(personTableView -> new TableRow<Person>(){
                {
                    setMouseTransparent(true);
                }
            });
    
            ComboBox<Integer> comboBox = new ComboBox<>();
            for (int i = 1; i < 11; i++) {
                comboBox.getItems().add(i);
            }
            comboBox.valueProperty().addListener((obs, old, val) -> {
                if (val != null) {
                    tableView.getSelectionModel().select(val.intValue()-1);
                } else {
                    tableView.getSelectionModel().clearSelection();
                }
            });
            HBox row = new HBox(new Label("Select Row : "), comboBox);
            row.setSpacing(10);
            VBox vb = new VBox(row, tableView);
            vb.setSpacing(10);
            vb.setPadding(new Insets(10));
            VBox.setVgrow(tableView, Priority.ALWAYS);
    
            Scene scene = new Scene(vb, 500, 300);
            primaryStage.setScene(scene);
            primaryStage.setTitle("TableView Selection Blocking Demo");
            primaryStage.show();
        }
    
        class Person {
            private StringProperty name = new SimpleStringProperty();
            private StringProperty id = new SimpleStringProperty();
    
            public Person(String id1, String name1) {
                name.set(name1);
                id.set(id1);
            }
    
            public StringProperty nameProperty() {
                return name;
            }
    
            public StringProperty idProperty() {
                return id;
            }
        }
    }
    

    Note: This may not be the approach for editable table.