How to show String instead of integer in tableview (FXML) - javafx

I Have a field in my database table which stores integers. To make program more user-friendly I want to represent data as string instead of integer in TableViews and ComboBoxes. For example instead of 0 it should show "unsupported" and instead of 1 it should show "supported". This is one of my fields:
private final SimpleIntegerProperty status = new ReadOnlyIntegerWrapper();
public int getStatus() {
return status.get();
}
public void setStatus(int val)
{
status.set(val);
}
I retrieve and inject data to a TableView using FXML with success but it shows 0 or 1; How do I implement this in Java?
I need a solution for ComboBoxes so user can select a meaningful string instead of a number.

Create your StringConverter which can convert each status to expressions what you want.
public class StatusStringConverter extends StringConverter<Integer> {
// Manage selectable options as Integer here
final public static ObservableList<Integer> OPTIONS = FXCollections.observableArrayList(0, 1);
#Override
public String toString(Integer value) {
switch (value) {
case 0: return "unsupported";
case 1: return "supported";
default: return "-";
}
}
#Override
public Integer fromString(String string) {
return null;
}
}
Using ComboBoxTableCell with the StatusStringConverter enable to show your expressions in cells and ComboBox choices. Try it out.
table.setEditable(true);
column.setEditable(true);
column.setCellValueFactory(new PropertyValueFactory<>("status"));
column.setCellFactory(ComboBoxTableCell.forTableColumn(
new StatusStringConverter(), StatusStringConverter.OPTIONS));

Related

Populate Tableview with super class fields

I have several classes that all inherit from one super class that need to populate several TableViews related to their class.
The super class is abstract and some of the getters and setters are final but still contains data needed to populate the cells.
Writing a new Callback class for each and every column is doable, but I'm looking for a way to implements this.
sample code
class SuperClass
{
protected String name;
protected double value;
public final void setName(String name)
{
this.name = name;
}
public final void getName()
{
return this.name;
}
public final void setValue(double value)
{
this.value = value;
}
public double getValue()
{
return this.value;
}
}
class SubClass1 extends SuperClass
{
private int id;
public void setId(int id)
{
this.id = id;
}
public int getId()
{
return this.id;
}
}
class SubClass2 extends SuperClass
{
private String location;
public void setLocation(String location)
{
this.location = location;
}
}
class SubClass3 extends SuperClass
{
private ObservableMap<SuperClass> map;
public ObservableMap<SuperClass> map()
{
return this.map;
}
}
TableView
TableColumn<SubClass1, Integer> tc1_id;
TableColumn<SubClass1, String> tc1_name;
TableColumn<SubClass1, Double> tc1_value;
TableColumn<SubClass2, String> tc2_loc;
TableColumn<SubClass2, String> tc2_name;
TableColumn<SubClass2, Double> tc2_value;
TableColumn<SubClass3, String> tc3_name;
TableColumn<SubClass3, Double> tc3_value;
Here's a reference of what I was going to do...
Accessing Subclass properties in a JavaFX TableView ObservableArrayList
But just with the sample code, I'm basically rewriting 2 methods, 3 times each... and there's a bit more than that in the actual program. (Just a smidge more)
I think you are just asking how to reduce the amount of code you have to write. The solution is just the same as any such question: write a method that performs the repetitive part, and parametrize it with the parts that vary. So in this case, you just need to write a generic utility method to generate your table columns, taking the title of the column and the function that produces the property the cell value factory needs.
E.g. you could do something like
private <S,T> TableColumn<S,T> createColumn(String title, Function<S, Property<T>> prop) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return column ;
}
and then if your model classes use JavaFX properties, all you need is
TableColumn<SubClass1, Number> tc1Id = createColumn("Id", SubClass1::idProperty);
etc.
If you are not using JavaFX properties (which is the recommended approach), you can still do
TableColumn<SubClass2, String> tc2Loc =
createColumn("Location", item -> new SimpleStringProperty(item.getLocation()));
or just create a method that accepts a Function<S,T> instead of a Function<S,Property<T>>.

JavaFX TreeTableView - Prevent editing of unavailable cells

