Reputation: 637
I've set up a multiselection enabled tableview and am trying to attach a listener a checkbox inserted into a column to the selection model of the table.
checkBoxTableColumn.setCellValueFactory(
cellData -> {
CheckBox checkBox = new CheckBox();
ObjectProperty<CheckBox> sop = new SimpleObjectProperty<CheckBox>();
sop.setValue(checkBox);
sop.getValue().setText("");
sop.getValue().selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(getJobTableView().getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
ObservableValue<CheckBox> ov = sop;
return ov;
}
But that doesn't change the table selection.
Edited as jurge stated
checkBoxTableColumn.setCellFactory(new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
@Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxCell(jobTableView);
}
});
and checkbox cell is as
class CheckBoxCell extends TableCell<Job, Boolean>{
private CheckBox checkBox;
private TableView<Job> jobTableView;
public CheckBoxCell(TableView<Job> tableView){
this.jobTableView = tableView;
}
@Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
checkBox = new CheckBox();
setGraphic(checkBox);
checkBox.selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(jobTableView.getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
}
}
The listener wouldn't work this time.....
edit-#2
in initialize method of controller:
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
@Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
@Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
the job class:
private BooleanProperty isContextSelected;
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
edit-- Ignore the unnecessary parts. The whole code as requested.: The controller:
package BillControl.view;
import BillControl.Controller.PopulateView;
import BillControl.Controller.Validator;
import BillControl.MainApp;
import BillControl.model.Article;
import BillControl.model.Job;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.TreeSet;
public class ArticleJobAssignmentController {
private Article article;
private Stage jobAssignStage;
private boolean okClicked = false;
private MainApp mainApp;
ArrayList<Job> selectedJobList = new ArrayList<>();
private ObservableList<Job> masterJobList = FXCollections.observableArrayList();
private ObservableList<Job> currentJobList = FXCollections.observableArrayList();
private ObservableList<Job> articleEngagementList = FXCollections.observableArrayList();
private TreeSet rowIndices = new TreeSet();
@FXML
private Label articleNameLabel;
@FXML
private Label noOfJobsLabel;
@FXML
private Button okButton;
@FXML
private Button cancelButton;
@FXML
private Label errorLabel;
@FXML
private TableView<Job> jobTableView;
@FXML
private TableColumn<Job, Boolean> checkBoxTableColumn;
@FXML
private TableColumn<Job, String> jobNameColumn;
@FXML
private TableColumn<Job, String> clientNameColumn;
@FXML
private TableColumn<Job, Integer> noOfArticlesColumn;
@FXML
private TableColumn<Job, String> alreadyEngagedColumn;
public ArticleJobAssignmentController(){
}
public void initialize(){
errorLabel.setVisible(false);
jobTableView.setEditable(true);
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
@Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
@Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
jobNameColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty()
);
noOfArticlesColumn.setCellValueFactory(
cellData -> {
SimpleIntegerProperty sip = new SimpleIntegerProperty(cellData.getValue().numberOfArticlesProperty().getValue());
ObservableValue<Integer> ov = sip.asObject();
return ov;
}
);
alreadyEngagedColumn.setCellValueFactory(
cellData -> {
Boolean engaged = false;
for(Job job: articleEngagementList){
if(job.getNumberID().equals(cellData.getValue().getNumberID())){
engaged = true;
}
}
if(engaged){
SimpleStringProperty sbp = new SimpleStringProperty("Yes");
ObservableValue<String> ov = sbp;
return ov;
}
else {
SimpleStringProperty sbp = new SimpleStringProperty("No");
ObservableValue<String> ov = sbp;
return ov;
}
}
);
jobTableView.getSelectionModel().getSelectedItems().addListener(
new ListChangeListener<Job>() {
@Override
public void onChanged(Change<? extends Job> c) {
noOfJobsLabel.setText(String.valueOf(c.getList().size()));
}
}
);
}
public void filterMasterList(){
for(Job job : masterJobList){
if(!job.getIsCompleted()){
currentJobList.add(job);
}
}
for(Job currentJob : currentJobList){
currentJob.setIsContextSelected(false);
}
}
@FXML
public void handleOkClicked(){
if(!Validator.articleJobAssignment(this)){
for(Job job : jobTableView.getSelectionModel().getSelectedItems()){
selectedJobList.add(job);
}
okClicked = true;
jobAssignStage.close();
}
else {
errorLabel.setText("Select at least one job");
errorLabel.setVisible(true);
}
}
@FXML
public void handleCancelClicked(){
jobAssignStage.close();
}
public void setArticle(Article article) {
this.article = article;
articleNameLabel.setText(article.getName());
}
public void setJobAssignStage(Stage jobAssignStage) {
this.jobAssignStage = jobAssignStage;
}
public void setOkClicked(boolean okClicked) {
this.okClicked = okClicked;
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
setMasterJobList(mainApp.getJobObservableList());
filterMasterList();
jobTableView.setItems(currentJobList);
if(article != null){
articleEngagementList = PopulateView.articleCurrentEngagementList(articleEngagementList, article.getId(), mainApp.getClientObservableList());
}
}
public Label getArticleNameLabel() {
return articleNameLabel;
}
public Label getNoOfJobsLabel() {
return noOfJobsLabel;
}
public Button getOkButton() {
return okButton;
}
public Button getCancelButton() {
return cancelButton;
}
public TableView<Job> getJobTableView() {
return jobTableView;
}
public TableColumn<Job, String> getJobNameColumn() {
return jobNameColumn;
}
public TableColumn<Job, String> getClientNameColumn() {
return clientNameColumn;
}
public TableColumn<Job, Integer> getNoOfArticlesColumn() {
return noOfArticlesColumn;
}
public ObservableList<Job> getMasterJobList() {
return masterJobList;
}
public void setMasterJobList(ObservableList<Job> masterJobList) {
this.masterJobList = masterJobList;
}
public boolean isOkClicked() {
return okClicked;
}
public ArrayList<Job> getSelectedJobList() {
return selectedJobList;
}
}
the job class(ignore the constructors):
package BillControl.model;
import BillControl.Controller.PopulateItems;
import BillControl.GeneralUtils.DateUtil;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Job {
private String numberID;
private StringProperty name;
private ObjectProperty<Client> client;
private StringProperty clientName;
private ObjectProperty<Date> startPeriod;
private ObjectProperty<Date> endPeriod;
private ObjectProperty<Date> startDate;
private ObjectProperty<Date> targetDate;
private BooleanProperty isDelayed;
private LongProperty remainingDays;
private LongProperty delayedDays;
private BooleanProperty isCompleted;
private ObjectProperty<Date> completionDate;
private LongProperty daysToComplete;
private StringProperty partner;
private IntegerProperty numberOfArticles;
private BooleanProperty isContextSelected;
private String clientID;
public Job(Client client){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>(client);
this.clientName = new SimpleStringProperty(client.getName());
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
// todo check fill others logic
}
public Job(ObservableList clientList, String numberID){
this.numberID = numberID;
// this.numberID = null;
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty();
this.numberOfArticles = new SimpleIntegerProperty();
this.isContextSelected = new SimpleBooleanProperty(false);
PopulateItems.populateJob(this);
Client selectedClient = null;
for(Object clientObject : clientList){
Client queriedClient = (Client) clientObject;
String name = queriedClient.getName();
String queriedName = PopulateItems.clientID2NameHelper(this.getClientID());
if(name.equals(queriedName)){
selectedClient = (Client) clientObject;
break;
}
}
this.setClient(selectedClient);
this.setClientName(this.getClient().getName());
this.fillOthers(true);
}
public Job(){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty("");
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
}
public void fillOthers(Boolean filledJob){
if(filledJob){
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
try {
Date startDate = this.getStartDate();
Date completionDate = this.getCompletionDate();
Date currentDate = DateUtil.getCurrentDate();
Date targetDate = this.getTargetDate();
if (this.getIsCompleted()){
// completion days
this.daysToComplete = new SimpleLongProperty();
long duration = completionDate.getTime() - startDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setDaysToComplete(diffInDays);
}
else{
this.remainingDays = new SimpleLongProperty();
this.isDelayed = new SimpleBooleanProperty();
if (targetDate.after(currentDate) && !this.getIsCompleted()){
// remaining days
long duration = targetDate.getTime() - currentDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(diffInDays);
this.setIsDelayed(false);
}
else if (targetDate.before(currentDate) && !this.getIsCompleted()) {
// delayed days
this.delayedDays = new SimpleLongProperty();
this.setIsDelayed(true);
long duration = currentDate.getTime() - targetDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(0);
this.setDelayedDays(diffInDays);
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
else {
//TODO client creation form job
}
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public Client getClient() {
return client.get();
}
public ObjectProperty<Client> clientProperty() {
return client;
}
public void setClient(Client client) {
this.client.set(client);
}
public Date getStartDate() {
return startDate.get();
}
public ObjectProperty<Date> startDateProperty() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate.set(startDate);
}
public Date getTargetDate() {
return targetDate.get();
}
public ObjectProperty<Date> targetDateProperty() {
return targetDate;
}
public void setTargetDate(Date targetDate) {
this.targetDate.set(targetDate);
}
public boolean getIsDelayed() {
return isDelayed.get();
}
public BooleanProperty isDelayedProperty() {
return isDelayed;
}
public void setIsDelayed(boolean isDelayed) {
this.isDelayed.set(isDelayed);
}
public long getRemainingDays() {
return remainingDays.get();
}
public LongProperty remainingDaysProperty() {
return remainingDays;
}
public void setRemainingDays(long remainingDays) {
this.remainingDays.set(remainingDays);
}
public long getDelayedDays() {
return delayedDays.get();
}
public LongProperty delayedDaysProperty() {
return delayedDays;
}
public void setDelayedDays(long delayedDays) {
this.delayedDays.set(delayedDays);
}
public boolean getIsCompleted() {
return isCompleted.get();
}
public BooleanProperty isCompletedProperty() {
return isCompleted;
}
public void setIsCompleted(boolean isCompleted) {
this.isCompleted.set(isCompleted);
}
public Date getCompletionDate() {
return completionDate.get();
}
public ObjectProperty<Date> completionDateProperty() {
return completionDate;
}
public void setCompletionDate(Date completionDate) {
this.completionDate.set(completionDate);
}
public long getDaysToComplete() {
return daysToComplete.get();
}
public LongProperty daysToCompleteProperty() {
return daysToComplete;
}
public void setDaysToComplete(long daysToComplete) {
this.daysToComplete.set(daysToComplete);
}
public String getPartner() {
return partner.get();
}
public StringProperty partnerProperty() {
return partner;
}
public void setPartner(String partner) {
this.partner.set(partner);
}
public Integer getNumberOfArticles() {
return numberOfArticles.get();
}
public IntegerProperty numberOfArticlesProperty() {
return numberOfArticles;
}
public void setNumberOfArticles(int numberOfArticles) {
this.numberOfArticles.set(numberOfArticles);
}
public String getNumberID() {
return numberID;
}
public String getClientName() {
return clientName.get();
}
public StringProperty clientNameProperty() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName.set(clientName);
}
public Date getStartPeriod() {
return startPeriod.get();
}
public ObjectProperty<Date> startPeriodProperty() {
return startPeriod;
}
public void setStartPeriod(Date startPeriod) {
this.startPeriod.set(startPeriod);
}
public Date getEndPeriod() {
return endPeriod.get();
}
public ObjectProperty<Date> endPeriodProperty() {
return endPeriod;
}
public void setEndPeriod(Date endPeriod) {
this.endPeriod.set(endPeriod);
}
public String getClientID() {
return clientID;
}
public void setClientID(String clientID) {
this.clientID = clientID;
}
public boolean getIsContextSelected() {
return isContextSelected.get();
}
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
public void setIsContextSelected(boolean isContextSelected) {
this.isContextSelected.set(isContextSelected);
}
}
Upvotes: 2
Views: 16099
Reputation: 2154
Ok, so this one way to do it. You'll need a BooleanProperty in your backing model to hold the value of the check box so that the table will 'remember' if that rows check box should be selected or not if the row scrolls out of view and then back again.
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( new Callback<TableColumn<Job,Boolean>, TableCell<Job,Boolean>>()
{
@Override
public TableCell<Job,Boolean> call( TableColumn<Job,Boolean> param )
{
return new CheckBoxTableCell<Job,Boolean>()
{
{
setAlignment( Pos.CENTER );
}
@Override
public void updateItem( Boolean item, boolean empty )
{
if ( ! empty )
{
TableRow row = getTableRow();
if ( row != null )
{
int rowNo = row.getIndex();
TableViewSelectionModel sm = getTableView().getSelectionModel();
if ( item ) sm.select( rowNo );
else sm.clearSelection( rowNo );
}
}
super.updateItem( item, empty );
}
};
}
} );
checkCol.setEditable( true );
checkCol.setMaxWidth( 50 );
checkCol.setMinWidth( 50 );
Upvotes: 8
Reputation: 2154
You are using checkBoxTableColumn.setCellValueFactory
incorrectly.
Your TableView has data items of type T, and the setCellValueFactory
method on a column is there to tell the column what value it must extract out of an object of type T to display.
You however are returning an observable value containing a GUI component (CheckBox), whereas you should be returning an observable Boolean value extracted from cellData
.
See here for an Oracle tutorial on TableView: http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJAGAAEE
Adding a checkbox column to a table where changes to the table checkbox are propogated back to the model object is quite simple:
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( CheckBoxTableCell.forTableColumn( checkCol ) );
Note that "checkBoxValue" is the partial name of a property method in Job called checkBoxValueProperty() that returns a BooleanProperty. (It doesn't have to be called checkBoxValue, you can give it a different name, but it must end with Property.)
Upvotes: 3