简体   繁体   中英

Get Checkbox value in a table in JavaFX

I have a table and it has a column with checkboxes.

到目前为止的输出

On a button click I want to find out which checkboxes are checked and which are not. So far I managed to create checkboxes in a table. The code is as follows.

public class TTEs implements Initializable {

    @FXML
    private TableView<TestObject> tableReport;

    @FXML
    private TableColumn<TestObject, String> name;

    @FXML
    private TableColumn<TestObject, Boolean> checkbox;

    @FXML
    public void getValues() {        
        //the method will get what check boxes are checked (this part is the problem)
    }

    @Override
    public void initialize(URL arg0, ResourceBundle arg1) { 
        ObservableList<TestObject> data = FXCollections.observableArrayList();
        data.add(new TestObject("Test 1", true));
        data.add(new TestObject("Test 2", false));

        tableReport.setItems(data);

        name.setCellValueFactory(new PropertyValueFactory<TestObject, String>("name"));
        checkbox.setCellValueFactory(new PropertyValueFactory<TestObject, Boolean>("checked"));

        checkbox.setCellFactory(new Callback<TableColumn<TestObject, Boolean>,
            TableCell<TestObject, Boolean>>() {

            public TableCell<TestObject, Boolean> call(TableColumn<TestObject, Boolean> p) {
                return new CheckBoxTableCell<TestObject, Boolean>();
            }
        });
    }

    //CheckBoxTableCell for creating a CheckBox in a table cell
    public static class CheckBoxTableCell<S, T> extends TableCell<S, T> {
        private final CheckBox checkBox;
        private ObservableValue<T> ov;

        public CheckBoxTableCell() {
            this.checkBox = new CheckBox();
            this.checkBox.setAlignment(Pos.CENTER);

            setAlignment(Pos.CENTER);
            setGraphic(checkBox);
        } 

        @Override public void updateItem(T item, boolean empty) {
            super.updateItem(item, empty);
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                setGraphic(checkBox);
                if (ov instanceof BooleanProperty) {
                    checkBox.selectedProperty().unbindBidirectional((BooleanProperty) ov);
                }
                ov = getTableColumn().getCellObservableValue(getIndex());
                if (ov instanceof BooleanProperty) {
                    checkBox.selectedProperty().bindBidirectional((BooleanProperty) ov);
                }
            }
        }
    }
}

When I debug, I find that:

        ov = getTableColumn().getCellObservableValue(getIndex());
        if (ov instanceof BooleanProperty) {
            checkBox.selectedProperty().bindBidirectional((BooleanProperty) ov);
        }

In the above condition, it never goes inside the if statement, meaning that the ov is not an instance of BooleanProperty . But when I print the class of ov ,

System.out.println(ov.getClass().getName());

it prints as

javafx.beans.property.ReadOnlyObjectWrapper

ReadOnlyObjectWrapper is a subclass of BooleanProperty , so why is the instanceof check not working?

Tested on Java 8.
Only 4 simple things.

1) Make CheckBoxCellFactory class. Put somewhere in your project.

public class CheckBoxCellFactory implements Callback {
    @Override
    public TableCell call(Object param) {
        CheckBoxTableCell<Person,Boolean> checkBoxCell = new CheckBoxTableCell();
        return checkBoxCell;
    }
}

2) Your model class. Person for example.

public static class Person {
   private SimpleBooleanProperty checked = new SimpleBooleanProperty(false);
   // other columns here

    public SimpleBooleanProperty checkedProperty() {
        return this.checked;
    }

    public java.lang.Boolean getChecked() {
        return this.checkedProperty().get();
    }

    public void setChecked(final java.lang.Boolean checked) {
        this.checkedProperty().set(checked);
    }

    // getter/setter for other columns

}

3) Made modifications in your fxml file. The section of your TableView -> TableColumn gonna look like this:

<TableColumn fx:id="checkBoxTableColumn" maxWidth="34.0" minWidth="26.0" prefWidth="34.0" resizable="false" sortable="false">   
<cellValueFactory><PropertyValueFactory property="checked" /></cellValueFactory>
<cellFactory><partarch.fx.CheckBoxCellFactory /></cellFactory>
</TableColumn>

4) If you want to make your checkbox being editable

  • 4.1 Open fxml in the Scene Builder.
  • 4.2 Select checkBox column and then check "Editable" property in the Properties pane.
  • 4.3 Select TableView containing your checkbox and do the same(check "Editable" property in the Properties pane).

Implementation in binaries on SF and source on GitHub

Loop through the data model of your TableView checking the boolean value that is bound to each CheckBox .

@FXML
public void getValues(){        
    ObservableList<TestObject> data = tableReport.getItems();

    for (TestObject item : data){
        //check the boolean value of each item to determine checkbox state
    }
}
package checkboxtablecelltest;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

/**
 *
 * @author reegan
 */
public class CheckBoxTableCellTest extends Application {

