Showing posts with label JavaFX. Show all posts
Showing posts with label JavaFX. Show all posts

Saturday, 4 June 2016

JavaFX TreeTableView Example with Different Entities

In an earlier post, I had showed how to build a tree-table component (in Swing with SwingX JXTreeTable component) with 2 different entities (which have a HAS-A relationship). In this post, let us examine how to achieve the same in JavaFX TreeTableView.

As indicated in my earlier post, in real scenario(s), we will need to deal with showing a tree-table view with different entities, for example, to display a department with list of employees belonging to the department. These may share a relationship at the db level, but it will be a HAS-A relationship at object level. When we attemp to display this in a tree-table, note that the parent is one type of object and the children (leaf) another.

The official JavaFX TreeTableView tutorial available on Oracle site, actually does display a department and a list of employees as children. But, if you notice the code, it uses only one object - an Employee object throughout (even for the department). The department is just displayed as a trick of using the first object as root (where the name is set and email is left as empty). This is good for illustration purposes, but, it is important to know how to deal with 2 different objects for such scneario(s).

Let us first create the entities. I will be using the same attributes (except photo for Employee) from the earlier example.

public class Employee {

    private int id;
    private String name;
    private Date doj;

    public Employee(int id, String name, Date doj) {
        this.id = id;
        ...
    }
    //setters and getters not shown for brevity
}

public class Department {

    private int id;
    private String name;
    private List<Employee> employeeList;

    public Department(int id, String name, List<Employee> empList) {
        this.id = id;      
        ...
    }

    public List<Employee> getEmployeeList() {
        return employeeList;
    }

    public void setEmployeeList(List<Employee> employeeList) {
        this.employeeList = employeeList;
    }

    //other setters and getters
}

As JavaFX came into existence after Java 1.5 (the Java API version), everything in JavaFX is generified. So, right from declaring the TreeTableView to adding columns, everywhere the entity is specified. However, for us, we will be dealing with 2 different entities. So, we will not specify the entity anywhere. We will just use the normal syntax for dealing with objects like:
TreeTableView treeTableView = new TreeTableView(dummyRoot);
instead of
TreeTableView<Employee> treeTableView = new TreeTableView<>(dummyRoot);

As we are going to show a list of departments (where each department will have a list of employees), I am using a dummy root object (this root object can represent an Organization when we want to show the root), like:
final TreeItem dummyRoot = new TreeItem();

 Now, I create the TreeTableView with this dummyRoot tree item. Then I call the setShowRoot(false) to hide the root. Again, note the lack of usage of generics here.

Now, we need to add all our items to this root. We will deal with that later.

Let's now create the columns. We need to show 3 columns (id, name and doj) for Employee. For Department, the 3rd column will not display any value. First, I create the column, like:
TreeTableColumn idColumn = new TreeTableColumn("Id");

Next step is to specify how the column is going to fetch the value from the entity and display it.
We need to set the cellValueFactory by passing a javafx.util.Callback which will be type parameterized as follows:
Callback<TreeTableColumn.CellDataFeatures<S,T>, ObservableValue<T>> 

The Callback interface has one method call() which we need to override. The call() method actually takes TreeTableColumn.CellDataFeatures<S,T> as param and returns ObservableValue<T>. Here, S is the parent object that represents that row and T is the type for that column.  This will be called back whenever JavaFX decides to update its view.

As is the norm in JavaFX, we would normally generify the call with the object that we expect, say Employee and the column type, say, String. However, in our case, as the parent object can either be Employee or Department, we will not be able to use generics. Likewise, the column may actually have Integer type for Employee object and String type for Department object (not in this example, but, this is very much possible).

So, we will simply use the Object as the type for the parameter to the call method, So, the implementation will be like:
idColumn.setCellValueFactory(new Callback() {
    @Override
    public Object call(Object obj) {
            //return ObservableValue (without type)
}
});

Remember that as mentioned above, the incoming Object obj is actually of type TreeTableColumn.CellDataFeatures. So, we have to type cast the same and then call getValue() which will then return a TreeItem object:
((TreeTableColumn.CellDataFeatures)obj).getValue()

