After hearing my colleague constantly talking about the editable grids that the company I work at designed a long time ago, I decided to do something to make him proud of me. So I wrote a application that creates an editable TableView in JavaFX that can be used to brainlessly enter in data, if that’s what you want to do. Unfortunately JavaFX didn’t want to make this nice and simple to do and I even found a bug in the JavaFX code while writing the example code… So buckle up as there is a lot of code in this post and don’t worry I’ll put some explanations and even pictures in so you don’t get lost.

tableexample1

Check out Getting started with JavaFX if your need some background knowledge in what will be explained in this post.

Lets start with the fxml for setting up the table, the rest of the fxml code can be found on my Github.

Some simple things you can see from quickly looking at the code is that a TableView has been defined with some columns which each have names and some have fx:id‘s to be used within the controller later on. An important feature that we need to notice is the cellValueFactory and PropertyValueFactory that are defined within each of the TableColumn tags. These map to the model that the table’s data will be displaying where the properties defined in the PropertyValueFactory tags will match to the model’s fields. This will become clearer just below.

Now that the table’s basic layout has been constructed lets get on to setting up and representing the data that will be displayed in it. The first thing we need to do is create a object / model that will represent each row in the table and where each column will match to a property in from the model.

The first thing that is important to notice is that the types of the fields are not what you would normally expect, like a String or double for example, but are defined as properties instead. This is not 100% necessary to use and a String could be used instead of a SimpleStringProperty or a Date instead of a SimpleObjectProperty<Date>. What the property does is wrap the value it holds, which are accessed via get() or set(), and listens for events that are fired on the value. This means that you are able to add listeners or bind it to other properties which is not something that you could do with a simple String. The getters are 100% required to allow the display the values in the table and the setters are optional and only required if you want to do some setting, like editing a value for example. Also notice that they use the get() and set() methods to access the wrapped value of the property instead of returning or changing the actual property. The names of the properties inside the model do not matter, but do you remember where we defined names inside the PropertyValueFactory tags in the fxml earlier? We need the names defined in these tags to match up to the getters and setters in the model. If they don’t match it wont crash or anything but your not going to get any data display in the unmatched columns.

tableexample2
Property name does not match getter

To test this if you changed the PropertyValueFactory name for one of the fields and ran the code it would not display anything in that column, but if you then changed the name of the getter while leaving name of the models property the same it will now display correctly.

It is also possible to add the columns into the TableView within the Java code rather than in the fxml. The piece of code below is an example of how you could of added the dateOfBirth column to the table.

The table has now been defined in the fxml and the model to represent the data has been successfully created. Its time to get onto the main functionality and making the table editable.

Lets break that down into smaller chunks of code.

This is the basic code required to pass some data into the table. Notice that the table is defined as a TableView<PersonTableData> showing that the data it stores is represented by the model PersonTableData. The data that will be stored in the table is held within a ObservableList<PersonTableData> which is like the properties from earlier allowing us to listen for changes by using listeners. Once these are both setup we will link them together by calling table.setItems(data) and populate the data. I created a populateData() method in this example which in real situations might be a call to database which will return data or objects which are converted into the models stored in the table (PersonTableData in this example).

Setting the cell factory overrides the default allowing us to change the functionality of how data is displayed in the table. This piece of code allows the dateOfBirth column’s values be displayed in the form of “dd/MM/yyyy” instead of the default Date.toString() output which will normally look pretty ugly to store in a table. I did this by defining a my own version of a TableCell called EditCell and a converter MyDateStringConverter to convert the date into the format I desire. These will be explained later. The setOnEditCommit is used to save the committed value when the user has changed the value in the column by updating the PersonTableData model for the edited row.

tableexample3
Using the default Date.toString()

The salary column follows the same format as the dateOfBirth column but MyDoubleStringConverter was used instead to convert the input into a double value.

Going back to the EditCell that I mentioned earlier, this is a TableCell that extends TextFieldTableCell. Credit to james-d for writing the piece of code below.

To make the the cells in the table editable the default functionality of the cells needed to be overridden. The most important methods that needed to be changed from their default were the commitEdit and cancelEdit methods as by default cancelEdit will not attempt to commit the new value in the cell.

I want to bring your attention to this pretty ugly piece of code. This is needed to handle the key presses that occur when a cell is being edited. The events will not be fired when moving between cells only once you begin editing a value. With this piece of code we can move to the adjacent cells using the arrow keys or tab which is a nice feature for when you need to enter lots of values to different cells meaning you don’t need to use your mouse to select them.

tableexample4
Can move to adjacent cells from edit

Getting back to the converters that I mentioned earlier, they are pretty simple as there are already converters to use in JavaFX, but they don’t work if you put in invalid inputs… So these extend their functionality but are able to handle incorrect inputs.

So we have defined some editable cells in the table, but we need to actually prepare the table to be editable.

The method call your see in the code above is allowing the table to be edited which is then followed by allowing the individual cells to be selected rather than a whole row at once. The setOnKeyPressed event is required to allow us to traverse between the cells, which was mentioned earlier, without needing to be editing them first. Unfortunately the method TableView.getSelectionModel().selectPrevious() does not work correctly. As it does not let you select the previous cell when you are in the first cell in the last row of the table. It seems that a -1 was left in unnecessarily, so I copied the method and removed the -1

pos.getRow() < table.getItems().size() - 1

became

pos.getRow() < table.getItems().size()

The last piece of code in this example is adding a new row into the table by taking some values from text fields.

There’s not much to explain in this example as it is simply taking the values from the text fields and if valid a new PersonTableData model is created and added to the ObservableList which will then be displayed in the table.

If you have reached this point, well done! I know that was a lot of code to read through, I’m looking at the word count while writing this and it looks like I have written an essay. So anyway, by using the code that you have seen in this tutorial you should be able to make a fully editable grid which unfortunately requires a lot of configuration to get working… But there’s no reason for you to be sad as you have seen example of how to do it and if that’s not enough here is a link to the all the example code on Github.