    @Override   
    public void start(Stage primaryStage) {
        final TableView<Person> tableView = new TableView<Person>();
        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        tableView.setItems(FXCollections.observableArrayList(
                new Person("Robert", "Plant"),
                new Person("Neil", "Young"),
                new Person("Willie", "Nelson"),
                new Person("Natalie", "Merchant")));
        tableView.getItems().get(3).setVegetarian(true);
        final TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        final TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        final TableColumn<Person, Boolean> vegetarianCol = new TableColumn<Person, Boolean>("Vegetarian");
        tableView.getColumns().addAll(firstNameCol, lastNameCol, vegetarianCol);
        firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
        lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
        vegetarianCol.setCellValueFactory(new PropertyValueFactory<Person, Boolean>("vegetarian"));
        vegetarianCol.setCellFactory(CheckBoxTableCell.forTableColumn(vegetarianCol));
        vegetarianCol.setEditable(true);
        tableView.setEditable(true);

        final BorderPane root = new BorderPane();
        root.setCenter(tableView);

        final HBox controls = new HBox(5);
        final Button infoButton = new Button("Show details");
        infoButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                for (Person p : tableView.getItems()) {
                    System.out.printf("%s %s (%svegetarian)%n", p.getFirstName(),
                            p.getLastName(), p.isVegetarian() ? "" : "not ");
                            System.out.println(tableView.getSelectionModel().getSelectedItems());

                }
                System.out.println();
            }
        });
        controls.getChildren().add(infoButton);
        root.setBottom(controls);

        Scene scene = new Scene(root, 300, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

    public static class Person {

        private StringProperty firstName;
        private StringProperty lastName;
        private BooleanProperty vegetarian;

        public Person(String firstName, String lastName) {
            this.firstName = new SimpleStringProperty(firstName);
            this.lastName = new SimpleStringProperty(lastName);
            this.vegetarian = new SimpleBooleanProperty(false);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public String getLastName() {
            return lastName.get();
        }

        public boolean isVegetarian() {
            return vegetarian.get();
        }

        public void setFirstName(String firstName) {
            this.firstName.set(firstName);
        }

        public void setLastName(String lastName) {
            this.lastName.set(lastName);
        }

        public void setVegetarian(boolean vegetarian) {
            this.vegetarian.set(vegetarian);
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public BooleanProperty vegetarianProperty() {
            return vegetarian;
        }
    }
}

`

The main problem with your approach is the CheckBoxTableCell usage: A Cell is a heavily reused 'rendering' machine. If you try to add a state, like the CheckBox variable, you are in trouble.

The most simple fix to your problem is to allocate the checkbox each time. The following code provides a working checkbox column:

public static class Member {
    private StringProperty myName;
    private BooleanProperty myCheck;

    public Member(String name, boolean checked) {
        myName = new SimpleStringProperty(name);
        myCheck = new SimpleBooleanProperty(checked);
    }

    public StringProperty nameProperty() { return myName; }    
    public BooleanProperty checkProperty() { return myCheck; }
}

VBox testTable6(VBox box) { // check box bind to cell property
    ObservableList<Member> members = FXCollections.observableArrayList();

    members.add(new Member("peter", true));
    members.add(new Member("gernot", true));
    members.add(new Member("fritz", false));

    TableView<Member> table = new TableView<Member>();
    table.prefHeightProperty().bind(box.heightProperty());
    table.setItems(members);

    TableColumn<Member,String> c1 = new TableColumn<Member,String>("Name");
    c1.setCellValueFactory(new PropertyValueFactory<Member,String>("name"));
    table.getColumns().add(c1);

    TableColumn<Member,Boolean> c2 = new TableColumn<Member,Boolean>("Membercheck");
    c2.setCellValueFactory(new PropertyValueFactory<Member,Boolean>("check"));
    c2.setCellFactory(column -> new TableCell<Member, Boolean>(){
        public void updateItem(Boolean check, boolean empty) {
            super.updateItem(check, empty);
            if (check == null || empty) {
                setGraphic(null);
            } else {
                CheckBox box = new CheckBox();
                BooleanProperty checked = (BooleanProperty)column.getCellObservableValue(getIndex());
                box.setSelected(checked.get());
                box.selectedProperty().bindBidirectional(checked);
                setGraphic(box);
            }
        }
    }
            );
    table.getColumns().add(c2);

    box.getChildren().addAll(table);
    return box;
}

As the bidirectional property binding is a weak binding, carbage collection will work properly even if you are unable to unbind explicitely.

The unchecked cast to BooleanProperty is not a good style anyhow here, sorry. Consider to access the whole object using:

...
Member member = table.getItems().get(getIndex());
box.setSelected(member.checkProperty().get());
box.selectedProperty().bindBidirectional(member.checkProperty());
....

by the way: the birdeirectional binding will NOT set the selected property in that moment, only if it is changed afterward! so the explicit assignment:

box.setSelected(member.checkProperty().get());

is mandatory here.

A little off topic - but in Kotlin you can do this:

    isPvtColumn.cellFactory = Callback { CheckBoxTableCell<KeyWithProperties, Boolean>() }

    isPvtColumn.cellValueFactory = Callback {
        a -> a.value.private
    }

This is all there is to it

In the initialize method, switch your custom factory for the one provided by JavaFX:

@Override
public void initialize(URL arg0, ResourceBundle arg1) {

     ObservableList<TestObject> data = FXCollections.observableArrayList();
     data.add(new TestObject("Test 1", true));
     data.add(new TestObject("Test 2", false));

     tableReport.setItems(data);

     name.setCellValueFactory(new PropertyValueFactory<TestObject, String>("name"));
     checkbox.setCellValueFactory(new PropertyValueFactory<TestObject, Boolean>("checked"));

     checkbox.setCellFactory(
         CheckBoxTableCell.forTableColumn(checkbox)
     );

}

Now your data is binded to the column and you can iterate over the items and check for the "checked" field:

@FXML
public void getValues(){        
    ObservableList<TestObject> data = tableReport.getItems();

    for (TestObject item : data){
        //check the boolean value of each item to determine checkbox state
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM