How to set a different cell factory on each row in javafx TreeTableView in the same column? - javafx

I'm constructing a TreeTableView that has a column of data that has different type of data stored on each row. Some rows contain arbitrary strings, while other rows contain specific strings that are only available from a fixed set defined in a ComboBox.
This column of data must be editable by the user but I need to restrict what the users enters on each row depending on whether the row contains just a plain string or a combobox string.
My code looks something like:
TreeTableColumn<EIO, String> column = new TreeTableColumn<>( "Data" );
column.setEditable( true );
//if all data in this column contains arbitrary strings then I could do:
column.setCellFactory( c -> {
TextFieldTreeTableCell<EIO, String> cell = new TextFieldTreeTableCell<EIO, String>( new DefaultStringConverter() ) {
#Override
public void startEdit() {
TreeTableRow<EIO> row = getTableRow();
if( row == null ) {
return;
}
TreeItem<EIO> treeItem = getTreeItem( row.getIndex() );
if( treeItem == null || !treeItem.isLeaf() ) {
return;
}
super.startEdit();
}
};
return cell;
});
//if all data in the column contains string from 1 specific combobox than I could do:
column.setCellFactory( c -> {
ComboBoxTreeTableCell<EIO, String> cell = new ComboBoxTreeTableCell<EIO, String>( COMBO_OPTIONS ) {
#Override
public void startEdit() {
TreeTableRow<EIO> row = getTableRow();
if( row == null ) {
return;
}
TreeItem<EIO> treeItem = getTreeItem( row.getIndex() );
if( treeItem == null || !treeItem.isLeaf() ) {
return;
}
super.startEdit();
}
};
return cell;
});
But how do I set the cellFactory for this column based on which row in the column is being edited? The EIO instance has a method eio.getComboOptions() which will return null if this row contains an arbitrary string ... but how can I use that to defined a different factory per row?

Related

JavaFX: own TextField with a maximum of 2 characters ... can not replace all marked characters

When typing in MyTextFieldMaxLength2:
a) '1' mark and click on number 9 => will be replaced = '9' ... ok
b) '12' mark and click on number 9 => nothing happens
How can I get that even if all two numbers are marked, this is replaced by a new number?
public class MyTextFieldMaxLength2 extends TextField {
public boolean ifCondition_validate(String text) {
return (getText().length() < 2 || text.equals("")) && text.matches("[0-9]*");
}
#Override
public void replaceText(int start, int end, String text) {
if ( ifCondition_validate(text) ) {
super.replaceText(start, end, text);
}
}
#Override
public void replaceSelection(String text) {
if ( ifCondition_validate(text) ) {
super.replaceSelection(text);
}
}
}
You can do this easily by setting a TextFormatter on a standard text field, instead of subclassing text field:
TextField textField = new TextField();
UnaryOperator<TextFormatter.Change> filter = c -> {
String proposedText = c.getControlNewText();
if (proposedText.matches("[0-9]{0,2}")) {
return c ;
} else {
return null ;
}
};
textField.setTextFormatter(new TextFormatter<String>(filter));

Copy from TableView/TreeTableView with overridden cell factory

