Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Please Explain How to Use CheckBoxTableCell

I would like to know more about how to practically use or subclass (if necessary) CheckBoxTableCell. There is one specific case I would like to use this class, whereby the check box doesn't bind to the underlying data model property.

Suppose that I have a column call 'Select' that has check boxes in them. This column, more or less serve as a visual marker against the row. An user could tick these boxes and then use a button to execute some action against the ticked rows (e.g. delete all ticked rows).

I've search for tutorials on this subject. Whilst there were some tutorials or explanations on it, however, they involved some backing data model relating to the checkbox.

So I'm looking for a more detail explanation on this, where the checkbox is dynamically generated and serve as an auxilliary aid to the user interface such as the example that was explained above. Furthermore, I would like to know how does editing comes into play and coding it correctly to standards and conventions, particularly when Java 8 introduced updates with property, new javafx classes, etc.

If it assists, a common reference example could be a data model of 'Person', which only has one property 'Name'. A ObservableList could be bounded to a TableView which displays the names. And the other column set to the leftmost side (of the tableview) are check boxes against each name. Lastly, at least one button allow some form of manipulation of the list of person. And to make it simple, that button simply removes person(s) in the list based on whether a tick was marked against the person's name when the button is actioned. Furthermore, a button could made to add new person, but it could be optional.

I hope I've written topic in a concise and detailed manner and that there is a definitive solution to it. Thank you in advance to anyone that can provide any relevant information.

like image 508
pntran84 Avatar asked Aug 21 '14 06:08

pntran84


2 Answers

Java 8

In controller:

checked.setCellValueFactory(
      param -> param.getValue().isChecked()
);

checked.setCellFactory(CheckBoxTableCell.forTableColumn(checked));

In model:

public class FileFound {

private String fileName;
private Long fileSize;
private String fileExt;
private String fullPath;
private Path filePath;
private BooleanProperty checked = new SimpleBooleanProperty(true);


public String getFileName() {
    return fileName;
}

public void setFileName(String fileName) {
    this.fileName = fileName;
}

public Long getFileSize() {
    return fileSize;
}

public void setFileSize(Long fileSize) {
    this.fileSize = fileSize;
}

public String getFileExt() {
    return fileExt;
}

public void setFileExt(String fileExt) {
    this.fileExt = fileExt;
}

public String getFullPath() {
    return fullPath;
}

public void setFullPath(String fullPath) {
    this.fullPath = fullPath;
}

public Path getFilePath() {
    return filePath;
}

public void setFilePath(Path filePath) {
    this.filePath = filePath;
}

public ObservableBooleanValue isChecked() {
    return checked;
}

public void setChecked(Boolean checked) {
    this.checked.set(checked);
}}
like image 145
Anton Nikanorov Avatar answered Oct 21 '22 07:10

Anton Nikanorov


Using the data model Person example described as above, a boolean property, e.g. Registered status, is added and so a third table column called 'Registered' is added to the TableView.

Consider the code example:

//The Data Model
public class Person
{
    /*
     * Fields
     */
    private StringProperty firstName;

    private StringProperty lastName;

    private BooleanProperty registered;


    /* 
     * Constructors
     */
    public Person(String firstName, String lastName, boolean registered)
    {
        this.firstName = new SimpleStringProperty(firstName);
        this.lastName = new SimpleStringProperty(lastName);
        this.registered = new SimpleBooleanProperty(registered);
    }

    public Person()
    {
        this(null, null, false);
    }

    /*
     * Properties
     */

    public StringProperty firstNameProperty() { return firstName; }

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

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


    public StringProperty lastNameProperty() { return lastName; }

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

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


    public BooleanProperty registeredProperty() { return registered; }

    public boolean isRegistered() { return this.registered.get(); }

    public void setRegistered(boolean value) { this.registered.set(value); }
}


