Dans mon application de bureau Java, j'ai une table dans laquelle je veux avoir une colonne avec CheckBox.
J'ai trouvé où cela a été fait http://www.jonathangiles.net/javafx/2.0/CellFactories/ mais le téléchargement n'est pas disponible et parce que je ne sais pas dans quel délai Jonathan Giles répondra à mon email. Je pensais que je demanderais ...
Comment mettre une CheckBox dans une cellule de mon TableView?
Vous devez définir une CellFactory sur le TableColumn.
Par exemple:
Callback<TableColumn<TableData, Boolean>, TableCell<TableData, Boolean>> booleanCellFactory =
new Callback<TableColumn<TableData, Boolean>, TableCell<TableData, Boolean>>() {
@Override
public TableCell<TableData, Boolean> call(TableColumn<TableData, Boolean> p) {
return new BooleanCell();
}
};
active.setCellValueFactory(new PropertyValueFactory<TableData,Boolean>("active"));
active.setCellFactory(booleanCellFactory);
class BooleanCell extends TableCell<TableData, Boolean> {
private CheckBox checkBox;
public BooleanCell() {
checkBox = new CheckBox();
checkBox.setDisable(true);
checkBox.selectedProperty().addListener(new ChangeListener<Boolean> () {
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(isEditing())
commitEdit(newValue == null ? false : newValue);
}
});
this.setGraphic(checkBox);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
this.setEditable(true);
}
@Override
public void startEdit() {
super.startEdit();
if (isEmpty()) {
return;
}
checkBox.setDisable(false);
checkBox.requestFocus();
}
@Override
public void cancelEdit() {
super.cancelEdit();
checkBox.setDisable(true);
}
public void commitEdit(Boolean value) {
super.commitEdit(value);
checkBox.setDisable(true);
}
@Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
checkBox.setSelected(item);
}
}
}
Utilise javafx.scene.control.cell.CheckBoxTableCell<S,T>
et le travail est terminé!
ObservableList< TableColumn< RSSReader, ? >> columns =
_rssStreamsView.getColumns();
[...]
TableColumn< RSSReader, Boolean > loadedColumn = new TableColumn<>( "Loaded" );
loadedColumn.setCellValueFactory(
new Callback<CellDataFeatures<RSSReader,Boolean>,ObservableValue<Boolean>>(){
@Override public
ObservableValue<Boolean> call( CellDataFeatures<RSSReader,Boolean> p ){
return p.getValue().getCompleted(); }});
loadedColumn.setCellFactory(
new Callback<TableColumn<RSSReader,Boolean>,TableCell<RSSReader,Boolean>>(){
@Override public
TableCell<RSSReader,Boolean> call( TableColumn<RSSReader,Boolean> p ){
return new CheckBoxTableCell<>(); }});
[...]
columns.add( loadedColumn );
UPDATE: code identique utilisant expressions lambda Java 8
ObservableList< TableColumn< RSSReader, ? >> columns =
_rssStreamsView.getColumns();
[...]
TableColumn< RSSReader, Boolean > loadedColumn = new TableColumn<>( "Loaded" );
loadedColumn.setCellValueFactory( f -> f.getValue().getCompleted());
loadedColumn.setCellFactory( tc -> new CheckBoxTableCell<>());
[...]
columns.add( loadedColumn );
Le nombre de lignes est divisé par deux! (16 ==> 8)
UPDATE: code identique utilisant Java 10 "var" contextuel
var columns = _rssStreamsView.getColumns();
[...]
var loadedColumn = new TableColumn<RSSReader, Boolean>( "Loaded" );
loadedColumn.setCellValueFactory( f -> f.getValue().getCompleted());
loadedColumn.setCellFactory( tc -> new CheckBoxTableCell<>());
[...]
columns.add( loadedColumn );
EDIT pour ajouter un exemple éditable fonctionnel complet (Java 8)
public class Os {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty delete = new SimpleBooleanProperty();
public Os( String nm, boolean del ) {
name .set( nm );
delete.set( del );
}
public StringProperty nameProperty () { return name; }
public BooleanProperty deleteProperty() { return delete; }
}
public class FxEditableCheckBox extends Application {
@Override
public void start( Stage stage ) throws Exception {
final TableView<Os> view = new TableView<>();
final ObservableList<TableColumn<Os, ?>> columns = view.getColumns();
final TableColumn<Os, Boolean> nameColumn = new TableColumn<>( "Name" );
nameColumn.setCellValueFactory( new PropertyValueFactory<>( "name" ));
columns.add( nameColumn );
final TableColumn<Os, Boolean> loadedColumn = new TableColumn<>( "Delete" );
loadedColumn.setCellValueFactory( new PropertyValueFactory<>( "delete" ));
loadedColumn.setCellFactory( tc -> new CheckBoxTableCell<>());
columns.add( loadedColumn );
final ObservableList<Os> items =
FXCollections.observableArrayList(
new Os( "Microsoft Windows 3.1" , true ),
new Os( "Microsoft Windows 3.11" , true ),
new Os( "Microsoft Windows 95" , true ),
new Os( "Microsoft Windows NT 3.51", true ),
new Os( "Microsoft Windows NT 4" , true ),
new Os( "Microsoft Windows 2000" , true ),
new Os( "Microsoft Windows Vista" , true ),
new Os( "Microsoft Windows Seven" , false ),
new Os( "Linux all versions :-)" , false ));
view.setItems( items );
view.setEditable( true );
final Button delBtn = new Button( "Delete" );
delBtn.setMaxWidth( Double.MAX_VALUE );
delBtn.setOnAction( e -> {
final Set<Os> del = new HashSet<>();
for( final Os os : view.getItems()) {
if( os.deleteProperty().get()) {
del.add( os );
}
}
view.getItems().removeAll( del );
});
stage.setScene( new Scene( new BorderPane( view, null, null, delBtn, null )));
BorderPane.setAlignment( delBtn, Pos.CENTER );
stage.show();
}
public static void main( String[] args ) {
launch( args );
}
}
EDIT pour ajouter un exemple éditable fonctionnel complet (Java 10)
public class Os {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty delete = new SimpleBooleanProperty();
public Os( String nm, boolean del ) {
name .set( nm );
delete.set( del );
}
public StringProperty nameProperty () { return name; }
public BooleanProperty deleteProperty() { return delete; }
}
public class FxEditableCheckBoxJava10 extends Application {
@Override
public void start( Stage stage ) throws Exception {
final var view = new TableView<Os>();
final var columns = view.getColumns();
final var nameColumn = new TableColumn<Os, Boolean>( "Name" );
nameColumn.setCellValueFactory( new PropertyValueFactory<>( "name" ));
columns.add( nameColumn );
final var loadedColumn = new TableColumn<Os, Boolean>( "Delete" );
loadedColumn.setCellValueFactory( new PropertyValueFactory<>( "delete" ));
loadedColumn.setCellFactory( tc -> new CheckBoxTableCell<>());
columns.add( loadedColumn );
final var items = FXCollections.observableArrayList(
new Os( "Microsoft Windows 3.1" , true ),
new Os( "Microsoft Windows 3.11" , true ),
new Os( "Microsoft Windows 95" , true ),
new Os( "Microsoft Windows NT 3.51", true ),
new Os( "Microsoft Windows NT 4" , true ),
new Os( "Microsoft Windows 2000" , true ),
new Os( "Microsoft Windows Vista" , true ),
new Os( "Microsoft Windows Seven" , false ),
new Os( "Linux all versions :-)" , false ));
view.setItems( items );
view.setEditable( true );
final var delBtn = new Button( "Delete" );
delBtn.setMaxWidth( Double.MAX_VALUE );
delBtn.setOnAction( e -> {
final var del = new HashSet<Os>();
for( final var os : view.getItems()) {
if( os.deleteProperty().get()) {
del.add( os );
}
}
view.getItems().removeAll( del );
});
stage.setScene( new Scene( new BorderPane( view, null, null, delBtn, null )));
BorderPane.setAlignment( delBtn, Pos.CENTER );
stage.show();
}
public static void main( String[] args ) {
launch( args );
}
}
TableColumn select = new TableColumn("CheckBox");
select.setMinWidth(200);
select.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person, CheckBox>, ObservableValue<CheckBox>>() {
@Override
public ObservableValue<CheckBox> call(
TableColumn.CellDataFeatures<Person, CheckBox> arg0) {
Person user = arg0.getValue();
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().setValue(user.isSelected());
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
user.setSelected(new_val);
}
});
return new SimpleObjectProperty<CheckBox>(checkBox);
}
});
table.getColumns().addAll( select);
La solution la plus simple est probablement de le faire en FXML:
Commencez par créer la classe ci-dessous:
public class CheckBoxCellFactory<S, T>
implements Callback<TableColumn<S, T>, TableCell<S, T>> {
@Override public TableCell<S, T> call(TableColumn<S, T> p) {
return new CheckBoxTableCell<>();
}
}
Ensuite, incluez une fabrique de cellules dans votre FXML:
<TableColumn text="Select" fx:id="selectColumn" >
<cellFactory>
<CheckBoxCellFactory/>
</cellFactory>
</TableColumn>
Vous devez également ajouter une importation dans le FXML, telle que <?import com.assylias.factories.*?>
.
Bonus: vous pouvez rendre la fabrique plus personnalisable, par exemple pour déterminer où la case à cocher doit apparaître, en ajoutant des champs à la classe CheckBoxCellFactory
, tels que:
private Pos alignment = Pos.CENTER;
public Pos getAlignment() { return alignment; }
public void setAlignment(Pos alignment) { this.alignment = alignment; }
Et le FXML:
<cellFactory>
<CheckBoxCellFactory alignment="BOTTOM_RIGHT"/>
</cellFactory>
Il existe un moyen très simple de procéder. Vous n'avez pas besoin de modifier votre classe de modèle avec SimpleBooleanProperty ou quoi que ce soit, procédez comme suit:
1 - Supposons que vous ayez un objet "Person" avec une méthode isUnemployed:
public class Person {
private String name;
private Boolean unemployed;
public String getName(){return this.name;}
public void setName(String name){this.name = name;}
public Boolean isUnemployed(){return this.unemployed;}
public void setUnemployed(Boolean unemployed){this.unemployed = unemployed;}
}
2 - Créer la classe de rappel
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class PersonUnemployedValueFactory implements Callback<TableColumn.CellDataFeatures<Person, CheckBox>, ObservableValue<CheckBox>> {
@Override
public ObservableValue<CheckBox> call(TableColumn.CellDataFeatures<Person, CheckBox> param) {
Person person = param.getValue();
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().setValue(person.isUnemployed());
checkBox.selectedProperty().addListener((ov, old_val, new_val) -> {
person.setUnemployed(new_val);
});
return new SimpleObjectProperty<>(checkBox);
}
}
3 - Lier le rappel à la colonne de table
Si vous utilisez FXML, placez la classe de rappel dans votre colonne:
<TableView fx:id="personList" prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="196.0" text="Unemployed">
<cellValueFactory>
<PersonUnemployedValueFactory/> <!--This is how the magic happens-->
</cellValueFactory>
</TableColumn>
...
</columns>
</TableView>
N'oubliez pas d'importer la classe dans votre FXML:
<?import org.yourcompany.yourapp.util.PersonUnemployedValueFactory?>
Sans FXML, procédez comme suit:
TableColumn<Person, CheckBox> column = (TableColumn<Person, CheckBox>) personTable.getColumns().get(0);
column.setCellValueFactory(new PersonUnemployedValueFactory());
4 - C'est ça
Tout doit fonctionner comme prévu, la valeur étant définie sur le bean de support lorsque vous cliquez sur la case à cocher et la valeur de la case à cocher correctement définie lorsque vous chargez la liste des éléments de votre table.
Petit et simple.
row.setCellValueFactory(c -> new SimpleBooleanProperty(c.getValue().getIsDefault()));
row.setCellFactory(tc -> new CheckBoxTableCell<>());
Voici un exemple de travail complet montrant comment maintenir le modèle synchronisé avec la vue .....
package org.pauquette.example;
import javafx.application.Application;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class CheckBoxExample extends Application {
class BooleanCell extends TableCell<TableData, Boolean> {
private CheckBox checkBox;
public BooleanCell() {
checkBox = new CheckBox();
checkBox.setDisable(true);
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (isEditing())
commitEdit(newValue == null ? false : newValue);
}
});
this.setGraphic(checkBox);
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
this.setEditable(true);
}
@Override
public void cancelEdit() {
super.cancelEdit();
checkBox.setDisable(true);
}
public void commitEdit(Boolean value) {
super.commitEdit(value);
checkBox.setDisable(true);
}
@Override
public void startEdit() {
super.startEdit();
if (isEmpty()) {
return;
}
checkBox.setDisable(false);
checkBox.requestFocus();
}
@Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()) {
checkBox.setSelected(item);
}
}
}
// Pojo class. A Javabean
public class TableData {
SimpleBooleanProperty favorite;
SimpleStringProperty stooge;
// A javabean typically has a zero arg constructor
// https://docs.Oracle.com/javase/tutorial/javabeans/
public TableData() {
}
// but can have others also
public TableData(String stoogeIn, Boolean favoriteIn) {
stooge = new SimpleStringProperty(stoogeIn);
favorite = new SimpleBooleanProperty(favoriteIn);
}
/**
* @return the stooge
*/
public String getStooge() {
return stooge.get();
}
/**
* @return the favorite
*/
public Boolean isFavorite() {
return favorite.get();
}
/**
* @param favorite
* the favorite to set
*/
public void setFavorite(Boolean favorite) {
this.favorite.setValue(favorite);
}
/**
* @param stooge
* the stooge to set
*/
public void setStooge(String stooge) {
this.stooge.setValue(stooge);
}
}
// Model class - The model in mvc
// Typically a representation of a database or nosql source
public class TableModel {
ObservableList<TableData> stooges = FXCollections.observableArrayList();
public TableModel() {
stooges.add(new TableData("Larry", false));
stooges.add(new TableData("Moe", true));
stooges.add(new TableData("Curly", false));
}
public String displayModel() {
StringBuilder sb=new StringBuilder();
for (TableData stooge : stooges) {
sb.append(stooge.getStooge() + "=" + stooge.isFavorite() + "|");
}
return sb.toString();
}
/**
* @return the stooges
*/
public ObservableList<TableData> getStooges() {
return stooges;
}
public void updateStooge(TableData dataIn) {
int index=stooges.indexOf(dataIn);
stooges.set(index, dataIn);
}
}
public static void main(String[] args) {
launch(args);
}
private TableModel model;
private TableModel getModel() {
if (model == null) {
model = new TableModel();
}
return model;
}
@Override
public void start(Stage primaryStage) throws Exception {
final VBox view=new VBox(10);
final TableView<TableData> table = new TableView<>();
final ObservableList<TableColumn<TableData, ?>> columns = table.getColumns();
final TableModel model = getModel();
final TableColumn<TableData, String> stoogeColumn = new TableColumn<>("Stooge");
stoogeColumn.setCellValueFactory(new PropertyValueFactory<>("stooge"));
columns.add(stoogeColumn);
final Button showModelButton = new Button("Show me the Model, woo,woo,woo");
final Label showModelLabel = new Label("Model? Whats that?");
showModelButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
showModelLabel.setText(model.displayModel());
}});
final TableColumn<TableData, CheckBox> favoriteColumn = new TableColumn<TableData, CheckBox>("Favorite");
favoriteColumn.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<TableData, CheckBox>, ObservableValue<CheckBox>>() {
@Override
public ObservableValue<CheckBox> call(TableColumn.CellDataFeatures<TableData, CheckBox> arg0) {
TableData data = arg0.getValue();
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().setValue(data.isFavorite());
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov, Boolean old_val,
Boolean new_val) {
data.setFavorite(new_val);
checkBox.setSelected(new_val);
model.updateStooge(data);
}
});
return new SimpleObjectProperty<CheckBox>(checkBox);
}
});
columns.add(favoriteColumn);
table.setItems(model.getStooges());
HBox hbox = new HBox(10);
hbox.getChildren().addAll(showModelButton,showModelLabel);
view.getChildren().add(hbox);
view.getChildren().add(table);
Scene scene = new Scene(view, 640, 380);
primaryStage.setScene(scene);
primaryStage.show();
}
}
La solution la plus simple pour avoir une case à cocher ÉDITABLE liée au modèle est la suivante:
En supposant que vous ayez une classe de modèle Person
avec deux champs, une chaîne "name" et la valeur booléenne "selected":
public class Person {
private final SimpleBooleanProperty selected;
private final SimpleStringProperty name;
public Person(String name) {
this.selected = new SimpleBooleanProperty(false);
this.name = new SimpleStringProperty(name);
}
public boolean isSelected() {
return selected.get();
}
public SimpleBooleanProperty selectedProperty() {
return selected;
}
public void setSelected(boolean selected) {
this.selected.set(selected);
}
public String getName() {
return name.get();
}
public SimpleStringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}
Tout ce que vous avez à faire dans le contrôleur est:
@FXML private TableColumn<Person, Boolean> checkBoxCol;
@FXML private TableColumn<Person, String> nameCol;
@Override
public void initialize(URL location, ResourceBundle resources) {
checkBoxCol.setCellFactory(
CheckBoxTableCell.forTableColumn(checkBoxCol)
);
checkBoxCol.setCellValueFactory(
new PropertyValueFactory<>("selected")
);
nameCol.setCellValueFactory(
new PropertyValueFactory<>("name")
);
}
Voici ce qui a finalement fonctionné pour moi (notez que l'objet dans mon modèle est Candidate
et que la case à cocher détermine si elles sont ou non exclues, d'où isExcluded()
):
tableColumnCandidateExcluded.setCellValueFactory(
c -> {
Candidate candidate = c.getValue();
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().setValue(candidate.isExcluded());
checkBox
.selectedProperty()
.addListener((ov, old_val, new_val) -> candidate.setExcluded(new_val));
return new SimpleObjectProperty(checkBox);
});
C'est comme ça qu'on le fait
tbcSingleton.setCellValueFactory(data -> data.getValue().singletonProperty());
tbcSingleton.setCellFactory( param -> {
return new TableCell<FXMLController, Boolean>(){
{
setAlignment(Pos.CENTER);
}
protected void updateItem(Boolean item, boolean empty){
if(!empty && item!=null) {
CheckBox cb = new CheckBox();
cb.setSelected(item);
cb.setFocusTraversable(false);
cb.selectedProperty().addListener((obs,old,niu)->listaFXMLController.get(getIndex()).setSingleton(niu));
setGraphic(cb);
}else
setGraphic(null);
}
};
});
cb.setFocusTraversable (false) est nécessaire pour empêcher le focus de rester bloqué
setGraphic (null) est nécessaire pour effacer tout ce qui reste après la suppression d'un élément ou chaque fois que la liste source change
Voici un autre avec un ToggleGroup et ToggleButtons
tbcTipoControlador.setCellValueFactory(data -> data.getValue().controllerTypeProperty());
tbcTipoControlador.setCellFactory( param -> {
return new TableCell<FXMLController, ControllerType>() {
{
setAlignment(Pos.CENTER);
}
protected void updateItem(ControllerType item, boolean empty){
if(!empty && item!=null) {
ToggleButton tbModal = new ToggleButton("Modal");
tbModal.selectedProperty().addListener((obs,old,niu)->{
if(niu)
listaFXMLController.get(getIndex()).setControllerType(ControllerType.MODAL);
});
tbModal.setSelected(item.equals(ControllerType.MODAL));
ToggleButton tbPlain = new ToggleButton("Plain");
tbPlain.selectedProperty().addListener((obs,old,niu)->{
if(niu)
listaFXMLController.get(getIndex()).setControllerType(ControllerType.PLAIN);
});
tbPlain.setSelected(item.equals(ControllerType.PLAIN));
ToggleButton tbApplication= new ToggleButton("Application");
tbApplication.selectedProperty().addListener((obs,old,niu)->{
if(niu)
listaFXMLController.get(getIndex()).setControllerType(ControllerType.APPLICATION);
});
tbApplication.setSelected(item.equals(ControllerType.APPLICATION));
ToggleGroup gp = new ToggleGroup();
tbModal.setFocusTraversable(false);
tbPlain.setFocusTraversable(false);
tbApplication.setFocusTraversable(false);
tbModal.setPrefWidth(120);
tbPlain.setPrefWidth(120);
tbApplication.setPrefWidth(120);
gp.getToggles().addAll(tbModal,tbPlain,tbApplication);
HBox hb = new HBox();
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(tbModal,tbPlain,tbApplication);
setGraphic(hb);
}else
setGraphic(null);
}
};
});
J'ai fait quelques tests et la consommation de mémoire est fondamentalement identique à celle d'un ComboBoxTableCell
Voici à quoi ressemble ma petite application (sry, ma langue principale est l’espagnol et je la construis pour un usage personnel)
Inspiré des réponses précédentes, c'est la version la plus courte possible, je pense.
checkBoxColumn.setCellValueFactory(c -> {
c.getValue().booleanProperty().addListener((ch, o, n) -> {
// do something
});
return c.getValue().booleanProperty();
});
checkBoxColumn.setCellFactory(CheckBoxTableCell.forTableColumn(checkBoxColumn));