TableColumn<Event,Date> releaseTime = new TableColumn<>("Release Time");
releaseTime.setCellValueFactory(
new PropertyValueFactory<Event,Date>("releaseTime")
);
How can I change the format of releaseTime? At the moment it calls a simple toString on the Date object.
If you want to preserve the sorting capabilities of your TableColumn, none of the solutions above is valid: if you convert your Date to a String and show it that way in your TableView; the table will sort it as such (so incorrectly).
The solution I found was subclassing the Date class in order to override the toString() method. There is a caveat here though: the TableView uses java.sql.Date instead of java.util.Date; so you need to subclass the former.
import java.text.SimpleDateFormat;
public class CustomDate extends java.sql.Date {
public CustomDate(long date) {
super(date);
}
#Override
public String toString() {
return new SimpleDateFormat("dd/MM/yyyy").format(this);
}
}
The table will call that method in order to print the date.
Of course, you need to change too your Date class in the TableColumn declaration to the new subclass:
#FXML
TableColumn<MyObject, CustomDate> myDateColumn;
Same thing when you attach your object attribute to the column of your table:
myDateColumn.setCellValueFactory(new PropertyValueFactory< MyObject, CustomDate>("myDateAttr"));
And finally, for the shake of clarity this is how you declare the getter in your object class:
public CustomDate getMyDateAttr() {
return new CustomDate(myDateAttr.getTime()); //myDateAttr is a java.util.Date
}
It took me a while to figure out this due to the fact that it uses java.sql.Date behind the scenes; so hopefully this will save other people some time!
Update for Java FX8:
(I'm not sure it is the good place for that answer, but I get the problem in JavaFX8 and some things have changed, like java.time package)
Some differences with the previous answers:
I keep the date type on the column, so I need to use both cellValueFactory and cellFactory.
I Make a generic reusable method to generate the cellFactory for all date columns.
I use java 8 date for java.time package! But the method could be easily reimplemented for java.util.date.
#FXML
private TableColumn<MyBeanUi, ZonedDateTime> dateColumn;
#FXML
public void initialize () {
// The normal binding to column
dateColumn.setCellValueFactory(cellData -> cellData.getValue().getCreationDate());
//.. All the table initialisation and then
DateTimeFormatter format = DateTimeFormatter .ofLocalizedDate(FormatStyle.SHORT);
dateColumn.setCellFactory (getDateCell(format));
}
public static <ROW,T extends Temporal> Callback<TableColumn<ROW, T>, TableCell<ROW, T>> getDateCell (DateTimeFormatter format) {
return column -> {
return new TableCell<ROW, T> () {
#Override
protected void updateItem (T item, boolean empty) {
super.updateItem (item, empty);
if (item == null || empty) {
setText (null);
}
else {
setText (format.format (item));
}
}
};
};
}
The advantages are that:
The column is typed with a "java8 Date" to avoid the sort problem evoqued by #Jordan
The method "getDateCell" is generic and can be used as an util function for all Java8 Time types (Local Zoned etc.)
I'd recommend using Java generics to create re-usable column formatter that takes any java.text.Format. This cuts down on the amount of boilerplate code...
private class ColumnFormatter<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private Format format;
public ColumnFormatter(Format format) {
super();
this.format = format;
}
#Override
public TableCell<S, T> call(TableColumn<S, T> arg0) {
return new TableCell<S, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setGraphic(new Label(format.format(item)));
}
}
};
}
}
Examples of usage
birthday.setCellFactory(new ColumnFormatter<Person, Date>(new SimpleDateFormat("dd MMM YYYY")));
amplitude.setCellFactory(new ColumnFormatter<Levels, Double>(new DecimalFormat("0.0dB")));
I needed to do this recently -
dateAddedColumn.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<Film, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Film, String> film) {
SimpleStringProperty property = new SimpleStringProperty();
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
property.setValue(dateFormat.format(film.getValue().getCreatedDate()));
return property;
}
});
However - it is a lot easier in Java 8 using Lamba Expressions:
dateAddedColumn.setCellValueFactory(
film -> {
SimpleStringProperty property = new SimpleStringProperty();
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
property.setValue(dateFormat.format(film.getValue().getCreatedDate()));
return property;
});
Hurry up with that Java 8 release oracle!
You can accomplish that through Cell Factories. See
https://stackoverflow.com/a/10149050/682495
https://stackoverflow.com/a/10700642/682495
Although the 2nd link is about ListCell, the same logic is totally applicable to TableCells too.
P.S. Still if you need some sample code, kindly will attach here.
An universal solution could be as simple as that:
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public interface AbstractConvertCellFactory<E, T> extends Callback<TableColumn<E, T>, TableCell<E, T>> {
#Override
default TableCell<E, T> call(TableColumn<E, T> param) {
return new TableCell<E, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(convert(item));
}
}
};
}
String convert(T value);
}
And its sample usage:
TableColumn<Person, Timestamp> dateCol = new TableColumn<>("employment date");
dateCol.setCellValueFactory(new PropertyValueFactory<>("emploumentDateTime"));
dateCol.setCellFactory((AbstractConvertCellFactory<Person, Timestamp>) value -> new SimpleDateFormat("dd-MM-yyyy").format(value));
This is what i did and i worked perfectly.
tbColDataMovt.setCellFactory((TableColumn<Auditoria, Timestamp> column) -> {
return new TableCell<Auditoria, Timestamp>() {
#Override
protected void updateItem(Timestamp item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
}
}
};
});
You can easily pipe Properties of different type and put a formatter or converter in between.
//from my model
ObjectProperty<Date> valutaProperty;
//from my view
TableColumn<Posting, String> valutaColumn;
valutaColumn.setCellValueFactory(
cellData -> {
SimpleStringProperty property = new SimpleStringProperty();
property.bindBidirectional(cellData.getValue().valutaProperty, new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN));
return property;
});
The StringConverter classes are another mechanism.
TextFieldTableCell has a constructor as follows: public TextFieldTableCell(StringConverter<T> converter).
... and StringConverters consist of subclasses such as LocalDateStringConverter. A default implementation would then be:
new TextFieldTableCell( new LocalDateStringConverter() );
... this is not bad, but the parameter-less LocalDateStringConverter uses dates of the format 'dd/mm/yyyy' both for parsing (fromString() method) and toString(). But there are other constructors where you can pass a FormatStyle or DateTimeFormatter.
From my experiments, however, StringConverters are slightly problematic in that it is difficult to catch the DateTimeParseException thrown by fromString() with an invalid date.
This can be remedied by creating your own StringConverter class, e.g.:
class ValidatingLocalDateStringConverter extends LocalDateStringConverter {
boolean valid;
#Override
LocalDate fromString(String value) {
valid = true;
if (value.isBlank()) return null;
try {
// NB wants ISO
return LocalDate.parse( value );
} catch ( DateTimeParseException e) {
valid = false;
}
return null;
}
#Override
String toString( LocalDate date ){
// NB returns ISO or the String "null" with null date value (!)
String s = date.toString();
return s.equals( 'null' )? '' : s;
}
}
Using this StringConverter solution will mean that dates are sorted according to chronological order, regardless of the String representation.
Related
I am learning JavaFX and came across a requirement in my JavaFX application where I want to create a Datepicker using an ArrayList of dates. I need to disable all other dates which are not present in this list (dateList). Finally, according to the date selected, I need to render a ComboBox with values in morningSlot and eveningSlot. The structure of the object is as follows.
Schedule {
List<LocalDate> dateList;
String morningSlot;
String eveningSlot;
}
Solved the same using the below code. Thanks VGR for the input.
final Callback<DatePicker, DateCell> dayCellFactory = new Callback<DatePicker, DateCell>() {
#Override
public DateCell call(final DatePicker datePicker)
{
return new DateCell() {
#Override
public void updateItem(LocalDate item, boolean empty)
{
super.updateItem(item, empty);
setDisable(empty || !dateList.contains(item));
}
};
}
};
In my table I have one cell that does not update without interaction with the table.
I found the reason already here Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
My problem is, the default value of the cells item is LocalDate.MIN and I want my cell to contain "---" as long as the item has this default value. When I update the item, I want the cell to contain the current date string.
Item Class:
public class ItemEv {
private final ObjectProperty<LocalDate> openedAt;
#XmlJavaTypeAdapter(LocalDateAdapter.class)
public final LocalDate getOpenedAt() {
return openedAt.get();
}
public final ObjectProperty<LocalDate> openedAtProperty() {
return this.openedAt;
}
public final void setOpenedAt(LocalDate openedAt) {
this.openedAt.set(openedAt);
}
}
in another CellFactory I set the new value: i.setOpenedAt(LocalDate.now());
this is working but not wanted:
openedAtColumnEv.setCellValueFactory(cellData -> cellData.getValue().openedAtProperty().asString());
and this is what I tried so far:
openedAtColumnEv.setCellValueFactory(new Callback<CellDataFeatures<ItemEv, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<ItemEv, String> i) {
if (i.getValue().getOpenedAt().equals(LocalDate.MIN)) {
return new SimpleStringProperty("---");
}
return i.getValue().openedAtProperty().asString();
}
});
and this:
openedAtColumnEv.setCellValueFactory(cellData -> {
if(cellData.getValue().openedAtProperty().getValue().equals(LocalDate.MIN)) {
return new SimpleStringProperty("---");
}
return cellData.getValue().openedAtProperty().asString();
});
Both of my tests return either SimpleStringProperty or StringBinding which should be fine.
In my tests I made a mistake where the first return in the IF statement does never return true, then the cell values show the standard string for LocalDate.MIN and get updated immediately when the item property changes.
Im a bit lost on this. Please forgive my bad english, Im not a native speaker.
If the property in the model class is an ObjectProperty<LocalDate>, then the column should be a TableColumn<ItemEv, LocalDate>, not a TableColumn<ItemEv, String>.
Implementing the cellValueFactory directly (typically with a lambda expression) is always preferable to using the legacy PropertyValueFactory class. You never "need to use" a PropertyValueFactory (and never should).
The cellValueFactory is only used to determine what data to display. It is not used to determine how to display the data. For the latter, you should use a cellFactory.
So:
private TableColumn<ItemEv, LocalDate> opendAtColumnEv ;
// ...
openedAtColumnEv.setCellValueFactory(cellData -> cellData.getValue().openedAtProperty());
openedAtColumnEv.setCellFactory(column -> new TableCell<ItemEv, LocalDate>() {
#Override
protected void updateItem(LocalDate openedAt, boolean empty) {
super.updateItem(openedAt, empty);
if (openedAt == null || empty) {
setText("");
} else {
if (openedAt.equals(LocalDate.MIN)) {
setText("---");
} else {
// Note you can use a different DateTimeFormatter as needed
setText(openedAt.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
}
});
Edit:
I first voted to close as a duplicate after finding this answer by James_D, which sets a TextFormatter on a TextField. But then firstly I found that (in a TableView context) the method TextFieldTableCell.forTableColumn() does not in fact draw a TextField when it starts editing, but instead a LabeledText, which does not subclass TextInputControl, and therefore does not have setTextFormatter().
Secondly, I wanted something which acted in a familiar sort of way. I may have produced the "canonical" solution in my answer: let others judge.
This is a TableColumn in a TableView (all Groovy):
TableColumn<Person, String> ageCol = new TableColumn("Age")
ageCol.cellValueFactory = { cdf -> cdf.value.ageProperty() }
int oldAgeValue
ageCol.onEditStart = new EventHandler(){
#Override
public void handle( Event event) {
oldAgeValue = event.oldValue
}
}
ageCol.cellFactory = TextFieldTableCell.forTableColumn(new IntegerStringConverter() {
#Override
public Integer fromString(String value) {
try {
return super.fromString(value)
}
catch ( NumberFormatException e) {
// inform user by some means...
println "string could not be parsed as integer..."
// ... and cancel the edit
return oldAgeValue
}
}
})
Excerpt from class Person:
public class Person {
private IntegerProperty age;
public void setAge(Integer value) { ageProperty().set(value) }
public Integer getAge() { return ageProperty().get() }
public IntegerProperty ageProperty() {
if (age == null) age = new SimpleIntegerProperty(this, "age")
return age
}
...
Without the start-edit Handler, when I enter a String which can't be parsed as an Integer NumberFormatException not surprisingly gets thrown. But I also find that the number in the cell then gets set to 0, which is likely not to be the desired outcome.
But the above strikes me as a pretty clunky solution.
I had a look at ageCol, and ageCol.cellFactory (as these are accessible from inside the catch block) but couldn't see anything better and obvious. I can also see that one can easily obtain the Callback (ageCol.cellFactory), but calling it would require the parameter cdf, i.e. the CellDataFeatures instance, which again you'd have to store somewhere.
I'm sure a validator mechanism of some kind was involved with Swing: i.e. before a value could be transferred from the editor component (via some delegate or something), it was possible to override some validating mechanism. But this IntegerStringConverter seems to function as a validator, although doesn't seem to provide any way to revert to the existing ("old") value if validation fails.
Is there a less clunky mechanism than the one I've shown above?
Edit
NB improved after kleopatra's valuable insights.
Edit2
Overhauled completely after realising that the best thing is to use the existing default editor and tweak it.
I thought I'd give an example with a LocalDate, slightly more fun than Integer. Given the following class:
class Person(){
...
private ObjectProperty<LocalDate> dueDate;
public void setDueDate(LocalDate value) {
dueDateProperty().set(value);
}
public LocalDate getDueDate() {
return (LocalDate) dueDateProperty().get();
}
public ObjectProperty dueDateProperty() {
if (dueDate == null) dueDate = new SimpleObjectProperty(this, "dueDate");
return dueDate;
}
Then you create a new editor cell class, which is exactly the same as TextFieldTreeTableCell (subclass of TreeTableCell), which is used by default to create an editor for a TreeTableView's table cell. However, you can't really subclass TextFieldTreeTableCell as, for example, its essential field textField is private.
So you copy the code in full from the source* (only about 30 lines), and you call it
class DueDateEditor extends TreeTableCell<Person, LocalDate> {
...
You then have to create a new StringConverter class, subclassing LocalDateStringConverter. The reason for subclassing is that if you don't do that it is impossible to catch the DateTimeParseException thrown by fromString() when an invalid date is received: if you use LocalDateStringConverter the JavaFX framework unfortunately catches it, without any frames in the stack trace involving your own code. So you do this:
class ValidatingLocalDateStringConverter extends LocalDateStringConverter {
boolean valid;
LocalDate fromString(String value) {
valid = true;
if (value.isBlank()) return null;
try {
return LocalDate.parse(value);
} catch (Exception e) {
valid = false;
}
return null;
}
}
Back in your DueDateEditor class you then rewrite the startEdit method as follows. NB, as with the TextFieldTreeTableCell class, textField is actually created lazily, when you first edit.
#Override
void startEdit() {
if (! isEditable()
|| ! getTreeTableView().isEditable()
|| ! getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
if (textField == null) {
textField = CellUtils.createTextField(this, getConverter());
// this code added by me
ValidatingLocalDateStringConverter converter = getConverter();
Callable bindingFunc = new Callable(){
#Override
Object call() throws Exception {
// NB the return value from this is "captured" by the editor
converter.fromString( textField.getText() );
return converter.valid? '' : "-fx-background-color: red;";
}
}
def stringBinding = Bindings.createStringBinding( bindingFunc, textField.textProperty() );
textField.styleProperty().bind( stringBinding );
}
CellUtils.startEdit(this, getConverter(), null, null, textField);
}
}
NB don't bother trying to look up CellUtils: this is package-private, the package in question being javafx.scene.control.cell.
To set things up you do this:
Callback<TreeTableColumn, TreeTableCell> dueDateCellFactory =
new Callback<TreeTableColumn, TreeTableCell>() {
public TreeTableCell call(TreeTableColumn p) {
return new DueDateEditor( new ValidatingLocalDateStringConverter() );
}
}
dueDateColumn.setCellFactory(dueDateCellFactory);
... the result is a nice, reactive editor cell: when containing an invalid date (acceptable pattern yyyy-mm-dd; see other LocalDate.parse() variant for other formats) the background is red, otherwise normal. Entering with a valid date works seamlessly. You can also enter an empty String, which is returned as a null LocalDate.
With the above, pressing Enter with an invalid date sets the date to null. But overriding things to prevent this happening (i.e. forcing you to enter a valid date, or cancel the edit, e.g. by Escape) is trivial, using the ValidatingLocalDateStringConverter's valid field:
#Override
void commitEdit( LocalDate newDueDate ){
if( getConverter().valid )
super.commitEdit( newDueDate );
}
* I couldn't find this online. I extracted from the javafx source .jar file javafx-controls-11.0.2-sources.jar
My goal is to display every field of an instance of a Class in a tableView. The Class has a field of type enum which has a field of type String.
The enum shall be displayed in a ComboBox as it's String field name.
Of course it also has to be editable.
Now what isn't working:
The String field of enum class is only displayed if the ComboBox is clicked on, otherwise it is the name of the enum constant. Also, if another enum in the combobox is selected, it can't be commited for edit. Clicking return doesn't deselect the Combobox neither is the method commitEdit invoked. If an other column is selected for edit, the attempted edit is cancelled.
I put some effort into trying to figure this out, so I thought maybe one could help me here.
As the original task is about much bigger classes in enterprise software, I abstracted it to ask this question.
I know I could make the column holding the enum of type String and make it work with MyEnum.values() and MyEnum.valueOf() but that could not go into production due to bad performance as the original classes are too big.
Here is my code as an example, if you don't understand the problems just try to use the combobox once and you'll see.
Class which is type of TableView:
public class MyClass {
private MyEnum myEnum;
private String string;
public MyClass(MyEnum myEnum, String string) {
this.myEnum = myEnum;
this.string = string;
}
public MyEnum getMyEnum() {
return myEnum;
}
public void setMyEnum(MyEnum myEnum) {
this.myEnum = myEnum;
}
public String getString() {
return string;
}
}
It's enum field:
public enum MyEnum {
EnumOne("First Enum"),
EnumTwo("Second Enum");
private String name;
public String getName() {
return name;
}
private MyEnum(String name) {
this.name = name;
}
}
FX App:
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<MyClass> items = FXCollections.observableArrayList();
items.add(new MyClass(MyEnum.EnumOne, "String"));
TableView<MyClass> table = new TableView(items);
table.setEditable(true);
TableColumn<MyClass, MyEnum> myEnumColumn = new TableColumn();
TableColumn<MyClass, String> stringColumn = new TableColumn();
stringColumn.setCellFactory(TextFieldTableCell.forTableColumn());
stringColumn.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getString()));
myEnumColumn.setCellFactory((param) -> new MyEnumComboBoxCell());
myEnumColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper(data.getValue().getMyEnum()));
myEnumColumn.setOnEditCommit(
event -> {
event.getRowValue().setMyEnum(event.getNewValue());
System.out.println(event.getRowValue().getMyEnum());
});
table.getColumns().addAll(myEnumColumn, stringColumn);
StackPane root = new StackPane();
root.getChildren().add(table);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
class MyEnumComboBoxCell extends ComboBoxTableCell<MyClass, MyEnum> {
private ComboBox<MyEnum> box;
public MyEnumComboBoxCell() {
box = new ComboBox<>(FXCollections.observableArrayList(MyEnum.values()));
box.setCellFactory(new Callback<ListView<MyEnum>, ListCell<MyEnum>>() {
#Override
public ListCell<MyEnum> call(ListView<MyEnum> param) {
return new ListCell<MyEnum>() {
#Override
protected void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( item != null ) setText(item.getName());
}
};
}
});
}
#Override
public void startEdit() {
super.startEdit();
setGraphic(box);
}
#Override
public void commitEdit(MyEnum newValue) {
super.commitEdit(newValue);
if ( newValue != null ) {
setText(newValue.getName());
getTableView().getSelectionModel().getSelectedItem().setMyEnum(newValue);
box.setValue(newValue);
}
}
#Override
public void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( empty ) {
setGraphic(null);
} else {
setGraphic(null);
setText(item.getName());
}
}
}
Instead of setting the name in updateItem use a StringConverter like:
public class MyEnumConverter extends StringConverter<MyEnum>{
#Override public String toString(MyEnum enumConstant) {
return enumConstant.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}
Then in the cell's constructor:
this.setConverter(new MyEnumConverter());
Edit: You may not #Override all of the ComboBoxTableCell's methods, since all of them are working like you want. On the other hand you should not specify an own ComboBox for the table cell, since it has one. You just have to add a StringConverter and set the items.
You may use like this:
myEnumColumn.setCellFactory((param) -> new ComboBoxTableCell<>(new StringConverter<MyEnum>() {
#Override public String toString(MyEnum object) {
return object.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}, MyEnum.values()));
If you prefer you can create a separate class for the StringConverter like I mentioned earlier, then just simply:
myEnumColumn.setCellFactory(factory -> new ComboBoxTableCell<>(new MyEnumConverter(), MyEnum.values()));
You can also get rid of the myEnumColumn.setOnEditCommit.
Thanks a lot! Actually I've spent a day on this, partially with another person, so this is really appreciated! :)
But:
I have to implement setOnEditCommit or otherwise the myEnum field backing the tableColumn does not change! With this everything works. Without, only what is displayed is changed.
myEnumColumn.setOnEditCommit(
event ->
{
event.getRowValue().setMyEnum(event.getNewValue());
});
This is my first time working on JavaFx and I'm following this tutorial just as a template: http://code.makery.ch/library/javafx-8-tutorial/part3/.
For my application, I'm working with 2 columns on the left side, telephone number and the call start date/time. I'm wanting to change the formatting of the data in the table as it's currently coming through as yyyy-MM-ddThh:mm.
I can't seem to figure out where to place the formatting piece at. I have a date formatter function that you can find at the link above, but it's returning a string and giving me errors. Thanks for any help you can give. Here are some code snippets of what I'm working with.
Controller:
#FXML
private void initialize() {
// Initialize the person table with the two columns.
billingNumberColumn.setCellValueFactory(cellData -> cellData.getValue().billingNumberProperty());
callStartColumn.setCellValueFactory(cellData -> cellData.getValue().callStartProperty());
}
Model:
public LocalDateTime getCallStart() {
return callStart.get();
}
public void setCallStart(LocalDateTime callStart) {
this.callStart.set(callStart);
}
public ObjectProperty<LocalDateTime> callStartProperty() {
return callStart;
}
Date Format:
public static String format(ObjectProperty<LocalDateTime> callStart) {
if (callStart == null) {
return null;
}
return DATE_FORMATTER.format((TemporalAccessor) callStart);
}
Use a cellFactory. TextFieldTableCell provides a method to create a cell factory given a converter. As converter a LocalDateTimeStringConverter can be used:
callStartColumn.setCellValueFactory(cellData -> cellData.getValue().callStartProperty());
callStartColumn.setCellFactory(TextFieldTableCell.forTableColumn(new LocalDateTimeStringConverter(DATE_FORMATTER, DATE_FORMATTER)));
Specify column
TableColumn<Person, LocalDateTime> column = new TableColumn<>("Birth");
Code for this is quite complicated and not really good lookin.
Make sure yo utake care of empty case or / null handled when no data is in the cell
column.setCellFactory(
new Callback<TableColumn<Person, LocalDateTime>, TableCell<Person, LocalDateTime>>() {
#Override
public TableCell<Person, LocalDateTime> call(TableColumn<Person, LocalDateTime> param
) {
return new TableCell<Person, LocalDateTime>() {
#Override
protected void updateItem(LocalDateTime item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
} else {
//FORMAT HERE AND CALL setText() with formatted date string
}
}
};
}
}
);