samedi 15 janvier 2022

Reset checkbox selection in ChangeListener under condition

In my JavaFX application I'm using Checkboxes in a TreeView to change the visibility of nodes.

  • checkbox selected = some nodes are visible
  • checkbox deselected = some nodes are invisible

In a special case, however, the user should be prompted to confirm their selection, because problems can arise when activating a specific checkbox. A dialog window opens in which the user can choose between "Yes" and "No". If the user chooses "Yes", nodes become visible and everything is fine. But if the user chooses "No", the checkbox should be deselected again.

My idea was to check the condition (in this case press "no" in a dialog window) in the ChangeListener and if it's true, set the selected value to false.

But for whatever reason, it didn't work. After that, I figured out that it works with the refresh() method of the TreeView.

Questions

  1. Why doesn't it work without the refresh() method, why setSelected() is ignored?
  2. Should I use the refresh() method?
  3. Is there a better workaround to change the selection status?

Minimal reproducible example

Using the refresh() line will show the desired behavior: The checkbox remains unselected after clicking because 5 > 4 (5>4 simulates for example pressing "no" in a dialog window).

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.CheckTreeView;

public class HelloApplication extends Application {

    enum Names { TEST1, TEST2, TEST3, TEST4 }

    private final CheckTreeView<String> checkTreeView = new CheckTreeView<>();

    @Override
    public void start(Stage stage) {
        VBox vBox = new VBox();
        Scene scene = new Scene(vBox, 500, 500);
        setTreeView();
        vBox.getChildren().add(checkTreeView);
        stage.setScene(scene);
        stage.show();
    }

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


    public void setTreeView() {
        CheckBoxTreeItem<String> rootItem = new CheckBoxTreeItem<>("Root");
        rootItem.setExpanded(true);
        for (Names name : Names.values()) {
            CheckBoxTreeItem<String> item = new CheckBoxTreeItem<>(name.toString(), null);
            item.selectedProperty().addListener(this.onSelectionChanged(item));
            rootItem.getChildren().add(item);
        }
        this.checkTreeView.setRoot(rootItem);
    }

    private ChangeListener<Boolean> onSelectionChanged(CheckBoxTreeItem<String> item) {
        return (observableValue, previousChoice, newChoice) -> {

            if (newChoice) { // if checkbox is selected
                // if anything happens... for example press a "no" button in a dialog window
                if (5 > 4) {
                    System.out.println("reset checkbox status");
                    item.setSelected(previousChoice);
                }
            }

            // it works with refresh:
            // this.checkTreeView.refresh();
        };
    }
}

EDIT:

The solution of @VGR with

Platform.runLater(() -> item.setSelected(previousChoice));

works for the minimal reproducible example, but not for my real application. As already discussed in the comments, with Platform.runLater() it's

working most of the time

and

there's no guarantee because the exact timing is unspecified, will break f.i. if something in the pending events does also use runlater.

so it doesn't seem to be the best way to do that.




Aucun commentaire:

Enregistrer un commentaire