TreeItem represents each node in the Tree and has a value attached to it - which is actually the value object. So, we have call getValue() on the TreeItem object to get the domain object which in our case can be Employee or Department:
Object dataObj = ((TreeTableColumn.CellDataFeatures)obj).getValue().getValue();

So, now we can do an instanceof check on dataObj to identify the correct type and then invoke the correct method, like:
((Department)dataObj).getId()

We need to return a ObservableValue from the method, so, we will return a ReadOnlyStringWrapper which takes a String value as an argument. So, we can return like:
return new ReadOnlyStringWrapper(String.valueOf(((Department)dataObj).getId()));

The full call looks like:
idColumn.setCellValueFactory(new Callback() {
@Override
public Object call(Object obj) {
Object dataObj = ((TreeTableColumn.CellDataFeatures)obj).getValue().getValue();
if(dataObj instanceof Department) {
return new ReadOnlyStringWrapper(String.valueOf(((Department)dataObj).getId()));
}
else if(dataObj instanceof Employee) {
return new ReadOnlyStringWrapper(String.valueOf(((Employee)dataObj).getId()));
}
return null;
}
});

We can use lambda expressions for the same, which I have done for the dojColumn:
dojColumn.setCellValueFactory((Object obj) -> {
final Object dataObj = ((TreeTableColumn.CellDataFeatures)obj).getValue().getValue();
if(dataObj instanceof Employee) {
return new ReadOnlyStringWrapper(((Employee)dataObj).getDoj().toString());
}
return null;
});

Remember that, there is no doj value present for Department. So, we do an instanceof check only for Employee and return the employee's doj. Otherwise, we simply return null. So, for the rows where the Department object is displayed, the doj column will simply be empty.

Once all the columns have been defined, we can add this to the tree as follows:
treeTableView.getColumns().setAll(idColumn, nameColumn, dojColumn);

As we created the TreeTableView with a dummyRoot TreeItem object, all we need to show data is to simply add TreeItem objects as children to this root and in a recursive/tree manner to add children of children. Remember that the Department object contains a List<Employee> within itself. We create two such Department objects and add them to another list called deptList. Adding data to tree is accomplished as follows:
//add data to tree
List<Department> deptList = buildData();
deptList.stream().forEach((department) -> {
final TreeItem deptTreeItem = new TreeItem(department);
dummyRoot.getChildren().add(deptTreeItem);
department.getEmployeeList().stream().forEach((employee) -> {
deptTreeItem.getChildren().add(new TreeItem(employee));
});
});


When I run the application, it looks like this:


When comparing with my earlier post on achieving the same with Swing, I have to say that doing the same is much more simpler in JavaFX as I deal with individual columns one at a time. And, I have to implement only one method that is equivalent of getValueAt. I don't have to deal with the getChild() and getChildCount() methods as in Swing.

The complete source code for this sample can be found in my github repo.

Sunday, 21 December 2014

Swing JTable to JavaFX TableView

Having worked quite a bit with Swing JTable, I explored on how to work with JavaFX TableView. In this post, I describe differences between the two, which, I hope would help Swing developers looking to migrate to JavaFX.

The complete source code is available in my github repo.

I will attempt to build a simple table which displays a list of employees. This will display varied information of the employee, like id (int), name (String), salary (double), part time flag (boolean), doj (LocalDate).

Let us first define the Employee class which uses JavaFX property model (more on this later).

public class Employee {
    private SimpleIntegerProperty id;
    private SimpleStringProperty name;
    private SimpleDoubleProperty salary;
    private SimpleBooleanProperty partTime;
    private SimpleObjectProperty<LocalDate> doj;

    public Employee(int id, String name, double salary, LocalDate doj, boolean partTime) {
        this.id = new SimpleIntegerProperty(id);
        this.name = new SimpleStringProperty(name);
        this.salary = new SimpleDoubleProperty(salary);
        this.partTime = new SimpleBooleanProperty(partTime);
        this.doj = new SimpleObjectProperty<>(doj);
    }

    public String getName() {
        return name.get();
    }

    public void setName(String value) {
        name.set(value);
    }

