Javafx 8 Tableview selection with checkbox

2020-03-01 02:37发布

问题:

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);
}
}

回答1:

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 );


回答2:

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.)