Reputation: 53
I have a TableView with a column that should be a checkbox for a boolean value (JavaFX 16, Java 11), but for some reason the checkbox refuses to actually bind to the field of the object. Trying to use the forTableColumn static method specifically made for boolean columns already fails, and I've tried extending CheckBoxTableColumn and making a binding inside it to no avail (though I shouldn't need to do that for just the basic binding.
In the controller of my FXML I'm calling ascColumn.setCellFactory(CheckBoxTableCell.forTableColumn(ascColumn));
, with my column being
<TableColumn fx:id="ascColumn" text="Asc" prefWidth="$SHORT_CELL_WIDTH">
<cellValueFactory>
<PropertyValueFactory property="ascension"/>
</cellValueFactory>
</TableColumn>
and it works of course, since the checkbox appears, but the checking and unchecking doesn't actually reach the source object. No other column needs the field to be an ObservableValue, all others can deal with it themselves, so I'm looking for a solution to doing it with the source value being just a regular boolean. I've also tried setting the selectedStateCallback to return a BooleanProperty which I then added a listener to, but the listener never gets called.
Ultimately what I want to achieve is for the checkboxes to only appear if the object of the row meets certain conditions, for which I've made a new class that extends CheckBoxTableCell, however since I can't get the default one working in the first place, I can't get that to work either, so I need to tackle this first.
EDIT: Since I guess this wasn't enough to demonstrate the problem, here's a sample.
Controller:
public class Controller {
@FXML
private TableColumn<TestObject, Boolean> checkColumn;
@FXML
private TableView<TestObject> table;
public void initialize() {
List<TestObject> items = new ArrayList<>();
items.add(new TestObject("test1", false));
items.add(new TestObject("test2", false));
items.add(new TestObject("test3", true));
table.setItems(FXCollections.observableArrayList(items));
checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(checkColumn));
}
}
FXML:
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:controller="Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<TableView fx:id="table" editable="true">
<columns>
<TableColumn text="Name">
<cellValueFactory>
<PropertyValueFactory property="name"/>
</cellValueFactory>
</TableColumn>
<TableColumn text="Check" fx:id="checkColumn">
<cellValueFactory>
<PropertyValueFactory property="check"/>
</cellValueFactory>
</TableColumn>
</columns>
</TableView>
</GridPane>
Main class:
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
@Override
public void stop() throws Exception {
super.stop();
}
public static void main(String[] args) {
launch(args);
}
}
build.gradle:
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.9'
}
group 'org.example'
version '1.0-SNAPSHOT'
mainClassName = 'Main'
repositories {
mavenCentral()
}
test {
useJUnitPlatform()
}
javafx {
version = "16"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
As I said, everything else works, without needing to rely on the input values already being properties, and the doc of the forTableColumn method literally say that the column specifically has to be Boolean (not Observable), so unless I'm severely misunderstanding something, it's supposed to work
Upvotes: 0
Views: 668
Reputation: 1718
[...] so I'm looking for a solution to doing it with the source value being just a regular boolean. [...]
Then you can't use CheckBoxTableCell.forTableColumn()
as far as I know. You can define your own custom table cell with a CheckBox
like this:
package org.example;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class App extends Application {
@Override
public void start(Stage stage) {
// Create table and column:
TableView<Item> table = new TableView<>();
TableColumn<Item, Item> // Do not use <Item, Boolean> here!
checkBoxCol = new TableColumn<>("checkBoxCol");
table.getColumns().add(checkBoxCol);
// Some styling:
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
checkBoxCol.setStyle("-fx-alignment: center;");
// Set cell value with the use of SimpleObjectProperty:
checkBoxCol.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue()));
// Create custom cell with the check box control:
checkBoxCol.setCellFactory(tc -> new TableCell<>() {
@Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
CheckBox checkBox = new CheckBox();
// Set starting value:
checkBox.setSelected(item.isSelected());
// Add listener!!!:
checkBox.selectedProperty().addListener((observable, oldValue, newValue) ->
item.setSelected(newValue));
setGraphic(checkBox);
}
}
});
// Print items to see if the selected value gets updated:
Button printBtn = new Button("Print Items");
printBtn.setOnAction(event -> {
System.out.println("---");
table.getItems().forEach(System.out::println);
});
// Add test data:
IntStream.range(0, 3).forEach(i -> table.getItems().add(new Item()));
// Prepare and show stage:
stage.setScene(new Scene(new VBox(table, printBtn), 100, 200));
stage.show();
}
/**
* Simple class with just one "regular boolean" property.
*/
public class Item {
private boolean selected = false;
@Override
public String toString() {
return Item.class.getSimpleName()
+ "[selected=" + selected + "]";
}
// Getters & Setters:
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
public static void main(String[] args) {
launch();
}
}
Ultimately what I want to achieve is for the checkboxes to only appear if the object of the row meets certain conditions [...]
You can then add an if-condition in the updateItem()
method like e. g.:
[...]
if (item == null || empty) {
setGraphic(null);
} else {
if (showCheckBox) {
CheckBox checkBox = new CheckBox();
[...]
setGraphic(checkBox);
} else
setGraphic(null);
}
[...]
But I think it is best to work with observable properties. If you don't want to touch the original class, you can maybe create a wrapper class.
Upvotes: -1