0

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.

kleopatra
  • 51,061
  • 28
  • 99
  • 211
Arturos
  • 25
  • 2
  • 6
  • sounds like an xy-problem.. – kleopatra Jul 27 '22 at 23:08
  • 2
    So the standard questions to ask with a potential [xy problem](https://xyproblem.info), are what is the context? what are the requirements? what are you trying to achieve at a functional level? In other words, *why* "selection events are blocked when their origin is user interaction", what problem does the user modifying the selection cause? Is there an alternate way of addressing the problem rather than trying to block user selection? You can edit the question to add the additional context. – jewelsea Jul 27 '22 at 23:17
  • 2
    The way I'd try to do this is via event filters and consuming any events I don't want to reach the table/row/cell. But I agree this seems like an XY Problem. Does the selection _mean_ something other than the row/cell being selected? If that's the case, I would suggest putting some sort of state in your model that indicates this meaning. And then you can use custom rows/cells and style them differently based on that state. This would work independently from the selection/focus system. – Slaw Jul 27 '22 at 23:39
  • .. and a terrible user experience: they click and nothing happens, they use the navigation keys and nothing happens .. so: why? Regarding the decoration approach: should work (theoretically, there will be issues because there's no way to un-attach the decorated model from table), you have to override all methods that are modifying the selection to do nothing and add your own methods to call when an "allowed" selection happens – kleopatra Jul 28 '22 at 08:36

1 Answers1

2

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.

Sai Dandem
  • 8,229
  • 11
  • 26