JavaFX Table Cell Formatting
Asked Answered
S

9

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

Socinus answered 10/7, 2012 at 11:27 Comment(0)
I
5

You can accomplish that through Cell Factories. See
https://mcmap.net/q/588594/-set-font-in-javafx
https://mcmap.net/q/575272/-combo-box-key-value-pair-in-javafx-2
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.

Impassive answered 10/7, 2012 at 11:46 Comment(0)
B
10

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!

Baccarat answered 26/2, 2015 at 15:21 Comment(0)
S
8

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.)
Succinctorium answered 5/4, 2016 at 14:38 Comment(1)
Yes, my experiments seem to show that even if you use a non-ISO date format (which sorts just like its String equivalent of course (2020-04-23, etc.)!) this leaves columns sortable by chronological order.Ctenophore
E
8

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")));
Enceinte answered 16/8, 2016 at 5:24 Comment(2)
but which property does is use? there is no property name in your sample.Picklock
@MaxiWu alongside the above setCellFactory(), you still need to call setCellValueFactory() to bind to an actual propertyArthritis
I
6

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!

Immingle answered 10/9, 2013 at 21:4 Comment(1)
This works fine for initially populating the table...if the date property in the model changes later than the SimpleStringProperty does not emit a notification, and the table is not updated.Enceinte
I
5

You can accomplish that through Cell Factories. See
https://mcmap.net/q/588594/-set-font-in-javafx
https://mcmap.net/q/575272/-combo-box-key-value-pair-in-javafx-2
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.

Impassive answered 10/7, 2012 at 11:46 Comment(0)
M
3

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));
Matelda answered 3/12, 2017 at 4:59 Comment(0)
C
2

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")));
            }
        }
    };
});
Columella answered 20/12, 2016 at 19:15 Comment(0)
A
0

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;
               });
Angelangela answered 23/12, 2017 at 10:33 Comment(0)
C
0

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.

Ctenophore answered 24/4, 2020 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.