The CheckBox in JavaFX can be configured for two states (selected, or not) or three states (selected, unselected, or indeterminate). This indeterminate state is often useful when a checkbox is being used in a TreeView, for example. You might be implementing a tree view showing which features are installed, and need to toggle to an indeterminate state for the branch of some of the children are selected, and some are not.



In JavaFX, putting the CheckBox into a mode where it can be indeterminate is quite simple, simply set the allowIndeterminate property to true:

        CheckBox cb = new CheckBox("Indeterminate CheckBox");
        cb.setAllowIndeterminate(true);

You manipulate the state of the check box through the use of two properties: selected and indeterminate. If allowIndeterminate is true, then as the user clicks on the check box, it will cycle through three state combinations:

  • selected = true, indeterminate = false
  • selected = false, indeterminate = true
  • selected = false, indeterminate = false

Here is a full example:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class IndeterminateCheckBox extends Application {
    Label label;
    CheckBox cbox;

    @Override public void start(Stage stage) {
        label = new Label();

        cbox = new CheckBox("Indeterminate CheckBox");
        cbox.setIndeterminate(true);
        cbox.setAllowIndeterminate(true);

        ChangeListener<Boolean> listener = new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> prop, Boolean old, Boolean val) {
                updateLabel();
            }
        };

        cbox.selectedProperty().addListener(listener);
        cbox.indeterminateProperty().addListener(listener);
        updateLabel();

        VBox vbox = new VBox(7);
        vbox.setAlignment(Pos.CENTER);
        vbox.getChildren().addAll(label, cbox);
        Scene scene = new Scene(vbox, 400, 400);
        stage.setTitle("Hello CheckBox");
        stage.setScene(scene);
        stage.setVisible(true);
    }

    private void updateLabel() {
        final String txt = cbox.isIndeterminate() ? "The check box is indeterminate" :
                cbox.isSelected() ? "The check box is selected" :
                "The check box is not selected";

        label.setText(txt);
    }

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

So, whenever either the indeterminate or selected property changes, we update the label’s text to match. This is kind of gross though (now that I’ve typed it out), because I have to attach a listener to two different properties. It would be nicer if there were a proper event handler that I could attach to the CheckBox which would be notified whenever indeterminate or selected changed.

I’ve filed a JIRA RT-13992 to track this.

Meanwhile, I can also use the high level binding APIs to do this. Here is an alternative implementation.

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class IndeterminateCheckBox2 extends Application {
    @Override public void start(Stage stage) {
        CheckBox cbox = new CheckBox("Indeterminate CheckBox");
        cbox.setIndeterminate(true);
        cbox.setAllowIndeterminate(true);

        Label label = new Label();
        label.textProperty().bind(
            Bindings.when(cbox.indeterminateProperty()).
                then("The check box is indeterminate").
                otherwise(
                    Bindings.when(cbox.selectedProperty()).
                        then("The check box is selected").
                        otherwise("The check box is not selected"))
        );

        VBox vbox = new VBox(7);
        vbox.setAlignment(Pos.CENTER);
        vbox.getChildren().addAll(label, cbox);
        Scene scene = new Scene(vbox, 400, 400);
        stage.setTitle("Hello CheckBox");
        stage.setScene(scene);
        stage.setVisible(true);
    }

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

And that’s pretty darn slick, IMHO.