I have a particular TreeTableView that displays a hierarchical tree of mixed types. These types do not necessarily have overlapping columns and as such the columns for some rows will be empty. As an example, consider the following classes:
public class Person {
private final StringProperty nameProperty;
private final StringProperty surnameProperty;
public Person() {
this.nameProperty = new SimpleStringProperty();
this.surnameProperty = new SimpleStringProperty();
}
public StringProperty nameProperty() {
return this.nameProperty;
}
public void setName(String value) {
this.nameProperty.set(value);
}
public String getName() {
return this.nameProperty.get();
}
public StringProperty surnameProperty() {
return this.surnameProperty;
}
public void setSurname(String value) {
this.surnameProperty.set(value);
}
public String getSurname() {
return this.surnameProperty.get();
}
}
public class Dog {
private final StringProperty nameProperty;
private final IntegerProperty ageProperty;
private final StringProperty breedProperty;
public Dog() {
this.nameProperty = new SimpleStringProperty();
this.ageProperty = new SimpleIntegerProperty();
this.breedProperty = new SimpleStringProperty();
}
public StringProperty nameProperty() {
return this.nameProperty;
}
public void setName(String value) {
this.nameProperty.set(value);
}
public String getName() {
return this.nameProperty.get();
}
public IntegerProperty ageProperty() {
return this.ageProperty;
}
public void setAge(int value) {
this.ageProperty.setValue(value);
}
public int getAge() {
return this.ageProperty.get();
}
public StringProperty breedProperty() {
return this.breedProperty;
}
public void setBreed(String breed) {
this.breedProperty.set(breed);
}
public String getBreed() {
return this.breedProperty.get();
}
}
If I construct the TreeTableView as follows:
TreeTableView<Object> treeTableView = new TreeTableView<>();
treeTableView.setEditable(true);
List<TreeTableColumn<Object, ?>> columns = treeTableView.getColumns();
TreeTableColumn<Object, String> nameColumn = new TreeTableColumn<>("Name");
nameColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
columns.add(nameColumn);
TreeTableColumn<Object, String> surnameColumn = new TreeTableColumn<>("Surname");
surnameColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
surnameColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("surname"));
columns.add(surnameColumn);
TreeTableColumn<Object, Integer> ageColumn = new TreeTableColumn<>("Age");
ageColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn(new IntegerStringConverter()));
ageColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("age"));
columns.add(ageColumn);
TreeTableColumn<Object, String> breedColumn = new TreeTableColumn<>("Breed");
breedColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
breedColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("breed"));
columns.add(breedColumn);
TreeItem<Object> rootItem = new TreeItem<>();
treeTableView.setRoot(rootItem);
treeTableView.setShowRoot(false);
List<TreeItem<Object>> rootChildren = rootItem.getChildren();
Person john = new Person();
john.setName("John");
john.setSurname("Denver");
TreeItem<Object> johnTreeItem = new TreeItem<>(john);
rootChildren.add(johnTreeItem);
List<TreeItem<Object>> johnChildren = johnTreeItem.getChildren();
Dog charlie = new Dog();
charlie.setName("Charlie");
charlie.setAge(4);
charlie.setBreed("Labrador");
TreeItem<Object> charlieTreeItem = new TreeItem<>(charlie);
johnChildren.add(charlieTreeItem);
Dog daisy = new Dog();
daisy.setName("Daisy");
daisy.setAge(7);
daisy.setBreed("Bulldog");
TreeItem<Object> daisyTreeItem = new TreeItem<>(daisy);
johnChildren.add(daisyTreeItem);
I will get a TreeTableView that looks like:
The Age and Breed columns are empty for the TreeItems that contains Person objects. However, nothing stops me from editing Age or Breed cell for the top-most Person row. Setting a value in one of those cells doesn't change the Person object, but the value still hangs around there like it is committed.
Is there any way to prevent this from happening? I know that I could check for nulls in a custom TreeTableCell subclass and prevent the editing from kicking off in the startEdit() method. However, there are circumstances where a null-value is valid and preventing editing by checking nulls is not a feasible solution for all situations. Also, creating a custom TreeTableCell subclass for every datatype and corresponding columns is painful. It would have been nice if TreeItemPropertyValueFactory could provide for a way to abort the edit when no value is present for a particular cell.
Ok, I scraped together something by looking at the TreeItemPropertyValueFactory class itself for inspiration. This gives me the desired functionality, although I'm not sure if it is 100% correct or what the implications are of using it.
It basically comes down to installing a new cell-factory that checks if the cell-value-factory is of type TreeItemPropertyValueFactory. If it is the case, a new cell-factory is installed that delegates to the original but adds listeners for the table-row and tree-item properties. When the TreeItem changes, we get the row-data and see if we can access the desired property (via a PropertyReference that is cached for performance). If we can't (and we get the two exceptions) we assume that the property cannot be accessed and we set the cell's editable-property to false.
public <S, T> void disableUnavailableCells(TreeTableColumn<S, T> treeTableColumn) {
Callback<TreeTableColumn<S, T>, TreeTableCell<S, T>> cellFactory = treeTableColumn.getCellFactory();
Callback<CellDataFeatures<S, T>, ObservableValue<T>> cellValueFactory = treeTableColumn.getCellValueFactory();
if (cellValueFactory instanceof TreeItemPropertyValueFactory) {
TreeItemPropertyValueFactory<S, T> valueFactory = (TreeItemPropertyValueFactory<S, T>)cellValueFactory;
String property = valueFactory.getProperty();
Map<Class<?>, PropertyReference<T>> propertyRefCache = new HashMap<>();
treeTableColumn.setCellFactory(column -> {
TreeTableCell<S, T> cell = cellFactory.call(column);
cell.tableRowProperty().addListener((o1, oldRow, newRow) -> {
if (newRow != null) {
newRow.treeItemProperty().addListener((o2, oldTreeItem, newTreeItem) -> {
if (newTreeItem != null) {
S rowData = newTreeItem.getValue();
if (rowData != null) {
Class<?> rowType = rowData.getClass();
PropertyReference<T> reference = propertyRefCache.get(rowType);
if (reference == null) {
reference = new PropertyReference<>(rowType, property);
propertyRefCache.put(rowType, reference);
}
try {
reference.getProperty(rowData);
} catch (IllegalStateException e1) {
try {
reference.get(rowData);
} catch (IllegalStateException e2) {
cell.setEditable(false);
}
}
}
}
});
}
});
return cell;
});
}
}
For the example listed in the question, you can call it after you created all your columns as:
...
columns.forEach(this::disableUnavailableCells);
TreeItem<Object> rootItem = new TreeItem<>();
treeTableView.setRoot(rootItem);
treeTableView.setShowRoot(false);
...
You'll see that cells for the Age and Breed columns are now uneditable for Person entries whereas cells for the Surname column is now uneditable for Dog entries, which is what we want. Cells for the common Name column is editable for all entries as this is a common property among Person and Dog objects.