I was looking for a universal way (i.e., that can be applied to an arbitrary TableView or TreeTableView) to copy data "as you see it" from a TableView/TreeTableView. I found a couple of posts about how to copy contents from a TableView (here and here), but the important part "as you see it" is the issue with all of them.
All solutions I saw are relying on getting the data associated with each cell (pretty easy to do), and calling .toString() on it. The problem is that when you store one type (let's say a Long) as actual data in a column, and then define a custom cell factory to display it as a String (it's beyond the scope why you would do that, I just want a method that works with such table views):
TableColumn<MyData, Long> timeColumn;
<...>
timeColumn.setCellFactory(param -> new TableCell<MyData, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
super.setText(null);
} else {
super.setText(LocalDate.from(Instant.ofEpochMilli(item)).format(DateTimeFormatter.ISO_DATE));
}
}
}
);
Those methods based on converting the underlying data (which is Long here) to String will obviously not work, because they will copy a number and not a date (which is what the user sees in the table).
Possible (envisioned) solutions:
If I could get my hand on the TableCell object associated with each table cell, I could do TableCell.getText(), and we are done. Unfortunately, TableView does not allow this (have I missed a way to do it?)
I can easily get the CellFactory associated with the column, and therefore create a new TableCell (identical to that one existing in the table view):
TableCell<T, ?> cell = column.getCellFactory().call(column);
Then the problem is there's no way (again, did I miss it?) to force a TableCell to call the updateItem method! I tried to use commitEdit(T newValue), but it's pretty messy: there are checks inside, so you need to make the whole stuff (column, row, table) Editable, and call startEdit first.
2a. So the only solution that works for me, uses the Reflection to call the protected updateItem, which feels kind of dirty hacking:
// For TableView:
T selectedItem = <...>;
// OR for TreeTableView:
TreeItem<T> selectedItem = <...>;
TableCell<T, Object> cell = (TableCell<T, Object>) column.getCellFactory().call(column);
try {
Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class);
update.setAccessible(true);
Object data = column.getCellData(selectedItem);
update.invoke(cell, data, data == null);
} catch (Exception ex) {
logger.warn("Failed to update item: ", ex);
}
if (cell.getText() != null) {
return cell.getText().replaceAll(fieldSeparator, "");
} else {
return "";
}
I would appreciate any comment on it, namely if this can be achieved with less blood. Or may be indicate some problems with my solution which I missed.
Here's the full code in case someone wants to use it (in spite of its ugliness :)
package com.mycompany.util;
import com.google.common.collect.Lists;
import javafx.beans.property.ObjectProperty;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class TableViewCopyable {
private static final Logger logger = LoggerFactory.getLogger(TableViewCopyable.class.getName());
protected final String defaultFieldSep;
protected final String defaultLineSep;
protected TableViewCopyable(String defaultFieldSep, String defaultLineSep) {
this.defaultFieldSep = defaultFieldSep;
this.defaultLineSep = defaultLineSep;
}
protected static final <T> void copyToClipboard(List<T> rows, Function<List<T>, String> extractor) {
logger.info("Copied " + rows.size() + " item(s) to clipboard");
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, extractor.apply(rows)));
}
public static TableViewCopyable with(String fieldSep, String lineSep) {
return new TableViewCopyable(fieldSep, lineSep);
}
public static TableViewCopyable toCsv() {
// When using System.lineSeparator() as line separator, there appears to be an extra line break :-/
return with(",", "\n");
}
public final <T> void makeCopyable(TableView<T> table, Function<List<T>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(table.getSelectionModel().getSelectedItems(), extractor));
MenuItem copyAll = new MenuItem("Copy all");
copyAll.setOnAction(event -> copyToClipboard(table.getItems(), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
public final <T> void makeCopyable(TreeTableView<T> table, Function<List<TreeItem<T>>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(getItemsToCopy(table, true), extractor));
MenuItem copyAll = new MenuItem("Copy all (expanded only)");
copyAll.setOnAction(event -> copyToClipboard(getItemsToCopy(table, false), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
protected <T> List<TreeItem<T>> getItemsToCopy(TreeTableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
// Otherwise, copy everything
List<TreeItem<T>> list = Lists.newArrayList();
for (int i = 0; i < table.getExpandedItemCount(); i++) {
list.add(table.getTreeItem(i));
}
return list;
}
}
protected <T> List<T> getItemsToCopy(TableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
return table.getItems();
}
}
protected void addToContextMenu(ObjectProperty<ContextMenu> menu, MenuItem... items) {
if (menu.get() == null) {
menu.set(new ContextMenu(items));
} else {
for (MenuItem item : items) {
menu.get().getItems().add(item);
}
}
}
public final <T> void makeCopyable(TableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TreeTableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TableView<T> table) {
makeCopyable(table, csvVisibleColumns(table, defaultFieldSep));
}
public final <T> void makeCopyable(TreeTableView<T> table) {
makeCopyable(table, defaultFieldSep);
}
protected <T> String extractDataFromCell(IndexedCell<T> cell, Object data, String fieldSeparator) {
try {
Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class);
update.setAccessible(true);
update.invoke(cell, data, data == null);
} catch (Exception ex) {
logger.warn("Failed to updated item: ", ex);
}
if (cell.getText() != null) {
return cell.getText().replaceAll(fieldSeparator, "");
} else {
return "";
}
}
public final <T> Function<List<T>, String> csvVisibleColumns(TableView<T> table, String fieldSeparator) {
return (List<T> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TableColumn<T, Object>) col).getCellFactory().call((TableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
public final <T> Function<List<TreeItem<T>>, String> csvVisibleColumns(TreeTableView<T> table, String fieldSeparator) {
return (List<TreeItem<T>> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TreeTableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TreeTableColumn<T, Object>) col).getCellFactory().call((TreeTableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
}
Then the usage is pretty simple:
TableViewCopyable.toCsv().makeCopyable(someTreeTableView);
TableViewCopyable.toCsv().makeCopyable(someTableView);
Thanks!

Binding a TreeTableView

How can I bind TreeTableView? This gives me an error:
tblTreeView.rootProperty().bind(model.transactionProperty());
where transactionProperty():
public SimpleListProperty<Document> transactionsProperty() {
if(transactions == null){
transactions = new SimpleListProperty<>();
}
return transactions;
}
The rootProperty of a TreeTableView is an ObjectProperty<TreeItem<S>> (S The type of the TreeItem instances used in this TreeTableView)
TreeTableView<Document> treeTable = new TreeTableView<>();
ObjectProperty<TreeItem<Document>> rootProperty = treeTable.rootProperty();
it could be bound to an ObservableValue<TreeItem<S>>:
ObjectProperty<TreeItem<Document>> modelProperty = new SimpleObjectProperty<>();
rootProperty.bind(modelProperty);
modelProperty.set(myDocument); // will update the bound root property

Add up childern values and write them in parent, JavaFX

I have creating this treetable
Now I want to sum up th children values and show the result in the parent cell under the related column. For example for Function 7 in column 2 and row 2 I want to right 2.0, and for Function 11 column 4 row 4 right 1.0 (function 12 + function 13)
Here is the code which produces the treetable.
root.setExpanded(true);
Set<String> combinedKeys = new HashSet<>(dc.getCombiFunc().keySet());
Set<String> funcAllKeys = new HashSet<>(dc.getSortedfuncAll().keySet());
funcAllKeys.removeAll(dc.getCombiFunc().keySet());
for (List<String> value : dc.getCombiFunc().values()) {
funcAllKeys.removeAll(value);
}
for (String valueremained : funcAllKeys) {
ArrayList<String> tempNameId = new ArrayList<>();
tempNameId.add(dc.getSortedfuncAll().get(valueremained));
// all elements which are not in combined functions (They are all
// orphan)
root.getChildren().add(new TreeItem<String>(tempNameId.get(0)));
}
// Getting Keys that have children//////
Set<String> keyFromcombined = new HashSet<>();
List<String> valueOfCombined = new ArrayList<String>();
for (Entry<String, List<String>> ent : dc.getCombiFunc().entrySet()) {
for (int i = 0; i < ent.getValue().size(); i++)
valueOfCombined.add(ent.getValue().get(i));
}
List<String> rootKeyList = new ArrayList<>();
for (String key : combinedKeys) {
if (!valueOfCombined.contains((key))) {
keyFromcombined.add(dc.getFuncAll().get(key));
rootKeyList.add(key);
}
}
String[] rootKeys = rootKeyList.toArray(new String[rootKeyList.size()]);
// ////////////////treetable////////////////////////////
treeTable.setRoot(root);
Arrays.stream(rootKeys).forEach(
rootKey -> root.getChildren().add(
createTreeItem(dc.getCombiFunc(), rootKey)));
// ////////////////First column/////////////////////////
TreeTableColumn<String, String> firstColumn = new TreeTableColumn<>("");
treeTable.getColumns().add(firstColumn);// Tree column
firstColumn.setPrefWidth(50);
firstColumn
.setCellValueFactory(new Callback<CellDataFeatures<String, String>, ObservableValue<String>>() {
public ObservableValue<String> call(
CellDataFeatures<String, String> p) {
return new ReadOnlyStringWrapper(p.getValue()
.getValue());
}
});
// //////////////////Rest Columns////////////////////////
for (Entry<String, String> ent : dc.getSortedAssignedOrg().entrySet()) {
TreeTableColumn<String, ArrayList<String>> col = new TreeTableColumn<>();
Label label = new Label(ent.getValue());
col.setGraphic(label);
label.setTooltip(new Tooltip(label.getText()));// tooltip for column
// headers
col.setPrefWidth(45);
// cell Value Factory////////////////////////
col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, ArrayList<String>>, ObservableValue<ArrayList<String>>>() {
#Override
public ObservableValue<ArrayList<String>> call(
CellDataFeatures<String, ArrayList<String>> param) {
TreeMap<String, List<String>> temp = (TreeMap<String, List<String>>) dc
.getFuncTypeOrg().clone();
ArrayList<String> result = new ArrayList<>();
for (int i = 0; i < dc.getFuncTypeOrg().size(); i++) {
List<String> list = temp.firstEntry().getValue();
String key = temp.firstEntry().getKey();
if (list.get(1).equals(param.getValue().getValue())
&& !list.get(5).equals(label.getText())) {
result.add("white");
}
if (!root.isLeaf()) {
result.add("parent");
}
if (list.get(1).equals(param.getValue().getValue())
&& list.get(5).equals(label.getText())) {
result.add(0, list.get(2));// weight
if (list.size() > 6) {
result.add(1, list.get(list.size() - 1));// color
result.add(2, list.get(6));// App component
}
else
// result.add("white");
result.add("noOrg");
} else
temp.remove(key);
}
return new ReadOnlyObjectWrapper<ArrayList<String>>(result);
}
}); // end cell Value Factory
// //////////////cellfactory/////////////////////////
col.setCellFactory(new Callback<TreeTableColumn<String, ArrayList<String>>, TreeTableCell<String, ArrayList<String>>>() {
#Override
public TreeTableCell<String, ArrayList<String>> call(
TreeTableColumn<String, ArrayList<String>> param) {
return new TreeTableCell<String, ArrayList<String>>() {
public void updateItem(ArrayList<String> item,
boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setStyle("");
setText("");
} else if (item.contains("Green")) {
float weightInt = Float.parseFloat(item.get(0));
float res = weightInt * 1;
String resString = Float.toString(res);
this.setStyle("-fx-background-color:green");
setTooltip(new Tooltip(item.get(2)));
setText(resString);
} else if (item.contains("yellow")) {
this.setStyle("-fx-background-color:yellow");
setTooltip(new Tooltip(item.get(2)));
setText("0");
} else if (item.contains("white")) {
this.setStyle("-fx-background-color:linear-gradient(black, white); ");
// setText("DD");
} else if (item.contains("parent")) {
for (int i = 0; i < dc.getFuncTypeOrg().size(); i++) {
}
String text = param.getCellData(root).get(0);
// setText(text);
}
}
};
};
});// end cell factory
treeTable.getColumns().add(col);
}//end for loop col
TreeMap temp clones dc.getFuncTypeOrg(). In this TreeMap I have value for each child (color and the number). then in cellfactory i multiply value in color ( green = 1 and yellow = 0). Outside the loop I thought to make a treemap containg each parent as key and all it's children as value. Then I can sum up children values together and make a treemap in which first key is parent and as value the required value(or just string ArrayList ). After that I can check the name of cell in cellFactory and if it is a parent just right the value in the cell. I have been told how i can get treeitem values, and i am now here :
//after col loop ends
TreeMap<String, List<TreeItem<String>>> mytreemap = new TreeMap<>();
TreeMap<String, List<String>> parChild = new TreeMap<>();
for(TreeItem node: root.getChildren()){
if(!node.isLeaf())
mytreemap.put(node.getValue().toString(), node.getChildren());
}
for(Entry<String, List<TreeItem<String>>> ent: mytreemap.entrySet()){
for(TreeItem myItem : ent.getValue()){
// how can i fill parChild with parent as key and all its children as value?
System.out.println(ent.getKey()+" "+myItem.getValue());
}
}
treeTable.setPrefWidth(1200);
treeTable.setPrefHeight(500);
treeTable.setShowRoot(false);
treeTable.setTableMenuButtonVisible(true);
return treeTable; }
Here at setCellFactory
else if (item.contains("parent")) {
for (int i = 0; i < dc.getFuncTypeOrg().size(); i++) {
}
i can get the roots. Is there a way to do a recursion (up to the number of children and subchildren for that root cell) and add their value together and setText the parent cell to that value?
You can use onEditCommit method to add all childern values and show them in parent cell. For example
column1.setOnEditCommit((evt) -> {
//finalsing value of the cell
evt.getRowValue().getValue().setCellValue((evt.getNewValue()));
//Returns all the sibblings of the current cell
ObservableList<TreeItem> children = evt.getRowValue().getParent().getChildren();
int parentValue = 0;
for (TreeItem<> child : children) {
parentValue = parentValue + Integer.valueOf(child.getValue().getCellValue());
}
evt.getRowValue().getParent().getValue().setCellValue(parentValue);
});

how to display different set of values in combo box which has been added as column property of vaadin treetable

I have two combo box columns in my tree table, say Group and User. Now based on group selected in first column I want to show users of that group in second column. How to do this? Has anyone have an idea?
A raw example:
final Map<String, Set<String>> groupMap = new HashMap<String, Set<String>>();
Set<String> userSet1 = new HashSet<String>();
userSet1.add( "userA in group1" );
userSet1.add( "userB in group1" );
groupMap.put( "group1", userSet1 );
Set<String> userSet2 = new HashSet<String>();
userSet2.add( "userX in group2" );
userSet2.add( "userY in group2" );
groupMap.put( "group2", userSet2 );
HierarchicalContainer container = new HierarchicalContainer();
container.addItem( 1 );
container.addItem( 2 );
container.setParent( 2, 1 );
final TreeTable tt = new TreeTable( null, container );
tt.addGeneratedColumn( "group", new ColumnGenerator() {
#Override
public Object generateCell( Table source, Object itemId, Object columnId ) {
final ComboBox gCB = new ComboBox();
gCB.setData( itemId.toString() + ";group" );
for (String g : groupMap.keySet()) {
gCB.addItem(g);
}
gCB.addValueChangeListener( new ValueChangeListener() {
#Override
public void valueChange( ValueChangeEvent event ) {
String[] sArray = ((String) gCB.getData()).split( ";" );
String id = sArray[0] + ";user";
Iterator<Component> iterator = tt.iterator();
while ( iterator.hasNext() ) {
Component c = iterator.next();
if ( c instanceof ComboBox ) {
ComboBox cBox = (ComboBox) c;
if (cBox.getData().equals(id)) {
cBox.removeAllItems();
for (String u : groupMap.get(event.getProperty().getValue())) {
cBox.addItem(u);
}
}
}
}
}
} );
return gCB;
}
} );
tt.addGeneratedColumn( "user", new ColumnGenerator() {
#Override
public Object generateCell( Table source, Object itemId, Object columnId ) {
ComboBox uCB = new ComboBox();
uCB.setData( itemId.toString() + ";user" );
return uCB;
}
} );
tt.setVisibleColumns( "group", "user" );
addComponent( tt );
If GeneratedColumn is not an option then you can override bindPropertyToField:
TreeTable tt = new TreeTable( null, container ) {
#Override
protected void bindPropertyToField( Object rowId, Object colId, Property property, Field field ) {
if (field instanceof ComboBox) {
ComboBox cb = (ComboBox) field;
cb.setData(rowId.toString() + ";" + colId.toString());
}
super.bindPropertyToField( rowId, colId, property, field );
}
};

Resources