    public StringProperty nameProperty() {
        return name;
    }

    //other setters and getters...
}

Now, let us write the JavaFX code to build the table. I am directly using the main class to build the GUI (without using FXML).

    @Override
    public void start(Stage stage) throws Exception {
        //define sample Employee objects
        final Employee emp1 = new Employee(1, "Ram", 23123.23, LocalDate.now(), false);
        final Employee emp2 = new Employee(2, "Krishna", 32398.76, LocalDate.now(), true);

        final ObservableList<Employee> data = FXCollections.observableArrayList(emp1, emp2);

        //initialise the TableView
        TableView<Employee> tableView = new TableView<>();        
        //define the columns in the table
        TableColumn idCol = new TableColumn("ID");
        idCol.setCellValueFactory(new PropertyValueFactory<>("id"));

        TableColumn nameCol = new TableColumn("Name");
        nameCol.setPrefWidth(100);
        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));

        //...likewise for other columns

        //add the columns to the table view
        tableView.getColumns().addAll(idCol, nameCol, salaryCol, partTimeCol, dojCol);

        //Load the data into the table
        tableView.setItems(data);

        StackPane root = new StackPane();
        root.getChildren().add(tableView);
        
        Scene scene = new Scene(root, 450, 300);

        stage.setTitle("JavaFX TableView Sample");
        stage.setScene(scene);
        stage.show();
    }

When I run this application, we get the following output:


Let me now talk about the differences between Swing JTable and JavaFX TableView (I will be numbering the differences throughout the post (I have used a 'minus' against some numbers to indicate that they are negative points)).

1) Generics
The first difference we notice is that the TableView is right-away generified, like:
private TableView<Employee> tableView = new TableView<>();

In 99% of the cases, a table displays homogeneous data, so this is actually good. The Swing components were not generified (sure, Generics came later, but even after they came, this change was done in later versions only). Without this, in Swing, there was always a kind of discomfort - a row not being openly identified with an Object. It was just a physical row.

In JavaFX, due to this, loading data to the table is easy. We basically need to set an ObservableList to the table (more on this in point 8). A convenience class FXCollections is used to build the ObservableList - we can either build this directly from separate instances or use a collection:
final ObservableList<Employee> data = FXCollections.observableArrayList(emp1, emp2);
tableView.setItems(data);

2) Scrollpane
In Swing, when we add a JTable, we will not get any scrollbars. We need to wrap the JTable within a JScrollPane and actually add the scroll pane to the view. Only then will we get the scrollbars. In JavaFX, we get this right away (you can see the horizontal scrollbar in the image). Note that the component is named TableView and not just Table (probably due to the inherent scrollbar).

3) Column names and types:
In Swing, we will usually write a TableModel class which will provide the information about column names and types (via overridden getColumnName() and getColumnClass() methods).
As we don't write a model separately in JavaFX, we add columns directly when creating the table (yes, we don't need to write a separate model - more on that later).
This is done by creating a TableColumn instance and adding the same to the table, like:
TableColumn<Employee, Integer> idCol = new TableColumn<>("ID");

This declaration of the column provides information about the type of the column and also the name of the column (display name). And then we add this to the table, like:
tableView.getColumns().add(idCol);

4) Display of values:
In Swing, the getValueAt() method defined in the TableModel is queried and is used to display the data for the cells. So, we have to write the getValueAt() usually checking the column number and returning the appropriate value (Object). The toString() method will then be invoked and displayed (by the default renderer).

In JavaFX, this is made easier. After we create a TableColumn, we need to then set a 'cellValueFactory' which will calculate the value for the cell, like:
idCol.setCellValueFactory(new PropertyValueFactory<>("id"));

This probably uses reflection to invoke the getter method and get the value. Similar to Swing JTable, the toString() is invoked on the object and the value displayed. Note that reading and displaying the values is not related to the JavaFX property model. This would work with any POJO (JavaBean model) except for the CheckBox control (which I think is a bug - JavaFX TableView does not pick the correct value of the Boolean value when a POJO is used. Even when using a JavaFX property model, the value is picked and displayed correctly only when the xxxProperty() method is present in addition to the regular getter and setter).

