I created a TableView a while back and registered Properties to each of the TableColumns. Editing of the internal data reflected itself back in the TableView just fine.
With a ListView, however, it is a different story. The changes are not being shown right away unless I close the frame and open it again.
My ListView consists of ActionSteps. Note that I used the Javafx beans properties.
package application.objects;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.function.IntPredicate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class ActionStep {
private StringProperty actionStepID;
private ObjectProperty<LocalDate> dateSet, dateFinished;
private StringProperty stepName;
private IntegerProperty completion;
private ArrayList<StepComment> comments;
public ActionStep(String name) {
actionStepID = new SimpleStringProperty();
stepName = new SimpleStringProperty();
dateSet = new SimpleObjectProperty<LocalDate>();
dateFinished = new SimpleObjectProperty<LocalDate>();
completion = new SimpleIntegerProperty();
stepName.setValue(name);
}
public void setName(String name) {
stepName.setValue(name);
}
public String getName() {
return stepName.getValue();
}
public StringProperty stepNameProperty() {
return actionStepID;
}
public void setID(String id) {
actionStepID.setValue(id);
}
public String getID() {
return actionStepID.get();
}
public StringProperty actionStepIDProperty() {
return actionStepID;
}
public void setCompletion(int percent) {
if (percent < 0 || percent > 100)
return;
completion.set(percent);
}
public int getCompletion() {
return completion.get();
}
public IntegerProperty completionProperty() {
return completion;
}
public void setDateSet(LocalDate date) {
dateSet.set(date);
}
public LocalDate getDateSet() {
return dateSet.get();
}
public ObjectProperty<LocalDate> dateSetProperty() {
return dateSet;
}
public void setDateFinished(LocalDate date) {
dateFinished.set(date);
}
public LocalDate getDateFinished() {
return dateFinished.get();
}
public ObjectProperty<LocalDate> dateFinishedProperty() {
return dateFinished;
}
public String toString() {
return stepNameProperty().get();
}
}
My ListView uses an ObservableList as well.
#FXML
private ListView<ActionStep> actionStepsListView;
private ObservableList<ActionStep> listOfSteps;
listOfSteps = FXCollections.observableArrayList();
actionStepsListView.setItems(listOfSteps);
if (plan != null) {
ArrayList<ActionStep> arrayOfSteps = plan.getStepsArrayList();
for (int i = 0; i < arrayOfSteps.size(); i++)
listOfSteps.add(arrayOfSteps.get(i));
} else
plan = new ActionPlan();
How come changes made to the ObservableList do not reflect themselves in the ListView? I noticed that the ListView called upon every object's toString() to display their values in the ListView, rather than binding it to their Properties.
What am I doing wrong? Am I supposed to override a cell factory or something?
Note that you're trying to do something more complex with the cells in your ListView than you were with the cells in the TableView. In the TableView, the objects displayed in the cells were changing, so it was easy for the cells to observe this. In the ListView, you want the cells to notice when properties that belong to the objects displayed in the cells change; this is one further step removed, so you have to do a bit of extra coding (though not much, as you'll see).
You could create a custom cell factory to bind to the stepNameProperty(), but it's tricky (you have to make sure to unbind/remove listeners from old items in the updateItem() method).
The easier way, though, which isn't well documented is to use an ObservableList with an extractor defined.
First, fix your method names: you have some weird mismatches in the code you posted. The getX/setX/xProperty method names should all match correctly. I.e. instead of
public void setName(String name) {
stepName.setValue(name);
}
public String getName() {
return stepName.getValue();
}
public StringProperty stepNameProperty() {
return actionStepID;
}
you should have
public final void setName(String name) {
stepName.setValue(name);
}
public final String getName() {
return stepName.getValue();
}
public StringProperty nameProperty() {
return stepName;
}
and similarly for the other property accessor methods. (Obviously, the names of the fields can be anything you like, as they're private.) Making the get/set methods final is good practice.
Then, create the list with an extractor. The extractor is a function that maps each element in the list to an array of Observables which the list will observe. If those values change, it will fire list updates to the list's observers. Since your ActionStep's toString() method references the nameProperty(), I assume you want the ListView to update if the nameProperty() changes. So you want to do
listOfSteps = FXCollections.observableArrayList(
actionStep -> new Observable[] { actionStep.nameProperty() } // the "extractor"
);
actionStepsListView.setItems(listOfSteps);
Note that in earlier versions of JavaFX 2.2 the ListView did not properly observe the list for update events; this was fixed (if I remember correctly) shortly prior to the release of Java 8. (Since you tagged the question JavaFX8, I assume you're using Java 8 and so you should be fine here.)
If you are not using Java 8, you can use the following (equivalent but more verbose) code:
listOfSteps = FXCollections.observableArrayList(
new Callback<ActionStep, Observable[]>() {
#Override
public Observable[] call(ActionStep actionStep) {
return new Observable[] { actionStep.nameProperty() } ;
}
});
actionStepListView.setItems(listOfSteps);
Here is sample how make listview with custom objects:
public class JavaFX_ListView extends Application {
class MyObject {
String day;
int number;
MyObject(String d, int n) {
day = d;
number = n;
}
String getDay() {
return day;
}
int getNumber() {
return number;
}
#Override
public String toString() {
return number + " " + day;
}
}
ObservableList<MyObject> myList;
// Create dummy list of MyObject
private void prepareMyList() {
myList = FXCollections.observableArrayList();
myList.add(new MyObject("Sunday", 50));
myList.add(new MyObject("Monday", 60));
myList.add(new MyObject("Tuesday", 20));
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("sample");
prepareMyList();
ListView<MyObject> listView = new ListView<>();
listView.setItems(myList);
Pane root = new Pane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
// testing
Timer timer = new Timer();
timer.schedule(new UpdateListTask(), 1000, 1000);
}
public static void main(String[] args) {
launch(args);
}
// testing
public class UpdateListTask extends TimerTask {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
myList.add(new MyObject("sample", Calendar.getInstance()
.getTime().getSeconds()));
}
});
}
}
}
Related
I have a following situation:
Large file with a lot of lines (~100k, logs from server). Each line in this file should be parsed, filtered and disaplayed on UI.
To read data from file I use BufferedReader, wich read lines, parse it and prepare for disaplying. It runs on different thread (THREAD-1) and populates BlockingQueue. In another thread (THREAD-2) runned UIUpdater - it purpose to get line batch from queue and run something line this:
Platform.runLater(() -> logArea.append(batchedLine));
Obviously, FX Thread floods and UI is freezes.
So, question is: where I can get information about patterns/best practices to resolove this issue?
It really depends on the control that you want to populate.
Adding lots of nodes to the scene-graph is expensive therefore it will be slow (for example putting Text objects to any container).
I would suggest the usage of a control that was originally designed to display a huge amount of data, like ListView.
In the example, even during the update of the ListView the Button is responsive.
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox();
Scene scene = new Scene(root, 700, 400, Color.WHITE);
TableView<Person> personsTable = new TableView<Person>();
TableColumn<Person, String> nameCol = new TableColumn<Person, String>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
personsTable.getColumns().add(nameCol);
ObservableList<Person> persons = FXCollections.observableArrayList();
Thread th = new Thread(new Runnable() {
#Override
public void run() {
for (int i = 0; i < 100000; i++) {
Person person = new Person();
person.setName("Name" + i);
person.setAddress("Address" + i);
person.setCountry("Country" + i);
person.setCourse("Course" + i);
persons.add(person);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}) ;
th.start();
personsTable.setItems(persons);
Button b = new Button();
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("I am printing independently of Person update!");
}
});
root.getChildren().addAll(personsTable, b);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Person.java
public class Person {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
private String name;
private String address;
private String country;
private String course;
}
User jewelsea has made a really good example on lgging. With little tailoring it could solve your issue.
I'm creating simple JavaFX application. I want my model layer to be completely independent from JavaFX - no StringProperty, IntegerProperty and etc. as fields. I want it to be POJO. Main reason to do so is that I want it to be Serializable.
I've created DataRepository - simple CRUD-like interface and some implementations of it, so I can at anytime change where I store my data - XML file, SQLite database or anything else. I also have to somehow connect my data storage with JavaFX (to display its content in TableView), so I decided to create my implementation of ObservableList which wraps my repository. My question is - is there any other way? ObservableList contains about 30 methods to implement and it looks like I'm doing something wrong.
My (simplified) model:
public class Movie implements Serializable {
private String title;
private String director;
public Movie() {
}
public Movie(String title, String director) {
this.title = title;
this.director = director;
}
// Getters and setters, equals etc...
}
MovieRepository:
public interface MovieRepository {
public void add(Movie movie);
public void remove(String title);
public void remove(int index);
public Movie get(String title);
public Movie get(int index);
public List<Movie> getAll();
}
Controller for my main view:
public class MainController {
#FXML
private TableView<Movie> movieTable;
#FXML
private TableColumn<Movie, String> movieTitleColumn;
#FXML
private Label titleLabel;
private MovieRepository movies = new DBMovieRepository(); //MovieRepository implementation which uses SQLite DB to store data
private MainApp app;
#FXML
private void initialize() {
movieTable.setItems(new ObservableMovies(movies));
// ObservableMovies is my implementation of ObservableList
// It basically wraps methods from MovieRepository
// and notifies listeners
showMovieDetails(null);
movieTitleColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().getTitle()));
movieTable.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> showMovieDetails(newValue));
}
private void showMovieDetails(Movie movie) {
if(movie != null) {
titleLabel.setText(movie.getTitle());
} else {
titleLabel.setText("");
}
}
#FXML
private void handleNew() {
Movie movie = new Movie();
app.showNewMovieDialog(movie);
movieTable.getItems().add(movie);
}
public void setApp(MainApp app) {
this.app = app;
}
}
You have a couple of options here (maybe more), which are covered in other questions on this site. However, for convenience, I'll summarize them here too.
1. Use JavaFX Properties and make the class Serializable
You can do this with a custom serialized form. Make the JavaFX properties transient and implement readObject and writeObject to store the values they wrap:
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Movie implements Serializable {
private transient StringProperty title = new SimpleStringProperty();
private transient StringProperty director = new SimpleStringProperty();
public Movie() {
}
public Movie(String title, String director) {
setTitle(title);
setDirector(director);
}
#Override
public int hashCode() {
return Objects.hash(getDirector(), getTitle());
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Movie other = (Movie) obj;
return Objects.equals(getTitle(), other.getTitle())
&& Objects.equals(getDirector(), other.getDirector());
}
public final StringProperty titleProperty() {
return this.title;
}
public final String getTitle() {
return this.titleProperty().get();
}
public final void setTitle(final String title) {
this.titleProperty().set(title);
}
public final StringProperty directorProperty() {
return this.director;
}
public final String getDirector() {
return this.directorProperty().get();
}
public final void setDirector(final String director) {
this.directorProperty().set(director);
}
private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
s.defaultReadObject();
title = new SimpleStringProperty((String) s.readObject());
director = new SimpleStringProperty((String) s.readObject());
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject(getTitle());
s.writeObject(getDirector());
}
}
2. Use a POJO with "bound properties".
See JavaBean wrapping with JavaFX Properties for details. In brief:
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class Movie {
private String title ;
private String director ;
private final PropertyChangeSupport propertySupport ;
public Movie(String title, String director) {
this.title = title ;
this.director = director ;
this.propertySupport = new PropertyChangeSupport(this);
}
public Movie() {
this("", "");
}
public String getTitle() {
return title ;
}
public String setTitle(String title) {
String oldTitle = this.title ;
this.title = title ;
propertySupport.firePropertyChange("title", oldTitle, title);
}
// similarly for director...
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
// hashCode and equals...
}
For wanting to wrap your repository as an observable list, instead wrap it with a repository implementation that uses an observable list:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class ObservableMovieRepository implements MovieRepository {
private final MovieRepository repository ;
private final ObservableList<Movie> movieList;
public ObservableMovieRepository(MovieRepository repository) {
this.repository = repository ;
this.movieList = FXCollections.observableArrayList(repository.getAll());
}
#Override
public void add(Movie movie) {
repository.add(movie);
movieList.add(movie);
}
#Override
public void remove(String title) {
Movie movie = get(title);
repository.remove(title);
movieList.remove(title);
}
#Override
public void remove(int index) {
repository.remove(index);
movieList.remove(index);
}
#Override
public Movie get(String title) {
return repository.get(title);
}
#Override
public Movie get(int index) {
return movieList.get(index);
}
#Override
public ObservableList<Movie> getAll() {
return movieList ;
}
}
This uses the standard ObservableList implementation that copies an existing list on creation, and the implementation keeps that list in sync with the list in the wrapped repository. Now your UI code can do
ObservableMovieRepository movies = new ObservableMovieRepository(new DBMovieRepository());
// ...
movieTable.setItems(movies.getAll());
With the Movie class above, you would just do
movieTitleColumn.setCellValueFactory(cellData -> cellData.getValue().titleProperty());
If you use the POJO version you can do
movieTitleColumn.setCellValueFactory(cellData -> {
try {
return new JavaBeanStringPropertyBuilder()
.bean(cellData.getValue())
.name("title")
.build();
} catch (Exception e) { throw new RuntimeException(e); }
}
There seem to be multiple question in here, so I'm not really sure, if I understood you correctly, but I will try to split it up a bit.
I want my model layer to be completely independent from JavaFX - no
StringProperty, IntegerProperty and etc. as fields. I want it to be
POJO.
You could mark your properties as transient. Then you just need to wrap them around your values and it will be both JavaFX compliant and Serializable. You just have to propagate changes back to your backing attributes.
I also have to somehow connect my data storage with JavaFX (to display
its content in TableView), so I decided to create my implementation of
ObservableList which wraps my repository. My question is - is there
any other way?
Very limited information on this and I really don't know, why you would need to create your own implementation of ObservableList, but to keep it POJO, you could maintain plain java.util.Collections in your bean and provide transient ObservableLists, which you can create on creation by wrapping your java.util.Lists in your POJO. You can find those methods in the FXCollections utility class.
ObservableList contains about 30 methods to implement and it looks
like I'm doing something wrong.
If you really need to implement it, you can inherit from ObservableListBase.
We're integrating JavaFX onto a large legacy code base containing many "original" Java beans, i.e. the type implemented using java.beans.PropertyChangeSupport.
JavaFX does not support update of these style of beans, only initial value, as documented in javafx.scene.control.cell.PropertyValueFactory
If no method matching this pattern exists, there is fall-through
support for attempting to call get() or is() (that
is, getFirstName() or isFirstName() in the example above). If a method
matching this pattern exists, the value returned from this method is
wrapped in a ReadOnlyObjectWrapper and returned to the TableCell.
However, in this situation, this means that the TableCell will not be
able to observe the ObservableValue for changes (as is the case in the
first approach above).
Upgrading the beans to the property API is not an option as they live in a separate code base which we don't wish to add JavaFX dependencies on as it is still used by legacy Java 6 projects.
My question, how can I get a TableView to update when properties are changed without having to add/remove listeners onto all the individual beans in the table.
I was considering creating my own version of PropertyValueFactory which supports this, but I'd like to know if there are any other possible solutions.
I've produced two examples to illustrate this.
TableView using old-school beans
public class OldBeanTableView extends Application {
public class OldBean {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public static final String PROPERTY_NAME_FOO = "foo";
private int foo = 99;
public int getFoo() {
return foo;
}
public void setFoo(int foo) {
int oldValue = this.foo;
this.foo = foo;
pcs.firePropertyChange(PROPERTY_NAME_FOO, oldValue, foo);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<OldBean> beans = FXCollections.observableArrayList();
beans.add(new OldBean());
TableView<OldBean> tableView = new TableView<>();
TableColumn<OldBean, Integer> column = new TableColumn<OldBeanTableView.OldBean, Integer>();
tableView.getColumns().add(column);
column.setCellValueFactory(new PropertyValueFactory<>("foo"));
tableView.setItems(beans);
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> beans.get(0).setFoo(beans.get(0).getFoo() + 1), 0,
1, TimeUnit.SECONDS);
}
}
TableView using new beans
public class NewBeanTableView extends Application {
public class NewBean {
private IntegerProperty fooProperty = new SimpleIntegerProperty(0);
public int getFoo() {
return fooProperty.get();
}
public void setFoo(int foo) {
fooProperty.set(foo);
}
public IntegerProperty fooProperty() {
return fooProperty;
}
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<NewBean> beans = FXCollections.observableArrayList();
beans.add(new NewBean());
TableView<NewBean> tableView = new TableView<>();
TableColumn<NewBean, Integer> column = new TableColumn<NewBeanTableView.NewBean, Integer>();
tableView.getColumns().add(column);
column.setCellValueFactory(new PropertyValueFactory<>("foo"));
tableView.setItems(beans);
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> beans.get(0).setFoo(beans.get(0).getFoo() + 1), 0,
1, TimeUnit.SECONDS);
}
}
A very quick example for using JavaBeanProperty as valueFactory:
Callback<CellDataFeatures<OldBean, Integer>, ObservableValue<Integer>> valueFactory = cdf -> {
OldBean bean = cdf.getValue();
JavaBeanObjectProperty<Integer> wrappee;
try {
wrappee = JavaBeanObjectPropertyBuilder.create()
.name("foo").bean(bean).build();
return wrappee;
} catch (Exception e) {
e.printStackTrace();
}
return null;
};
column.setCellValueFactory(valueFactory);
Note that the bean must have methods add/removePropertyChangeListeners (which your real beans will have anyway :-) to work.
Extrapolating kleopatra's answer to the generic solution.
public class LegacyValueFactory<T, F> implements Callback<CellDataFeatures<T, F>, ObservableValue<F>> {
private String propertyName;
public LegacyValueFactory(String propertyName) {
this.propertyName = propertyName;
}
#Override
public ObservableValue<F> call(CellDataFeatures<T, F> param) {
try {
return JavaBeanObjectPropertyBuilder.create().name(propertyName).bean(param.getValue()).build();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
}
Usage
column.setCellValueFactory(new LegacyValueFactory<OldBean, Integer>("foo"));
I use java 8.0.45. I have implemented my first javafx application (very simple) with data binding. However, biding from user input-> pojo seems to work with bugs. I've checked about 200 times. I entered new values in text fields and after that I checked model values. The same code, the same my behaviour. Sometimes everything works fine (in most cases - about 80-90%) sometimes model value!=textfield value. I've noticed the following. Data binding for some certain text field works,works and then at some point of time that binding stops working and all new values for this certain textfield are not passed to model. Nor exceptions. Nor any warnings. Nothing. Just binding doesn't work.
I have 4 textfiled which are created via fxml. Two for string model type. One for integer. One for bigdecimal. The problem happens to all these fields(sometimes to one, sometimes to several). As my number fields can have null values, I use for example PropertyObject but not IntegerProperty (people from openjfx advised so).
So is this JavaFx bug or what? P.S. I use felix osgi, weld cdi, and pax - I don't know if it matters...
My code is the following:
DTO - POJO Model
public class Task {
private String name;
private Integer order;
private BigDecimal weight;
private String comment;
private final PropertyChangeSupport propertyChangeSupport;
public Task() {
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
public String getName() {
return name;
}
public void setName(String name) {
String pv = this.name ;
this.name = name;
propertyChangeSupport.firePropertyChange("name", pv, name);
}
public Integer getOrder() {
return order;
}
public void setOrder(Integer order) {
Integer pv = this.order;
this.order = order;
propertyChangeSupport.firePropertyChange("order", pv, this.order);
}
public BigDecimal getWeight() {
return weight;
}
public void setWeight(BigDecimal weight) {
BigDecimal pv = this.weight;
this.weight = weight;
propertyChangeSupport.firePropertyChange("weight", pv, weight);
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
String pv = this.comment;
this.comment = comment;
propertyChangeSupport.firePropertyChange("comment", pv, this.comment);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
}
Adapter
public class TaskAdapter {
private StringProperty nameProperty;
private ObjectProperty<Integer> orderProperty;
private ObjectProperty<BigDecimal> weightProperty;
private StringProperty commentProperty;
public TaskAdapter(Task task) {
try {
nameProperty=new JavaBeanStringPropertyBuilder().bean(task).name("name").build();
orderProperty=new JavaBeanObjectPropertyBuilder<Integer>().bean(task).name("order").build();
weightProperty=new JavaBeanObjectPropertyBuilder<BigDecimal>().bean(task).name("weight").build();
commentProperty=new JavaBeanStringPropertyBuilder().bean(task).name("comment").build();
} catch (NoSuchMethodException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
public StringProperty getNameProperty() {
return nameProperty;
}
public ObjectProperty<Integer> getOrderProperty() {
return orderProperty;
}
public ObjectProperty<BigDecimal> getWeightProperty() {
return weightProperty;
}
public StringProperty getCommentProperty() {
return commentProperty;
}
}
BigDecimal Converter
public class SimpleBigDecimalStringConverter extends StringConverter<BigDecimal>{
#Override
public String toString(BigDecimal i) {
if (i == null) {
return "" ;
} else {
return i.toString();
}
}
#Override
public BigDecimal fromString(String string) {
if (string.trim().length() == 0) {
return null ;
} else {
try {
return new BigDecimal(string);
} catch (NumberFormatException nfe) {
return null ;
}
}
}
}
IntegerConverter
public class SimpleIntegerStringConverter extends StringConverter<Integer>{
#Override
public String toString(Integer i) {
if (i == null) {
return "" ;
} else {
return i.toString();
}
}
#Override
public Integer fromString(String string) {
if (string.trim().length() == 0) {
return null ;
} else {
try {
return Integer.valueOf(string);
} catch (NumberFormatException nfe) {
return null ;
}
}
}
}
Initializing code
Task task=new Task();
TaskAdapter adapter=new TaskAdapter(task);
nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());
What is happening
JavaFX Bindings use WeakChangeListeners behind the scenes to implement the binding. This means that the binding itself can be garbage collected if no other references to it are in scope. In your code, the adapter is defined as a local variable, so it gets prematurely garbage collected at some arbitrary time when the gc runs.
Demo
Here's a demo using your code that shows the issue. It has the same text fields you define, plus two buttons. One button dumps the value of the task to the console, the other forces the garbage collector to run. You'll see that the binding stops working as soon as you run the gc.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.converter.BigDecimalStringConverter;
public class POJOBindingExample extends Application {
private TextField nameTextField = new TextField();
private TextField orderTextField = new TextField();
private TextField weightTextField = new TextField();
private TextField commentTextField = new TextField();
#Override
public void start(Stage primaryStage) {
Task task = new Task();
TaskAdapter adapter = new TaskAdapter(task);
nameTextField.textProperty().bindBidirectional(adapter.getNameProperty());
orderTextField.textProperty().bindBidirectional(adapter.getOrderProperty(),new SimpleIntegerStringConverter());
weightTextField.textProperty().bindBidirectional(adapter.getWeightProperty(),new BigDecimalStringConverter());
commentTextField.textProperty().bindBidirectional(adapter.getCommentProperty());
GridPane grid = new GridPane();
grid.addRow(0, new Label("Name:"), nameTextField);
grid.addRow(1, new Label("Order:"), orderTextField);
grid.addRow(2, new Label("Weight:"), weightTextField);
grid.addRow(3, new Label("Comment:"), commentTextField);
Button showButton = new Button("Show Task");
showButton.setOnAction(e -> {
System.out.println(task.getName());
System.out.println(task.getOrder());
System.out.println(task.getWeight());
System.out.println(task.getComment());
System.out.println();
});
Button gcButton = new Button("Run GC");
gcButton.setOnAction(e -> System.gc());
HBox buttons = new HBox(10, showButton, gcButton);
BorderPane.setAlignment(grid, Pos.CENTER);
BorderPane.setAlignment(buttons, Pos.CENTER);
BorderPane.setMargin(grid, new Insets(10));
BorderPane.setMargin(buttons, new Insets(10));
BorderPane root = new BorderPane(grid, null, null, buttons, null);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Fix
To fix the problem, you need to ensure a reference to the TaskAdapter persists as long as you need it. In the above code, if you move the reference to the TaskAdapter so that it is an instance field, everything will work as required:
public class POJOBindingExample extends Application {
private TextField nameTextField = new TextField();
private TextField orderTextField = new TextField();
private TextField weightTextField = new TextField();
private TextField commentTextField = new TextField();
private TaskAdapter adapter;
#Override
public void start(Stage primaryStage) {
Task task = new Task();
adapter = new TaskAdapter(task);
// ... etc
}
}
You might also be interested in reading Tomas Mikula's blog, though I don't think you can use his library directly to implement binding to a POJO.
I need some help, even my case is simple.
But i can't understand why the check box in the tableview is not geting the value.
I have get the example from the javafx ensemble
I have a class
public class ReservationObj {
private BooleanProperty tcheck;
private StringProperty tname;
private StringProperty tstatus;
private int tser;
public ReservationObj(boolean tcheck, String lname , String lBStatus, int serialinVector) {
this.tcheck = new SimpleBooleanProperty(tcheck);
this.tname = new SimpleStringProperty(lname);
this.tstatus = new SimpleStringProperty(lBStatus);
this.tser = serialinVector;
this.tcheck.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
System.out.println("The check Box is: " + t1);
}
});
}
public BooleanProperty getTcheck() {return tcheck;}
public String getTname() {return tname.getValue() ;}
public void setTname(String tname) {this.tname.set(tname);}
public String getTstatus() {return tstatus.getValue() ;}
public void setTstatus(String tstatus) {this.tstatus.set(tstatus);}
public int getTser() {return tser;}
public void setTser(int tser) { this.tser = tser;}
}
And also i have the.
public Parent createContent() {
final ObservableList<ReservationObj> ReservationList = FXCollections.observableArrayList(
new ReservationObj(true, "aaaaaaaa", "bbbbbbbbb", 1));
TableColumn RCheckCol = new TableColumn<ReservationObj, Boolean>();
RCheckCol.setCellValueFactory(new PropertyValueFactory("tcheck"));
RCheckCol.setText("aaa");
RCheckCol.setCellFactory(new Callback<TableColumn<ReservationObj, Boolean>, TableCell<ReservationObj, Boolean>>() {
public TableCell<ReservationObj, Boolean> call(TableColumn<ReservationObj, Boolean> p) {
return new CheckBoxTableCell<ReservationObj, Boolean>();
}
});
TableColumn RNameCol = new TableColumn();
RNameCol.setCellValueFactory(new PropertyValueFactory("tname"));
RNameCol.setText("bbbb");
TableColumn RAgeCol = new TableColumn();
RAgeCol.setCellValueFactory(new PropertyValueFactory("tstatus"));
RAgeCol.setText("cccc");
TableView AAView = new TableView();
AAView.setItems(ReservationList);
AAView.setEditable(true);
AAView.getColumns().addAll(RCheckCol,RNameCol,RAgeCol);
return AAView;
}
And when simple .
#Override
public void start(Stage primaryStage) {
primaryStage.setScene(new Scene(createContent()));
primaryStage.show();
}
The columns of the tableview are getting the values except from the first “Checkbox”.
Also the listener in the checkbox is not working.
I really don’t understand what I have done wrong. Because I get the example from the ensemble.
Thanks for every idea and any solution.
Elias
Your ReservationObj class doesn’t respect JavaFX Properties convention in naming methods.
If you want to bind the RCheckCol with the tcheck BooleanProperty in :
RCheckCol.setCellValueFactory(new PropertyValueFactory("tcheck"));
You have to provide a tcheckProperty method in your model class:
public BooleanProperty tcheckProperty() {
return tcheck;
}
As an example of a valid JavaFX Bean:
public class Person {
private StringProperty name = new SimpleStringProperty("");
public Person(String name) {
this.name.setValue(name);
}
public String getName() {
return name.getValue();
}
public void setName(String name) {
this.name.setValue(name);
}
public StringProperty nameProperty() {
return name;
}
}