How to use Firebase Query for data with anonymous keys

I have the following database data which I intend to display on a ListView with FirebaseListAdapter
My problem is creating a Query since the child elements after date are anonymous. Here is the query code
Query query = FirebaseDatabase.getInstance().getReference().child("Updates").child(refMail).child(day)
.orderByKey();
refMail and day are user email address and date respectively.
Here is also my Data Model Class
public class NotesDataModel {
private String Note;
private String uid;
private String time;
public NotesDataModel(){
}
public NotesDataModel(String Note, String uid, String time){
this.Note=Note;
this.uid=uid;
this.time=time;
}
public String getNote() {
return Note;
}
public void setNote(String note) {
Note = note;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}}
and finally the adapter initialization
FirebaseListOptions<NotesDataModel> options = new FirebaseListOptions.Builder<NotesDataModel>()
.setQuery(query, NotesDataModel.class)
.setLayout(R.layout.notes_cell_layout)
.build();
mAdapter = new FirebaseListAdapter<NotesDataModel>(options) {
#Override
protected void populateView(View view, NotesDataModel note, int position) { //some code }}; notesList.setAdapter(mAdapter);
Previous version worked like this
ref = FirebaseDatabase.getInstance().getReferenceFromUrl(FactoryDaftari.firebaseURL + "Updates/" + refMail + "/" + day);
And the Adapter initialization
mAdapter = new FirebaseListAdapter<NotesDataModel>(this, NotesDataModel.class, R.layout.notes_cell_layout, ref) {
#Override
protected void populateView(View view, NotesDataModel note, int position) { }};
You won't be able to make this query with the way your data is structured. It's common in NoSQL databases to make copies of data, structured for the purpose of specialized querying. So, if you want to query a list of notes, you'll need a structure where all the notes are children of the same parent, then make your query against that structure.
(Also, organizing your notes by a node with a date, like you have now, may not even be the best general structure in the first place.)

How to make TableCell editable , so it automatically updates the data class?

I am making a system for a school project , and one part of it is a TableView that is populated with rows using my own data class InventoryData that has properties correspondent to the table columns. I would like to make cells in some columns editable using a TextField, so that when an edit is committed, it will update the InventoryData object's relevant property.
I tried setting TextFieldTableCell.forTableColumn() as the cell factory of the columns. Although, now after committing the edit, the text in the cell will change, I don't think it is changing the property in the InventoryData object. The reason why I think that, is because when I try to edit that cell again ( after already being edited once), the TextField shows the former value ( before the first edit).
Did I do something wrong , or is that normal behavior and I have to implement the commits myself?
Here's the code for InventoryData :
package UILayer.TableData;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import ModelLayer.Product;
public class InventoryData {
// From Product
private Product productObj;
private SimpleIntegerProperty id;
private SimpleStringProperty name;
// Constructor - converts Product obj into InventoryData
public InventoryData(Product product)
{
this.productObj = product;
this.id = new SimpleIntegerProperty(product.getId());
this.name = new SimpleStringProperty(product.getName())
}
// GET & SET
public Product getProduct()
{
return productObj;
}
public int getId() {
return id.get();
}
public void setId(int id) {
this.id.set(id);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
productObj.setName(name);
System.out.println(productObj.getName());
}
}
You need your InventoryData class to use the JavaFX Properties pattern. Specifically it needs property-type accessor methods in order to retrieve the property in the table cells. Without this, the cell value factory just calls the standard getName() or getId() method, and wraps the result in a ReadOnlyStringWrapper (or ReadOnlyIntegerWrapper): the table cell cannot change the values of those wrappers (since they are read only).
public class InventoryData {
// From Product
private Product productObj;
private IntegerProperty id;
private StringProperty name;
// Constructor - converts Product obj into InventoryData
public InventoryData(Product product)
{
this.productObj = product;
this.id = new SimpleIntegerProperty(product.getId());
this.name = new SimpleStringProperty(product.getName())
this.name.addListener((obs, oldName, newName) ->
productObj.setName(newName));
}
// GET & SET
public Product getProduct()
{
return productObj;
}
public IntegerProperty idProperty() {
return id ;
}
public final int getId() {
return idProperty().get();
}
public final void setId(int id) {
idProperty().set(id);
}
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
this.nameProperty().set(name);
// productObj.setName(name);
// System.out.println(productObj.getName());
}
}

How to use SimpleIntegerProperty in JavaFX

I tried to populate a tableView, I followed the tutorial given in docs.oracle, but in my table there are Integer fields, so I do the same thing to add them.
The code in the information class (like Person class):
private SimpleIntegerProperty gel;
public int getGel() {
return gel.get();
}
public void setGel(int pop) {
gel.set(pop);
}
The code in the Main class:
TableColumn gel = new TableColumn("Gel");
gel.setMinWidth(100);
gel.setCellValueFactory(new PropertyValueFactory<Information, Integer>("gel"));
gel.setCellFactory(TextFieldTableCell.forTableColumn());
gel.setOnEditCommit(new EventHandler<CellEditEvent<Information, Integer>>() {
#Override
public void handle(CellEditEvent<Information, Integer> t) {
((Information) t.getTableView().getItems()
.get(t.getTablePosition().getRow()))
.setGel(t.getNewValue());
}
});
but I have errors:
Caused by: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at javafx.util.converter.DefaultStringConverter.toString(DefaultStringConverter.java:34)
at javafx.scene.control.cell.CellUtils.getItemText(CellUtils.java:100)
at javafx.scene.control.cell.CellUtils.updateItem(CellUtils.java:201)
at javafx.scene.control.cell.TextFieldTableCell.updateItem(TextFieldTableCell.java:204)
The problem is in your cell factory.
TableColumn should be typed to TableColumn<Information, Integer>. Then you will see an error here:
gel.setCellFactory(TextFieldTableCell.forTableColumn());
(the same error you have on runtime). The reason is the static callback forTableColumn is only for TableColumnof type String.
For other types you have to provide a custom string converter. This will solve your problems:
gel.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Integer>(){
#Override
public String toString(Integer object) {
return object.toString();
}
#Override
public Integer fromString(String string) {
return Integer.parseInt(string);
}
}));

Resources