//Dummy values for the data model
List<Person> personList = new ArrayList<Person>();
personList.add( new Person("John", "Smith", true) ;
personList.add( new Person("Jack", "Smith", false) );


TableView<Person> tblView = new TableView<Person>();

tblView.setItems( FXCollections.observableList(personList) );

TableColumn firstName_col = new TableColumn("First Name");
TableColumn lastName_col = new TableColumn("Last Name");
TableColumn registered_col = new TableColumn("Registered");

firstName.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
lastName.setCellValueFactory(new PropertyValueFactory<Person,String>("lastName"));

registered_col.setCellValueFactory(
new Callback<CellDataFeatures<Person,Boolean>,ObservableValue<Boolean>>()
{
    //This callback tell the cell how to bind the data model 'Registered' property to
    //the cell, itself.
    @Override
    public ObservableValue<Boolean> call(CellDataFeatures<Person, Boolean> param)
    {   
        return param.getValue().registeredProperty();
    }   
});

//This tell how to insert and render a checkbox in the cell.
//
//The CheckBoxTableCell has the updateItem() method which by default links up the
//cell value (i.e. the 'Registered' property to the checkbox.  And this method is
//automatically call at the appropriate time, such as when creating and rendering
//the cell (I believe).
//
//In this case, as the registed_col.setCellValueFactory() method has specified
//'Registered' in the actual data model (i.e. personList), therefore the checkbox will
//be bound to this property.
registered_col.setCellFactory( CheckBoxTableCell.forTableColumn(registered_col) );

tblView.getColumns.addAll(firstName_col, lastName_col, registered_col);

//table display preference - should not affect this exercise/problem
tblView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);


This code works correctly. When iterating either through the data model or tblView UI component to access the Registered property, it will show the correct values, and even when it changes (i.e. un/ticking the checkbox).

The original problem of trying to add a checkbox that does not bind to the data model is yet to be answered.

Suppose another column call 'Select' is added and it simply contains a checkbox to visually indicate that one or more rows can be (or is) selected. Reiterating, this column of checkboxes does not have any relevant meaning to the data model Person. And thus creating a property inside the Person class to hold this value is semantically unnecessary and probably considered poor coding practice. So how this problem solved?

How is the linking or binding an arbitrary BooleanProperty (or a list of it for each person in the personList) to the corresponding checkbox?

TableColumn select_col = new TableColumn("Select");

//Set a boolean property to represent cell value.
select_col.setCellValueFactory(
new Callback<CellDataFeatures<Person,Boolean>,ObservableValue<Boolean>>()
{
    @Override
    public ObservableValue<Boolean> call(CellDataFeatures<Person,Boolean> param)
    {
        //PROBLEM -- What Code goes here?
    }
};

//This call should be okay - it would display the checkbox according to the provided
//boolean (property).  This was proven with 
//registered_col.setCellFactory(CheckBoxTableCell.forTableColumn(registered_col)
select_col.setCellFactory(CheckBoxTableCell.forTableColumn(select_col);


One solution is to create an (anonymous) inner class that subclass Person and add the 'Select' property. Using similar code to the 'Registered' property and its table column to link the 'Select' property, it should work. As stated above, subclassing just to solve a visual problem breaks the data model semantic.

A better solution could be - to create an internal list of boolean property for each person in personList and linking them together. So how is the appropriate boolean property being retrieve that correspond to each person in personList in the setCellValueFactory() method? One possible solution to this is to use the index position in the personList, the list of boolean property for the select column, and the row index. So it comes down to getting the row index inside the setCellValueFactory(CellDataFeatures), and how is this done correctly?

Consider the code:

TableColumn<Person,Boolean> select_col = new TableColumn<Person,Boolea>("Select");

List<BooleanProperty> selectedRowList = new ArrayList<BooleanProperty>();

//This callback allows the checkbox in the column to access selectedRowList (or more
//exactly, the boolean property it contains
Callback<Integer,ObservableValue<Boolean>> selectedStateSelectColumn =
    new Callback<Integer,ObservableValue<Boolean>>()
{

    //index in this context reference the table cell index (I believe)
    @Override
    public ObservableValue<Boolean> call(Integer index)
    {
        return selectedRowList.get(index);
    }
}

//Initialise the selectedRowList
for(Person p : personList)
{
    //initially, it starts off as false, i.e. unticked state
    selectedRowList.add( new SimpleBooleanProperty() ); 
}

select_col.setCellValueFactory(
    new Callback<CellDataFeatures<Person,Boolean>,ObservableValue<Boolean>>
{
    //retrieve the cell index and use it get boolean property in the selectedRowList
    @Override
    public ObservableValue<Boolean> call(CellDataFeatures<Person,Boolean> cdf)
    {
        TableView<Person> tblView = cdf.getTableView();

        Person rowData = cdf.getValue();

        int rowIndex = tblView.getItems().index( rowData );

        return selectedRowList.get( rowIndex );
    }
}

select_col.setCellFactory(
    CheckBoxTableCell.forTableColumn(selectedStateSelectColumn));

These snippet does work for me. It just needs to be reorganise to compile and run. However, the essentials part are correct.

This problem or situation is very common, yet it took me days to implement and solve. I hope this serve well for others.

like image 22
pntran84 Avatar answered Oct 21 '22 08:10

pntran84