I have two types of Objects in a table, for simplicity, I will call them Dog and Cat, both of these objects extend from Animal. I am attempting to have a running count of the objects. For the moment I will focus on the Dog object.
I have a TableColumn that I've created a value factory for, and it looks like this:
column.setCellFactory(callback -> new TableCell<>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(!empty) {
if (getTableView().getItems().get(getIndex()) instanceof Dog) {
int count = setDogCount(getTableView(), getIndex(), 0);
setText(String.valueOf(count));
} else {
setText("");
}
} else {
setText("");
}
}
});
The recursive method I use is here:
private int setDogCount(TableView<Animal> table, int index, int count){
if(index == 0){
if(table.getItems().get(index) instanceof Dog) {
return count + 1;
} else {
return count;
}
}
if(table.getItems().get(index) instanceof Dog){
return setDogCount(table, --index, ++count);
}else {
return setDogCount(table, --index, count);
}
}
This actually works about 95% of the time. The only times it breaks is when some sorts occur. It breaks because cell factories are only called when they are sorted, so if a sort does not occur, then it won't update the count. This leads to some multiple counts of Dog depending on the circumstances.
QUESTION:
So, is there a way to have it update only one column on sort? I would like to try to avoid refreshing the entire table, and I was hoping there is a better way.
Thanks!
Edit:
By "if a sort does not occur" I mean that if that object was not moved from its current index, it does not call the cell factory.
Edit 2:
Here is a picture to see what I am facing.
Before Sorting -> After Sorting:
As you can see, index 0 switched with index 3, thus changing the count column appropriately, but now I have no number 1, and two number 3s. This is because it only updates the rows that were switched.
Edit 3:
Here is the small application to see the changes. When you run it, please click on the Commands Known column once to see what I am up against.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane root = new BorderPane();
TableView<Animal> table = new TableView<>();
TableColumn<Animal, String> count = new TableColumn<>("Count");
TableColumn<Animal, String> name = new TableColumn<>("Name");
TableColumn<Animal, String> sound = new TableColumn<>("Sound");
TableColumn<Animal, String> commandsKnown = new TableColumn<>("Commands Known");
table.getColumns().addAll(count, name, sound, commandsKnown);
root.setCenter(table);
count.setCellFactory(callback -> new TableCell<>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
if (getTableView().getItems().get(getIndex()) instanceof Dog) {
int count = setDogCount(getTableView(), getIndex(), 0);
setText(String.valueOf(count));
} else {
setText("");
}
} else {
setText("");
}
}
});
name.setCellValueFactory(data -> data.getValue().nameProperty());
sound.setCellValueFactory(data -> data.getValue().soundProperty());
commandsKnown.setCellValueFactory(data -> {
if(data.getValue() instanceof Dog){
return ((Dog) data.getValue()).commandsKnownProperty();
}
return new SimpleStringProperty("");
});
ObservableList<Animal> animals = FXCollections.observableArrayList();
animals.add(new Dog("Tweeter", "Woof", "Sit, rollover, shake, drop"));
animals.add(new Dog("Sub Woofer", "Woof", "Sit, rollover, shake"));
animals.add(new Cat("Kitter Cat", "Meow"));
animals.add(new Dog("Bass", "Woof", "Sit, rollover, shake, fetch"));
table.setItems(animals);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private int setDogCount(TableView<Animal> table, int index, int count){
if(index == 0){
if(table.getItems().get(index) instanceof Dog) {
return count + 1;
} else {
return count;
}
}
if(table.getItems().get(index) instanceof Dog){
return setDogCount(table, --index, ++count);
}else {
return setDogCount(table, --index, count);
}
}
public static void main(String[] args) {
launch(args);
}
public class Animal{
StringProperty name = new SimpleStringProperty();
StringProperty sound = new SimpleStringProperty();
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getSound() {
return sound.get();
}
public StringProperty soundProperty() {
return sound;
}
public void setSound(String sound) {
this.sound.set(sound);
}
}
public class Dog extends Animal{
StringProperty commandsKnown = new SimpleStringProperty();
public Dog(String name, String sound, String commandsKnown){
setName(name);
setSound(sound);
setCommandsKnown(commandsKnown);
}
public String getCommandsKnown() {
return commandsKnown.get();
}
public StringProperty commandsKnownProperty() {
return commandsKnown;
}
public void setCommandsKnown(String commandsKnown) {
this.commandsKnown.set(commandsKnown);
}
}
public class Cat extends Animal{
public Cat(String name, String sound){
setName(name);
setSound(sound);
}
}
}
To expand a bit on my comment:
a cell definitely is the wrong place to change the data (even if it's
meta) ... do it in the model ;)
As model I meant not only your data object/list but also all state that is related to the data, even if it's also related to the view, as f.i. a running counter. You must model that relation somehow outside of the view.
Below is an example that models the relation by an additional list for the counters, one property per owner. It's the task of the app to keep it in sync with the owners as shown in the table whenever anything that effects the counters changes (f.i. when the list is sorted or the pet changed or anything).
The code:
public class TableWithExternalCounterSO extends Application {
/**
* Updates the counter data from the given source list, assuming that
* both have the same size (if that's not true, adjust counter size
* as needed)
*/
private void updateDogCounterFrom(ObservableList<ObjectProperty<Integer>> dogCounter,
ObservableList<? extends PetOwner> owners) {
int count = 0;
for (int i = 0; i < owners.size(); i++) {
PetOwner owner = owners.get(i);
if (owner.petProperty().get() == Pet.DOG) {
dogCounter.get(i).set(++count);
} else {
dogCounter.get(i).set(-1);
}
}
}
private Parent createContent() {
// the base data
ObservableList<PetOwner> owners = PetOwner.owners();
// a list for the counters, that must be kept in sync with changes in the table
ObservableList<ObjectProperty<Integer>> dogCounter = FXCollections.observableArrayList();
owners.forEach(owner -> dogCounter.add(new SimpleObjectProperty<Integer>(-1)));
// initial sync
updateDogCounterFrom(dogCounter, owners);
SortedList<PetOwner> sorted = new SortedList<>(owners);
sorted.addListener((ListChangeListener<? super PetOwner>) c -> {
// sync after change
updateDogCounterFrom(dogCounter, c.getList());
});
TableView<PetOwner> table = new TableView<>(sorted);
sorted.comparatorProperty().bind(table.comparatorProperty());
TableColumn<PetOwner, String> name = new TableColumn<>("Name");
name.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<PetOwner, Pet> pet = new TableColumn<>("Pet");
pet.setCellValueFactory(new PropertyValueFactory<>("pet"));
TableColumn<PetOwner, Integer> dogIndex = new TableColumn<>("Running Dog#");
dogIndex.setSortable(false);
dogIndex.setCellValueFactory(cd -> {
// astonishingly, this is called for every cell after sorting,
// that is all cells are newly created
int index = cd.getTableView().getItems().indexOf(cd.getValue());
return dogCounter.get(index);
});
dogIndex.setCellFactory(cb -> {
return new TableCell<PetOwner, Integer>() {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty || item != null && item.intValue() < 0) {
setText("");
} else {
setText(String.valueOf(item));
}
}
};
});
table.getColumns().addAll(name, pet, dogIndex);
BorderPane pane = new BorderPane(table);
return pane;
}
private enum Pet {
CAT, DOG
}
public static class PetOwner {
ObjectProperty<Pet> pet;
StringProperty name;
PetOwner(String name, Pet pet) {
this.pet = new SimpleObjectProperty<>(this, "pet", pet);
this.name = new SimpleStringProperty(this, "name", name);
}
public ObjectProperty<Pet> petProperty() {
return pet;
}
public StringProperty nameProperty() {
return name;
}
public static ObservableList<PetOwner> owners() {
ObservableList<PetOwner> owners = FXCollections.observableArrayList();
for (int i = 0; i < 20; i++) {
owners.add(new PetOwner("O " + i, i % 3 == 0 ? Pet.CAT : Pet.DOG) );
}
return owners;
}
#Override
public String toString() {
return name.get( ) + " " + pet.get();
}
}
I suggest you use a static variable to keep up with the Dog's ID.
Example code below:
Full Code:
Dog Class
/**
*
* #author blj0011
*/
public class Dog extends Animal
{
static int dogCounter = 0;
private String commnads;
private final int dogId;
public Dog(String name, String sound, String commands)
{
super(name, sound);
this.commnads = commands;
dogId = ++dogCounter;
}
public String getCommnads()
{
return commnads;
}
public void setCommnads(String commnads)
{
this.commnads = commnads;
}
public int getDogId()
{
return dogId;
}
}
Cat Class
/**
*
* #author blj0011
*/
public class Cat extends Animal
{
public Cat(String name, String sound)
{
super(name, sound);
}
}
Main
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
BorderPane root = new BorderPane();
TableView<Animal> table = new TableView<>();
TableColumn<Animal, String> count = new TableColumn<>("Count");
TableColumn<Animal, String> name = new TableColumn<>("Name");
TableColumn<Animal, String> sound = new TableColumn<>("Sound");
TableColumn<Animal, String> commandsKnown = new TableColumn<>("Commands Known");
table.getColumns().addAll(count, name, sound, commandsKnown);
root.setCenter(table);
count.setCellValueFactory(new PropertyValueFactory("dogId"));
name.setCellValueFactory(new PropertyValueFactory("name"));
sound.setCellValueFactory(new PropertyValueFactory("sound"));
commandsKnown.setCellValueFactory(new PropertyValueFactory("commands"));
ObservableList<Animal> animals = FXCollections.observableArrayList();
animals.add(new Dog("Tweeter", "Woof", "Sit, rollover, shake, drop"));
animals.add(new Dog("Sub Woofer", "Woof", "Sit, rollover, shake"));
animals.add(new Cat("Kitter Cat", "Meow"));
animals.add(new Dog("Bass", "Woof", "Sit, rollover, shake, fetch"));
table.setItems(animals);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private int setDogCount(TableView<Animal> table, int index, int count)
{
if (index == 0) {
if (table.getItems().get(index) instanceof Dog) {
return count + 1;
}
else {
return count;
}
}
if (table.getItems().get(index) instanceof Dog) {
return setDogCount(table, --index, ++count);
}
else {
return setDogCount(table, --index, count);
}
}
public static void main(String[] args)
{
launch(args);
}
}
SortedList code:
import java.util.Comparator;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
BorderPane root = new BorderPane();
TableView<Animal> table = new TableView<>();
TableColumn<Animal, String> count = new TableColumn<>("Count");
TableColumn<Animal, String> name = new TableColumn<>("Name");
TableColumn<Animal, String> sound = new TableColumn<>("Sound");
TableColumn<Animal, String> commandsKnown = new TableColumn<>("Commands Known");
table.getColumns().addAll(count, name, sound, commandsKnown);
root.setCenter(table);
count.setCellValueFactory(new PropertyValueFactory("dogId"));
name.setCellValueFactory(new PropertyValueFactory("name"));
sound.setCellValueFactory(new PropertyValueFactory("sound"));
commandsKnown.setCellValueFactory(new PropertyValueFactory("commands"));
ObservableList<Animal> animals = FXCollections.observableArrayList();
animals.add(new Dog("Tweeter", "Woof", "Sit, rollover, shake, drop"));
animals.add(new Dog("Sub Woofer", "Woof", "Sit, rollover, shake"));
animals.add(new Cat("Kitter Cat", "Meow"));
animals.add(new Cat("Kitter Cat 2", "Meow"));
animals.add(new Dog("Bass", "Woof", "Sit, rollover, shake, fetch"));
SortedList<Animal> sortedList = new SortedList(animals);
Comparator<Animal> comparator = (c, d) -> {
if (c instanceof Cat && d instanceof Dog) {
return -1;
}
else if (c instanceof Dog && d instanceof Cat) {
return 1;
}
return 0;
};
comparator.thenComparing((c, d) -> {
if (c instanceof Cat && d instanceof Dog) {
return -1;
}
else if (c instanceof Dog && d instanceof Cat) {
return 1;
}
else {
if (c instanceof Cat && d instanceof Cat) {
return c.getName().compareTo(d.getName());
}
else {
Dog tempDog1 = (Dog) c;
Dog tempDog2 = (Dog) d;
if (tempDog1.getDogId() > tempDog2.getDogId()) {
return 1;
}
}
return 0;
}
});
sortedList.setComparator(comparator);
table.setItems(sortedList);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Except for the first item, all items of a comboBox are initially disabled (I used setCellFactory to accomplish this).
If I click on the option 0, I want for it to unlock option 1 and so on.
I tried to use some boolean variables inside a comboBox Listener but it seems like the setCellFactory is called only once. Is this correct?
If so, how could I achieve what I want?
SSCCE below adapted from here
Main.java
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
public class Main extends Application {
boolean isZeroLocked = false;
boolean isOneLocked = true;
boolean isTwoLocked = true;
boolean isThreeLocked = true;
#Override
public void start(Stage stage) throws Exception {
ComboBox<Integer> box = new ComboBox<Integer>();
ObservableList<Integer> values = FXCollections.observableArrayList(0,1,2,3);
box.setItems(values);
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
System.out.println(newValue + " was clicked. The next option will be unlocked.");
if(newValue.intValue() == 0)
isOneLocked = false;
if(newValue.intValue() == 1)
isTwoLocked = false;
if(newValue.intValue() == 2)
isThreeLocked = false;
});
box.setCellFactory(lv -> new ListCell<Integer>() {
#Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
setText(item.toString());
if(item.intValue() == 0)
setDisable(isZeroLocked);
if(item.intValue() == 1)
setDisable(isOneLocked);
if(item.intValue() == 2)
setDisable(isTwoLocked);
if(item.intValue() == 3)
setDisable(isThreeLocked);
}
}
});
Scene scene = new Scene(new Group(box));
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
application.css
.combo-box-popup .list-cell:disabled {
-fx-opacity: 0.4 ;
}
I created an Object called CustomNumber to keep up with the disabled property.
Key code:
This code sets the ComboBox's text and enables its cell.
Callback<ListView<CustomNumber>, ListCell<CustomNumber>> factory = lv -> new ListCell<CustomNumber>() {
#Override
protected void updateItem(CustomNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
System.out.println(item.getNum());
setText(Integer.toString(item.getNum()));
setDisable(item.isDisable());
}
}
};
This code gets the cell below the clicked cell and updates its disable property
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
if(newValue.intValue() + 1 < box.getItems().size())
{
CustomNumber tempCustomNumber = (CustomNumber)box.getItems().get(newValue.intValue() + 1);
tempCustomNumber.setDisable(false);
System.out.println(tempCustomNumber.getNum() + " " + tempCustomNumber.isDisable() + " was unlocked.");
box.getItems().set(newValue.intValue() + 1, tempCustomNumber);
}
});
Full Code:
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<CustomNumber> box = new ComboBox();
List<CustomNumber> customNumbers = new ArrayList();
customNumbers.add(new CustomNumber(0, false));
customNumbers.add(new CustomNumber(1, true));
customNumbers.add(new CustomNumber(2, true));
customNumbers.add(new CustomNumber(3, true));
ObservableList<CustomNumber> values = FXCollections.observableArrayList(customNumbers);
box.setItems(values);
Callback<ListView<CustomNumber>, ListCell<CustomNumber>> factory = lv -> new ListCell<CustomNumber>() {
#Override
protected void updateItem(CustomNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
System.out.println(item.getNum());
setText(Integer.toString(item.getNum()));
setDisable(item.isDisable());
}
}
};
box.setCellFactory(factory);
box.setButtonCell(factory.call(null));
box.getSelectionModel().selectedIndexProperty().addListener((observable,oldValue,newValue)->{
if(newValue.intValue() + 1 < box.getItems().size())
{
CustomNumber tempCustomNumber = (CustomNumber)box.getItems().get(newValue.intValue() + 1);
tempCustomNumber.setDisable(false);
System.out.println(tempCustomNumber.getNum() + " " + tempCustomNumber.isDisable() + " was unlocked.");
box.getItems().set(newValue.intValue() + 1, tempCustomNumber);
}
});
Scene scene = new Scene(new Group(box));
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
CustomNumber
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxapplication9;
/**
*
* #author sedrick
*/
public class CustomNumber {
private int num;
private boolean disable;
public CustomNumber(int num, boolean disable) {
this.num = num;
this.disable = disable;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public boolean isDisable() {
return disable;
}
public void setDisable(boolean isDisable) {
this.disable = isDisable;
}
}
I already know how I have to manipulate the table cell using a table cell factory callback. I added a currency symbol to the cell to make it look neat. (i.e. € 5,00 instead of 5,00) The thing is, when i double click on the cell i want that symbol to be removed. But for the heck of it, I'm unable to find how i am able to manipulate the textfield again to remove that currency symbol and bring it back in when the user committed the edit. Basically what I try to do is something similar when editing a cell in Excel :).
Any chance someone can help me out with a little basic example? Do I need to use the OnEditStart event?
Any time you want to configure how an item in a cell is displayed, without changing the actual data, you should use a custom TableCell. Here is an example that exhibits the behavior you want:
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.function.UnaryOperator;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class CurrencyCell<T> extends TableCell<T, Double> {
private final TextField textField ;
private final NumberFormat format = DecimalFormat.getCurrencyInstance();
private final DecimalFormat textFieldFormat = new DecimalFormat("0.00");
public CurrencyCell() {
this.textField = new TextField();
StringConverter<Double> converter = new StringConverter<Double>() {
#Override
public String toString(Double object) {
return object == null ? "" : textFieldFormat.format(object) ;
}
#Override
public Double fromString(String string) {
try {
return string.isEmpty() ? 0.0 : textFieldFormat.parse(string).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
return 0.0 ;
}
}
};
UnaryOperator<Change> filter = (Change change) -> {
String newText = change.getControlNewText() ;
if (newText.isEmpty()) {
return change ;
}
try {
textFieldFormat.parse(newText);
return change ;
} catch (ParseException exc) {
return null ;
}
};
TextFormatter<Double> textFormatter = new TextFormatter<Double>(converter, 0.0, filter);
textField.setTextFormatter(textFormatter);
textField.setOnAction(e -> commitEdit(converter.fromString(textField.getText())));
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else if (isEditing()) {
textField.setText(item.toString());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(format.format(item));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(textFieldFormat.format(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(format.format(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(Double newValue) {
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
And here's an example using it:
import java.util.Locale ;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class CurrencyCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(column("Item", Item::nameProperty));
TableColumn<Item, Double> priceCol = column("Price", item -> item.priceProperty().asObject());
table.getColumns().add(priceCol);
priceCol.setCellFactory(tc -> new CurrencyCell<>());
Random rng = new Random();
for (int i = 1; i <= 100; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(10000)/100.0));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final DoubleProperty price = new SimpleDoubleProperty();
public Item(String name, double price) {
setName(name);
setPrice(price);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final DoubleProperty priceProperty() {
return this.price;
}
public final double getPrice() {
return this.priceProperty().get();
}
public final void setPrice(final double price) {
this.priceProperty().set(price);
}
}
public static void main(String[] args) {
// for testing:
Locale.setDefault(new Locale("NL", "nl"));
launch(args);
}
}
I'm getting Syntax error on token “>>”, Expression expected after this token when calling setCellFactory below but do not understand why.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableVieww extends Application {
private static TableView<TableData> table;
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unchecked")
public void start(Stage primaryStage) throws Exception {
Stage s = primaryStage;
s.setTitle("The Title");
TableColumn<TableData, String> column1 = new TableColumn<>("String");
column1.setMinWidth(150);
column1.setCellValueFactory(new PropertyValueFactory<TableData, String>("s"));
column1.setEditable(true);
column1.setCellFactory(TextFieldTableCell.forTableColumn());
column1.setOnEditCommit(e -> {
});
TableColumn<TableData, Double> column2 = new TableColumn<>("Double");
column2.setMinWidth(50);
column2.setCellValueFactory(new PropertyValueFactory<TableData, Double>("d"));
column2.setEditable(true);
// Error within this line (The line below)
column2.setCellFactory(TextFieldTableCell.forTableColumn());
column2.setOnEditCommit(e -> {
});
// set up a table
table = new TableView<>();
table.setItems(getData());
table.getColumns().addAll(column1, column2);
table.setEditable(true);
// setting up a layout
VBox layout = new VBox(10);
layout.getChildren().add(table);
// Setting up a scene
Scene scene = new Scene(layout, 500, 200);
s.setScene(scene);
s.show();
}
private static ObservableList<TableData> getData(){
ObservableList<TableData> list = FXCollections.observableArrayList();
TableVieww view = new TableVieww();
list.add(view.new TableData("BlaBlaBla", 10));
list.add(view.new TableData("TraTraTra", 5.1));
return list;
}
public class TableData{
double d;
String s;
public TableData(String s, double d) {
this.d = d;
this.s = s;
}
public double getD() {
return d;
}
public void setD(double d) {
this.d = d;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
}
What is wrong? Thanks for your help in advance!
This is not entirely flawless, but it works. You will probaly need to adjust it too fit your needs a lot, but...it works.
public class SpinnerTableCell<S, T> extends TableCell<S, T> {
private Spinner spinner;
private String text;
public SpinnerTableCell(int min, int max) {
this.spinner = new Spinner(min, max, 0.0);
spinner.focusedProperty().addListener((observable, oldBoolean, newBoolean)->{
if(!newBoolean){
setText(spinner.getValue().toString());
text = getText();
setGraphic(null);
}
});
}
public void startEdit() {
if (this.isEditable() && this.getTableView().isEditable() && this.getTableColumn().isEditable()) {
super.startEdit();
if (this.isEditing()) {
setText("");
setGraphic(spinner);
}
}
}
public void cancelEdit() {
super.cancelEdit();
setText(text);
setGraphic(null);
}
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? "" : item.toString());
spinner.getValueFactory().setValue(empty ? 0.0 : item);
}
}
You can set the factory with tableCell.setCellFactory((e)->new SpinnerTableCell(min, max));
Adding the TableColumn objects to my TableView is a lengthy operation in my application---it causes everything to freeze for 3-4 seconds. I would like to keep the UI responsive while this is happening, but this is exactly the kind of thing that must be done on the JavaFX application thread. Can anything be done?
package tableviewpausetest;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.WorkerStateEvent;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author drmc
*/
public class TableViewPauseTest extends Application {
public static final int ROW_COUNT = 100;
public static final int COLUMN_COUNT = 80;
public static final ExecutorService executor = Executors.newSingleThreadExecutor();
private TableView<String> tableView = new TableView<>();
private Button button = new Button("Toggle Columns Visibility");
private ProgressIndicator progressIndicator = new ProgressIndicator();
private HBox buttonBox = new HBox(8);
private BorderPane borderPane = new BorderPane();
private Task task = null;
public void start(Stage primaryStage) {
this.tableView.setColumnResizePolicy(
TableView.CONSTRAINED_RESIZE_POLICY);
for (int i = 0; i < ROW_COUNT; ++i) {
this.tableView.getItems().add(":)");
}
this.button.setOnAction(new ToggleHandler(this));
this.buttonBox.getChildren().setAll(this.button);
this.borderPane.setCenter(this.tableView);
this.borderPane.setBottom(this.buttonBox);
Scene scene = new Scene(this.borderPane, 1024, 768);
primaryStage.setTitle("tableviewpausetest");
primaryStage.setScene(scene);
primaryStage.show();
}
private class ToggleHandler implements EventHandler<ActionEvent> {
private TableViewPauseTest app;
public ToggleHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(ActionEvent event) {
// Show the progress indicator.
this.app.buttonBox.getChildren().add(this.app.progressIndicator);
this.app.progressIndicator.setPrefHeight(this.app.button.getHeight());
// Ensure the columns exist.
if (this.app.tableView.getColumns().isEmpty()) {
for (int i = 0; i < COLUMN_COUNT; ++i) {
TableColumn<String, String> tableColumn = new TableColumn<>(
String.format("%s", i));
tableColumn.setVisible(false);
this.app.tableView.getColumns().add(tableColumn);
}
}
// Create and submit a concurrent task to toggle column visibility.
this.app.task = new ToggleTask(this.app);
this.app.task.setOnSucceeded(new ToggleSucceededHandler(this.app));
executor.submit(this.app.task);
}
}
private class ToggleSucceededHandler implements EventHandler<WorkerStateEvent> {
private TableViewPauseTest app;
public ToggleSucceededHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(WorkerStateEvent event) {
// Hide the progress indicator.
this.app.buttonBox.getChildren().remove(this.app.progressIndicator);
}
}
private class ToggleTask extends Task<String> {
private TableViewPauseTest app;
public ToggleTask(TableViewPauseTest app) {
this.app = app;
}
#Override
public String call() {
boolean newState = false;
String action = "hide";
if (this.app.tableView.getVisibleLeafColumns().isEmpty()) {
newState = true;
action = "show";
}
// This action must be performed on the JavaFX Application Thread,
// and it causes an extremely uncomfortable pause in my application.
Platform.runLater(new ToggleRunnable(this.app.tableView, newState));
return action;
}
}
private class ToggleRunnable implements Runnable {
private TableView<?> tableView;
private boolean newState;
public ToggleRunnable(TableView<?> tableView, boolean newState) {
this.tableView = tableView;
this.newState = newState;
}
#Override
public void run() {
for (TableColumn<?, ?> tableColumn : this.tableView.getColumns()) {
tableColumn.setVisible(this.newState);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
I think this example code useful for you
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package progressbartablecelltest;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author reegan
*/
public class ProgressBarTableCellTest extends Application {
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<>();
Random rng = new Random();
for (int i = 0; i < 20; i++) {
table.getItems().add(
new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
}
TableColumn<TestTask, String> statusCol = new TableColumn("Status");
statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
"message"));
statusCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
"progress"));
progressCol
.setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());
table.getColumns().addAll(statusCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
for (TestTask task : table.getItems()) {
executor.execute(task);
}
}
public static void main(String[] args) { launch(args); }
static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
private final int pauseTime; // milliseconds
public static final int NUM_ITERATIONS = 100;
TestTask(int waitTime, int pauseTime) {
this.waitTime = waitTime;
this.pauseTime = pauseTime;
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
this.updateMessage("Waiting...");
Thread.sleep(waitTime);
this.updateMessage("Running...");
for (int i = 0; i < NUM_ITERATIONS; i++) {
updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
Thread.sleep(pauseTime);
}
this.updateMessage("Done");
this.updateProgress(1, 1);
return null;
}
}
}
class ProgressIndicatorTableCell<S> extends TableCell<S, Double> {
public static <S> Callback<TableColumn<S, Double>, TableCell<S, Double>> forTableColumn() {
return new Callback<TableColumn<S, Double>, TableCell<S, Double>>() {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new ProgressIndicatorTableCell<>();
}
};
}
private final ProgressIndicator progressIndicator;
private ObservableValue observable;
public ProgressIndicatorTableCell() {
this.getStyleClass().add("progress-indicator-table-cell");
this.progressIndicator = new ProgressIndicator();
setGraphic(progressIndicator);
}
#Override public void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
progressIndicator.progressProperty().unbind();
observable = getTableColumn().getCellObservableValue(getIndex());
if (observable != null) {
progressIndicator.progressProperty().bind(observable);
} else {
progressIndicator.setProgress(item);
}
setGraphic(progressIndicator);
}
}
}
Table Column Add with Progress Indicator