In our Employee class, we have a LocalDate field doj. When we add this to the table, we see that the date is displayed by it's default toString() notation.

The other alternative is to set a cell factory on our own. This is a little verbose and we will have to return an ObservableValue (the verbosity can be reduced largely with lambdas though).

5) Sorting
Sorting is supported right out-of-the-box. This is good as this was a pain point in Swing JTable.

6) Alternate Row colors and CSS:
The TableView by default uses alternate colors for rows. We can customize the colors using CSS.

-7) Alignment (Integer and Double):
In our Employee class, we have an Integer field (id) and a Double field (salary). When these are added to TableView, they are not right-aligned. This would generally be the need. In Swing JTable, this would be done automatically which was cool (achieving this in JavaFX is easy though).

8) Updating the view:
In Swing, we would usually need to write a TableModel class (that usually extends AbstractTableModel). This class will provide all information about the data of the table (including column names, row count, and the row data themselves). We can load and show the data in the JTable with this. But, what happens when the data changes?
Normally, we would need to invoke the various 'fireXXX' methods from within this class which will in turn instruct the JTable update its view. Most starters with JTable miss this out and it takes some learning curve to get this working.

JavaFX TableView handles this right from the beginning. We simply supply the table via an ObservableList and rest is taken care of. From here on, whenever the data changes, view gets updated automatically (this happens because TableView 'observes' the passed in ObservableList).
(note that this works fine with using the JavaFX property model and PropertyValueFactory. If PropertyValueFactory is used along with regular POJO, this does not work).

This is really good. Along with generified TableView, this means that we simply need to build the list and pass it by wrapping it in an ObservableList. Building the data is also more cleanly separated.

-9) Boolean values
In our Employee class, we have a Boolean field (isPartTime). When we add this to the TableView, the simple toString() representation of this value is displayed. In Swing JTable, as soon as a column type is declared as Boolean, it right-away shows the value as a check box which is really cool. In JavaFX, we need to set a cell factory to do the same (see point 11.Editing).

10) Renderers:
Writing renderers for columns is similar to that of Swing. We write an implementation separately.

11) Editing
In Swing, we need to implement the setValueAt() method where we can set the value from the edited cell to the actual object (this works in combination with isCellEditable() method which dictates which cell is editable). We can also add custom editors like JComboBox etc.

In JavaFX, we need to set a CellFactory to the column to make it editable. This is made even more easier with the ready availability of several implementations - TextFieldTableCell for String values for example.

In our example, we simply set one for the nameColumn with the call 'TextFieldTableCell.forTableColumn()'. For the ID column though, as the data type is Integer and a TextField by default deals with String, we need to use a StringConverter. We can make use of the convenience method which takes an implementation of the abstract StringConverter class as the argument. Luckily, JavaFX comes with ready implementations like NumberStringConverter (which deal with java.lang.Number from which the wrappers descend). We simply use them.

For the Boolean column, ideally we need a CheckBox which is a different control. So, we use the available CheckBoxTableCell.

Note that editing works well with the JavaFX property model with minimal code. Otherwise we would need to write custom editor code (when we use a usual POJO, the editing will still work and the table cell will also display the edited value, but, the edit will not be updated back to the object!).

Everything works including editing except for the DOJ field. For the Date, similar to the Double field, we need to use a converter. JavaFX comes with a DateStringFormatter, but unfortunately, it works with the older java.util.Date and not the LocalDate which is what we want to use. So, to make this work, we simply need to write a converter by extending the StringConverter, like:
dojCol.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<LocalDate>(){
    @Override
    public String toString(LocalDate object)
    {
        if(null == object)
            return "";
        return object.toString();
    }

    @Override
    public LocalDate fromString(String value)
    {
        if (value == null) {
            return null;
        }                
        return LocalDate.parse(value);
    }
}));

This is a very simple converter which works with the default LocalDate pattern. So, while editing, the same pattern should be used. Using a DatePicker for the same would be cool, but that needs altogether an implementation of TableCell.

The complete source code used in this post is available in my github repo.