Maverick283
Maverick283

Reputation: 1403

JavaFX CheckBoxTableCell catch ClickEvent

Using the CheckBoxTableCell in a TableView I wan't to perform some code when the user clicks the checkbox. This code needs to be performed before the CheckBox get's checked and before the associated property is changed.

Since CheckBoxTableCell does not trigger startEdit(), commitEdit() and so on I need another point where I can put my code.

updateItem() would be too late, since the value has already been changed there.

Does anybody know any other place where I can insert my code? Or am I better of just writing my own CheckBoxTableCell?

For those who want to think further: When the user checks the checkBox I want to run a code that checks if his decision may cause him problems later on. When the property changes I have a changeListener that uploads the new value to a database. If there may be any problems (my code returns true) then there should be the obligatory error message: "Do you really want to do this...blabla" and only if he confirms the check box should be actually checked, the value changed and uploaded to the database.

This is how I set up my CheckBoxTableCell:

    //init cell factories
    Callback<TableColumn<PublicMovie, Boolean>, TableCell<PublicMovie, Boolean>> checkBoxCellFactory
            = (TableColumn<PublicMovie, Boolean> p) -> new CheckBoxTableCell<>();
    //Assign Column
    allMoviesCheckBoxColumn = (TableColumn<PublicMovie, Boolean>) allMoviesTableView.getColumns().get(0);
    //Set CellValueFactory
    allMoviesCheckBoxColumn.setCellValueFactory(cellData -> cellData.getValue().getSomeBooleanProperty();

someBooleanProperty has a listener. When changed to true, the statusProperty changes to 1.

This code snippet is in the constructor of the TableViews underlying class. Whenever the user clicks the CheckBox, it is executed.

    this.status.addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> {
        DataHandler.fireOnChangedEvent(PublicMovieChangeListener.STATUS_CHANGED, getMovie(), newValue, oldValue);
    });

DataHandler holds a list of Listeners (so far only one). The following code is executed when the onChange event is called by the DataHandler:

@Override
public void onChange(int changeID, PublicMovie changedPublicMovie, Object newValue, Object oldValue) {
    switch (changeID) {
        case STATUS_CHANGED:
            boolean mayUpdate = check(); 
            //check is a placeholder for the code that checks for problems.
            //It will return false if there might be a problem.

            if(!mayUpdate){
                    mayUpdate = ErrorDialogs.getConfirmationDialog("Überfüllung!", header, content);
            }
            if (mayUpdate) {
                updateMovie(changedPublicMovie);
            } else {
                changedPublicMovie.getStatusProperty().set((int) oldValue);
                refreshAllMoviesTable();
            }
            break;
        case THREE_DIM_CHANGED:
            updateMovie(changedPublicMovie);
            break;
        case CHILDREN_CHANGED:
            updateMovie(changedPublicMovie);
            break;
    }

What you can see here is my attempt to reverse the changed Property... but the checkbox remains checked. Thus I'm looking for a way to perform the checking prior to changing the Property.

Upvotes: 3

Views: 2605

Answers (1)

DVarga
DVarga

Reputation: 21799

Based on the documentation of CheckBoxTableCell:

If you want to be notified of changes, it is recommended to directly observe the boolean properties that are manipulated by the CheckBox.

I don't say that it is impossible with CheckBoxTableCell, but most probably it is not so straightforward.

This kind of "pre-select" events can be managed in most of the cases with EventFilters.

Therefore you could try to use a normal TableColumn with the appropriate cellFactory that utilizes these EventFilters.

My example

The used cellFactory:

tableCol.setCellFactory(p -> {
    CheckBox checkBox = new CheckBox();
    TableCell<Person, Boolean> tableCell = new TableCell<Person, Boolean>() {

        @Override
        protected void updateItem(Boolean item, boolean empty) {

            super.updateItem(item, empty);
            if (empty || item == null) 
                setGraphic(null);
            else {
                setGraphic(checkBox);
                checkBox.setSelected(item);
            }
        }
    };

    checkBox.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> 
        validate(checkBox, (Person) cell.getTableRow().getItem(), event));

    checkBox.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
        if(event.getCode() == KeyCode.SPACE)
            validate(checkBox, (Person) cell.getTableRow().getItem(), event);
    });

    tableCell.setAlignment(Pos.CENTER);
    tableCell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

    return tableCell;
});

and the used validate method:

private void validate(CheckBox checkBox, Person item, Event event){
    // Validate here
    event.consume();

    Alert alert = new Alert(AlertType.CONFIRMATION);
    alert.setTitle("Confirmation Dialog");
    alert.setHeaderText("Look, a Confirmation Dialog");
    alert.setContentText("Are you ok with this?");

    // Set the checkbox if the user want to continue
    Optional<ButtonType> result = alert.showAndWait();
    if (result.get() == ButtonType.OK)
        checkBox.setSelected(!checkBox.isSelected());
}

What is does:

It renders a simple CheckBox and adds two EventFilters to this CheckBox. The first one is executed on mouse press, the second one on keypress (here the KeyCode.SPACE is used). Both of them call the validate method. The validate method consumes the event (ensures that the selection state of the CheckBox is not modified), "validates" the input (no validation on my side) then shows a confirmation dialog. If the user agrees, the selectedProperty of the CheckBox is set.

Upvotes: 6

Related Questions