Maverick283 Maverick283 - 3 months ago 115
Java Question

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
TableView
s 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.

Answer

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 appropiate 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, new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent event) {
            validate(checkBox, (Person) cell.getTableRow().getItem(), event);
        }
    });

    checkBox.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>(){
        @Override
        public void handle(KeyEvent 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.

Comments