In vaadin7, I used GeneratedPropertyContainer to do this, eg adding row number:
wrappedContainer = new GeneratedPropertyContainer(_container);
wrappedContainer.addGeneratedProperty("#",
new PropertyValueGenerator<Integer>() {
#Override
public Integer getValue(Item item, Object itemId, Object propertyId) {
return (int) _container.indexOfId(itemId) + 1;
}
#Override
public Class<Integer> getType() {
return java.lang.Integer.class;
}
}
);
setContainerDataSource(wrappedContainer);
In vaadin8, since GeneratedPropertyContainer is deprecated, I tried to do like this:
grid.addColumn((v)->((List)_container.getData().getItems()).indexOf(v)+1);
But the index is static, when I sort the rows ascending and descending, the row number is moving too.
What I need is the first row is row number 1 and the last row is row number N, no matter how I sort the rows.
Thanks.
Unfortunately, as far as I can tell there is no simple solution out of the box. However, you can do like the following code:
Grid<MyBean> grid = new Grid<>();
grid.setDataProvider(new RowIndexDataProviderWrapper<>(DataProvider.ofItems(new MyBean("Item 1"), new MyBean("Item 2"), new MyBean("Item 3"))));
grid.addColumn(MyBean::getRowIndex).setCaption("#");
grid.addColumn(MyBean::getName).setCaption("Name");
public interface RowIndexAware {
void setRowIndex(int rowIndex);
int getRowIndex();
}
public class MyBean implements RowIndexAware {
// implement the interface (e.g. store row index in field)
// and add your bean properties
}
public class RowIndexDataProviderWrapper<T extends RowIndexAware, F> implements DataProvider<T, F> {
private DataProvider<T, F> wrapped;
public RowIndexDataProviderWrapper(DataProvider<T, F> wrapped) {
this.wrapped = wrapped;
}
// delegate all methods to be implemented for DataProvider interface
// to wrapped DataProvider with the exception of "fetch":
#Override
public Stream<T> fetch(Query<T, F> query) {
List<T> result = wrapped.fetch(query).collect(Collectors.toCollection(ArrayList::new));
for (int i = 0; i < result.size(); i++) {
result.get(i).setRowIndex(query.getOffset() + i);
}
return result.stream();
}
}
The idea is to get the row index when rows are fetched within the DataProvider and to store them in your bean.
Related
I'm trying to implement a heap sorting algorithm.
My problem is when I try to insert Elements to my PriorityQueue, it only works for one element. When I add multiple elements to it, I get these errors
Exception in thread "main" java.lang.ClassCastException: Element cannot be cast to java.lang.Comparable
at java.util.PriorityQueue.siftUpComparable(PriorityQueue.java:652)
at java.util.PriorityQueue.siftUp(PriorityQueue.java:647)
at java.util.PriorityQueue.offer(PriorityQueue.java:344)
at java.util.PriorityQueue.add(PriorityQueue.java:321)
at PQHeap.insert(PQHeap.java:47)
at PQHeap.main(PQHeap.java:17)
This is my Element class
public class Element {
public int key;
public Object data;
public Element(int i, Object o) {
this.key = i;
this.data = o;
}}
The interface:
public interface PQ {
public Element extractMin();
public void insert(Element e);
}
And this is the class, which generates the heap. Note that the main class is located here just to debug with. When I only insert Element e, it works. But when I insert f aswell, it give's me the errors above.
import java.util.*;
public class PQHeap implements PQ{
public static void main(String[] args) {
PQHeap hq = new PQHeap(5);
Element e = new Element(5, null);
hq.insert(e);
hq.insert(f);
for(int in = 0; in<hq.pq.size();in++){
System.out.println(hq.pq.remove());
}
}// end of main method
public PriorityQueue<Element> pq;
public PQHeap(int maxElms) {
this.pq = new PriorityQueue<Element>(maxElms);
}
#Override
public Element extractMin() {
Element min = pq.remove();
System.out.println(min.key);
return min;
}
#Override
public void insert(Element e) {
this.pq.add(e);
}
I have a recyclerview with a header achieved by using two different element types. In my header there is an edit text which I want to use for filtering the nonheader elements of the list. Below is my current implementation, I have one concern and one problem with it.
My concern is that what I am doing in publishResults with the notifyItemRangeRemoved and notifyItemInserted is the wrong way to update the recycler view. I originally was doing notifyDatasetChanged but his would cause the header row to be refreshed too and the edit text to lose focus. What I really want is a way to refresh only the item rows and leave the header row untouched.
My current problem is that with the existing code if I scroll down too much the edit text looses focus. I want the edit text to keep focus even if I scroll to the bottom of the list.
The code used to use a ListView with setHeaderView and that worked somehow so there must be someway of achieving the goal just not sure what the trick with a recycler view is. Any help is much appreciated.
public class SideListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Filterable {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private final List<String> data;
public List<String> filteredData;
private HeaderActionListener headerActionListener;
public SideListAdapter(Context context, ArrayList<String> data, HeaderActionListener headerActionListener) {
this.data = data;
filteredData = new ArrayList<>(data);
this.context = context;
this.headerActionListener = headerActionListener;
}
#Override
public Filter getFilter() {
return new TestFilter();
}
static class SideListItem extends RecyclerView.ViewHolder {
LinearLayout baseLayout;
public SideListItem(View itemView) {
super(itemView);
baseLayout = (LinearLayout) itemView.findViewById(R.id.settings_defaultcolor);
}
}
class SideListHeader extends SideListHeader {
EditText sort;
public SideListHeaderLoggedIn(View itemView) {
super(itemView);
sort = (EditText) itemView.findViewById(R.id.sort);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new SideListItem(v);
} else if (viewType == SideListHeader) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
return new SideListHeader(v);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
public interface HeaderActionListener {
boolean onSortEditorAction(TextView arg0, int arg1, KeyEvent arg2);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof SideListHeader) {
final SideListHeader sideListHeader = (SideListHeader) holder;
sideListHeader.sort.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
}
});
sideListHeader.sort.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
#Override
public void afterTextChanged(Editable editable) {
String result = sideListHeader.sort.getText().toString().replaceAll(" ", "");
getFilter().filter(result);
}
});
}
if (holder instanceof SideListItem) {
// Inflate normal item //
}
}
// need to override this method
#Override
public int getItemViewType(int position) {
if (isPositionHeader(position)) {
return TYPE_HEADER;
}
return TYPE_ITEM;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
//increasing getItemcount to 1. This will be the row of header.
#Override
public int getItemCount() {
return filteredData.size() + 1;
}
private class TestFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
String prefix = constraint.toString().toLowerCase();
if (prefix.isEmpty()) {
ArrayList<String> list = new ArrayList<>(data);
results.values = list;
results.count = list.size();
} else {
final ArrayList<String> list = new ArrayList<>(data);
final ArrayList<String> nlist = new ArrayList<>();
for (int i = 0 ; i < list.size(); i++) {
String item = list.get(i);
if (item.contains(prefix)) {
nlist.add(item);
}
}
results.values = nlist;
results.count = nlist.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
notifyItemRangeRemoved(1, getItemCount()-1);
filteredData.clear();
filteredData.addAll((List<String>)results.values);
for(int i = 1; i < getItemCount() - 1; i++){
notifyItemInserted(i);
}
}
}
}
I'm not sure how correct this way is, but in my code I implemented it like that
private var headerList: List<HeaderItem> = listOf(HeaderItem("Title"))
private fun searchItem(items: List<Items>, query: String) {
items.filterIsInstance<MainItem>().filter { filteredItems ->
filteredItems.header.lowercase().contains(query.lowercase())
}.let { searchedItems ->
rvAdapter.submitList(headerList + searchedItems)
}
}
This way I was able to preserve header element when I did my search
I was creating a web application in wicket and had created a table which shows the user some information. Now I wanted to manipulate this table so if the cell contained "N" the background color would be RED and if it contained "Y" the background color would be GREEN. At the moment I was having trouble to determine what is actually inside the cell. I create my table by the following:
dataTable = new DefaultDataTable<TableModalInt, String>("table", columns,
new TableModalProvider(), 100000){
#SuppressWarnings("rawtypes")
#Override
protected Item newCellItem(String id, int index, IModel model) {
Item item = super.newCellItem(id, index, model);
if (id == "3"){
item.add(AttributeModifier.replace("align", "center"));
}
return item;
}
};
I am capable of determining the cell which I wanna now check what is being displayed to the user. Any help on how i can do this? to change the color i know I'll have to add item.add(AttributeModifier.replace("bgcolor", "red")); but don't know how to tell whats inside the cell
You should do your checks in the IColumn implementation.
https://github.com/apache/wicket/blob/24e9db6c8af85043ce36e4d25a0e8a2d8dc2f49e/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/PropertyColumn.java#L94 populates the Item with a Label. You need to add a AttributeModifier to the Label.
You can also achieve your goal with pure JavaScript and/or CSS at the client side.
Item is extending list item, so you can try .getModelObject and validate it if it is "X" or "Y"
http://wicket.apache.org/apidocs/1.5/org/apache/wicket/markup/repeater/Item.html
http://wicket.apache.org/apidocs/1.5/org/apache/wicket/markup/html/list/ListItem.html#getModelObject()
This example extracts the cell value when a cell is clicked in a Wicket DataView. The model for this DataView is a Map with String keys and Integer values: Map<String,Integer>.
The PropertyColumn list is created using with column headers ("ALPHA", "BETA", "GAMMA") and property expressions: "alpha", "beta", "gamma". PropertyColumn uses the expressions to retrieve the values from the map.
The DataView is created with the list of PropertyColumns and a DataProvider. DataView uses the DataProvider to populate the PropertyColumn when the table is rendered and reacts to clicks to expose the cell values.
Cells are exposed by overriding the newCellItem(String,int,IModel) method and calling the super-class method to get the cell. This example adds a behavior to react to "onclick" events. Within the event, the cell's first child component should be the Label used to display the cell value.
The innermost model of the cell Label is the PropertyModel from the PropertyColumn.
innerModel.getPropertyExpression(): Our data map key (String).
innerModel.getObject(): The data value (Integer).
innerModel.getInnermostModelOrObject(): The list item (Map<String,Integer>).
Wicket DataView: Extract Cell Value
public class MessageLogStatus
extends WebPage
{
/** Represents serialVersionUID. */
private static final long serialVersionUID = 20150701L;
private static final Logger log = LoggerFactory.getLogger(MessageLogStatus.class);
static final String A = "alpha";
static final String B = "beta";
static final String C = "gamma";
public MessageLogStatus()
{
super();
final List<String> keys = Arrays.asList(A, B, C);
final List<Map<String,Integer>> data = Arrays.asList
(
map(A, 1).put(B, 11).put(C, 21).toMap(),
map(A, 2).put(B, 12).put(C, 22).toMap(),
map(A, 3).put(B, 13).put(C, 23).toMap(),
map(A, 4).put(B, 14).put(C, 24).toMap(),
map(A, 5).put(B, 15).put(C, 25).toMap(),
map(A, 6).put(B, 16).put(C, 26).toMap(),
map(A, 7).put(B, 17).put(C, 27).toMap(),
map(A, 8).put(B, 18).put(C, 28).toMap(),
map(A, 9).put(B, 19).put(C, 29).toMap()
);
// Using a DefaultDataTable
ISortableDataProvider<Map<String,Integer>,String> dataProvider = new SortableDataProvider<Map<String,Integer>,String>()
{
private static final long serialVersionUID = MessageLogStatus.serialVersionUID;
public Iterator<Map<String,Integer>> iterator(long first, long count)
{
int start = Math.max(0, (int) first);
int end = Math.min(data.size(), start + (int) count);
return data.subList(start, end).iterator();
}
public long size()
{
return data.size();
}
public IModel<Map<String,Integer>> model(Map<String,Integer> object)
{
return new CompoundPropertyModel<Map<String,Integer>>(object);
}
};
List<PropertyColumn<Map<String,Integer>,String>> columns = new ArrayList<PropertyColumn<Map<String,Integer>,String>>();
for (String key : keys)
{
columns.add
(
new PropertyColumn<Map<String,Integer>, String>(Model.of(key.toUpperCase()), key)
{
private static final long serialVersionUID = MessageLogStatus.serialVersionUID;
#Override
public void populateItem(Item<ICellPopulator<Map<String, Integer>>> item, String componentId,
IModel<Map<String, Integer>> rowModel)
{
super.populateItem(item, componentId, rowModel);
Map<String, Integer> entity = rowModel.getObject();
String px = getPropertyExpression();
PropertyModel<Object> propModel = new PropertyModel<Object>(rowModel, px);
log.info("Add Label to Cell: PropEx="+px+", Value="+propModel.getObject()+", entity="+entity);
}
}
);
}
//
// Wicket: <table wicket:id="dataTable"></table>
//
DataTable<Map<String,Integer>,String> dataTable =
new DataTable<Map<String,Integer>,String>("dataTable", columns, dataProvider, 5)
{
private static final long serialVersionUID = MessageLogStatus.serialVersionUID;
#Override
protected Item<IColumn<Map<String, Integer>, String>> newCellItem(final String id, final int index,
final IModel<IColumn<Map<String, Integer>, String>> model)
{
final Item<IColumn<Map<String,Integer>, String>> cell = super.newCellItem(id, index, model);
cell.add
(
new AjaxEventBehavior("onclick")
{
private static final long serialVersionUID = MessageLogStatus.serialVersionUID;
#SuppressWarnings("unchecked")
#Override
protected void onEvent(AjaxRequestTarget target)
{
if ( (cell.size() > 0) && (cell.get(0) instanceof Label) )
{
Label cellLabel = (Label) cell.get(0);
PropertyModel<Integer> cellLabelModel = (PropertyModel<Integer>) cellLabel.getInnermostModel();
String property = cellLabelModel.getPropertyExpression();
Integer value = cellLabelModel.getObject();
Map<String, Integer> entity = (Map<String,Integer>) cellLabelModel.getInnermostModelOrObject();
log.info("OnClick: Index="+index+", PropEx="+property+", Value="+value+", Entity="+entity);
}
}
}
);
return cell;
}
};
dataTable.addBottomToolbar(new NavigationToolbar(dataTable));
dataTable.addTopToolbar(new HeadersToolbar<String>(dataTable, null));
add(dataTable);
}
// Make building the data structure a little more fun :)
private MapBuilder<String, Integer> map(String key, Integer value)
{
return new MapBuilder<String, Integer>().put(key, value);
}
private static class MapBuilder<K, V>
{
Map<K, V> map = new HashMap<K, V>();
MapBuilder<K, V> put(K key, V value)
{
map.put(key, value);
return this;
}
Map<K, V> toMap()
{
return map;
}
}
}
Output
OnClick: Index=0, PropEx=alpha, Value=5, Entity={gamma=25, alpha=5, beta=15}
OnClick: Index=1, PropEx=beta, Value=15, Entity={gamma=25, alpha=5, beta=15}
OnClick: Index=2, PropEx=gamma, Value=25, Entity={gamma=25, alpha=5, beta=15}
I've got this webpage using Wicket, which loads in some data with some headlines, and then tons of rows beneath with the corresponding data.
Now, I want my data to be able to get sorted when i click on one of the headlines. From what I've found on google, SortableDataProvider is the way to go. But every example I find requires 1 parameter, whereas when I implement it, it requires two. What to do?
My class to sort is this:
public class SimpleDataView extends WebPage {
public SimpleDataView() {
addNumberOfRecords();
addHeadlines();
addRecords();
}
private void addNumberOfRecords() {
Data dataModel = getDataModel().getObject();
add(new Label("size", "Number of records: " + dataModel.numberOfRecords()));
}
private void addRecords() {
Data dataModel = getDataModel().getObject();
ListView records = new ListView("records", dataModel.getRecords()) {
#Override
protected void populateItem(ListItem item) {
item.add(new ListView("recordColumn", item.getModel()) {
#Override
protected void populateItem(ListItem item) {
item.add(new Label("value", item.getModel()));
}
});
}
};
add(records);
}
private void addHeadlines() {
Data dataModel = getDataModel().getObject();
ListView headlines = new ListView("headlines", dataModel.getHeaders()) {
#Override
protected void populateItem(ListItem item) {
item.add(new Label("headlineColumn", item.getModel()));
}
};
add(headlines);
}
private IModel<Data> getDataModel() {
IModel<Data> model = new LoadableDetachableModel<Data>() {
#Override
protected Data load() {
DataHandler dataHandler = new DataHandlerImpl();
return dataHandler.getDataFromSource();
}
};
return model;
}
}
This is a basic example in the Wicket wiki.
https://cwiki.apache.org/confluence/display/WICKET/Simple+Sortable+DataTable+Example
You can adjust the columns if you need more or less columns.
If you use SortableDataProvider, you should (when creating columns) define a sortProperty in the column's creation. Every one of the sortable-filterable columns has a constructor, with the possibility to define by hand the sortProperty.
It should by the way be identical to the generated SQL query's part that it points to.
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.