Double binding: Model <-> Bean <-> JavaFX - data-binding

Considering I have the following three classes:
Model:
class Field {
private String label;
}
Bean:
class FieldBean {
public FieldBean(Field f) { this.field = f};
private Field field;
private SimpleStringProperty label = new SimpleStringProperty();
public String getLabel() { return label.get(); }
}
JavaFX Application:
class MyApp extends Application {
public void start(Stage stage) {
Label lblTest = new Label();
FieldBean fieldBean = new FieldBean(model.getField());
Bindings.bindBidirectional(fieldBean.getLabel(), label.textProperty());
}
}
What I am trying to achieve is to have the Label updated, whenever i change the Field label. From what I know of this model binding so far, I need to add a PropertyChangeListener, but I haven't got a clue, where about it should be attached. My guess would be in the FieldBean. (and my model already has property change support, just stripped it for better readability).

You can use a JavaBeanStringProperty, which is basically an adapter between a JavaFX StringProperty and a bound JavaBeans property. For example:
class FieldBean {
private final StringProperty label ;
private final Field field;
public FieldBean(Field f) {
this.field = f;
label = JavaBeanStringPropertyBuilder.create()
.bean(this.field)
.name(label)
.build();
public String getLabel() { return label.get(); }
public StringProperty labelProperty() { return label ; }
public void setLabel(String label) { this.label.set(label); }
}
then (assuming you have property change listeners set up for Field.label), Field.label and FieldBean.label will automatically be bound.
As Tomas points out, you don't need both of these classes as they effectively represent exactly the same thing. You can omit the Field class as Tomas shows, or, if your Field class already exists as part of a data representation that's already written, you can just use the JavaBeanStringProperty as an adapter to bind directly to a bound JavaBean property:
Field field = new Field();
// ...
Label uiLabel = new Label();
uiLabel.textProperty().bind(JavaBeanStringPropertyBuilder.create()
.bean(field)
.name(label)
.build());
Calling field.setLabel(...) now automatically changes uiLabel's text. In this example the FieldBean class is omitted entirely.

If FieldBean.field.label and FieldBean.label.get() are meant to represent the same value, then there is no need to have them both.
class FieldBean {
private StringProperty label;
public FieldBean(String label) {
this.label = new SimpleStringProperty(label);
}
public StringProperty labelProperty() { return label; }
public String getLabel() { return label.get(); }
}
Then, to keep a Label updated, you just do
label.textProperty().bind(fieldBean.labelProperty());
Notice that the class Field is omitted altogether.

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 Application fails to compile on lambda expression

I am trying to build a JavaFX Application to display a TreeTableView. Still setting up this whole thing. I got it to work with only one column without the Product class but i am struggling to make it work with the Product class and two columns. The following piece of code fails to compile:
col1.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getNameProperty());
and spits out this error:
Error:(38, 121) java: incompatible types: bad return type in lambda expression
java.lang.String cannot be converted to javafx.beans.value.ObservableValue<java.lang.String>
This is the entire code:
public class Controller implements Initializable {
#FXML
private TreeTableView<Product> tableView;
#FXML
private TreeTableColumn<Product, String> col1;
#FXML
private TreeTableColumn<Product, String> col2;
TreeItem<Product> product1 = new TreeItem<>(new Product("Bread", "300g"));
TreeItem<Product> product2 = new TreeItem<>(new Product("Eggs", "5"));
TreeItem<Product> product3 = new TreeItem<>(new Product("Brad Pitt", "One and Only one"));
TreeItem<Product> product4 = new TreeItem<>(new Product("Moisturizer", "20"));
TreeItem<Product> product5 = new TreeItem<>(new Product("Horse Lubricant", "4"));
TreeItem<Product> root = new TreeItem<>(new Product("Name", "Quantity"));
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
root.getChildren().setAll(product1, product2, product3, product4, product5);
col1.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getNameProperty());
col2.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<Product, String> param) -> param.getValue().getValue().getQuantityProperty());
tableView.setRoot(root);
tableView.setShowRoot(false);
}
public class Product{
SimpleStringProperty nameProperty;
SimpleStringProperty quantityProperty;
public Product(String name, String quantity){
this.nameProperty = new SimpleStringProperty(name);
this.quantityProperty = new SimpleStringProperty(quantity);
}
public String getNameProperty() {
return nameProperty.get();
}
public SimpleStringProperty namePropertyProperty() {
return nameProperty;
}
public void setNameProperty(String nameProperty) {
this.nameProperty.set(nameProperty);
}
public String getQuantityProperty() {
return quantityProperty.get();
}
public SimpleStringProperty quantityPropertyProperty() {
return quantityProperty;
}
public void setQuantityProperty(String quantityProperty) {
this.quantityProperty.set(quantityProperty);
}
}
}
First, your Product class is not conventional. Typically the field name matches the property name (e.g. name, not nameProperty). Then you name your getter, setter, and property getter after the name of the property. For instance:
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
public class Product {
private final StringProperty name = new SimpleStringProperty(this, "name");
public final void setName(String name) { this.name.set(name); }
public final String getName() { return name.get(); }
public final StringProperty nameProperty() { return name; }
private final StringProperty quantity = new SimpleStringProperty(this, "quantity");
public final void setQuantity(String quantity) { this.quantity.set(quantity); }
public final String getQuantity() { return quantity.get(); }
public final StringProperty quantityProperty() { return quantity; }
public Product() {} // typically Java(FX)Beans provide no-arg constructors as well
public Product(String name, String quantity) {
setName(name);
setQuantity(quantity);
}
}
Note: Your class is a non-static nested (i.e. inner) class. This means each Product instance requires an instance of the enclosing class. If you want to keep Product a nested class, consider making it static. My example above assumes Product is in its own source file.
With that class, you would define your cell value factories like so:
TreeTableColumn<Product, String> nameCol = ...;
nameCol.setCellValueFactory(data -> data.getValue().getValue().nameProperty());
TreeTableColumn<Product, String> quantityCol = ...;
quantityCol.setCellValueFactory(data -> data.getValue().getValue().quantityProperty());
Notice the factories return the appropriate property of the Product instance. This solves your compilation error since StringProperty is an instance of ObservableValue<String>. It also means your table has direct access to the backing model's property, which helps with keeping the table up-to-date and also with implementing inline editing.
In case it helps, here's setting the cell value factory of nameCol using an anonymous class which explicitly shows all the types used:
nameCol.setCellValueFactory(new Callback<>() { // may have to explicitly define type arguments, depending on version of Java
#Override
public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<Product, String> data) {
TreeItem<Product> treeItem = data.getValue();
Product product = treeItem.getValue();
return product.nameProperty();
}
});

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 do I handle media metadata in separate class?

Im making a mediaplayer using JavaFX Media classes. I made a SongModel class, that incapsulates all metadata from a file and creates Media and MediaPlayer instances.
It looks something like this:
private final StringProperty album =
new SimpleStringProperty(this, "album");
public String getAlbum(){ return album.get(); }
public void setAlbum(String value){ album.set(value); }
public StringProperty albumProperty() { return album; }
There are also artist, year, title, and albumCover fields that look just like that. Also, MediaPlayer property is exposed as a read-only:
public MediaPlayer getMediaPlayer(){ return mediaPlayer.get(); }
public ReadOnlyObjectProperty<MediaPlayer> mediaPlayerProperty(){
return mediaPlayer.getReadOnlyProperty();
}
I use a MapChangelistener to check if the field is available and then pass it to the handleMetadata method:
private void initializeMedia(String url){
try {
final Media media = new Media(url);
media.getMetadata().addListener(new MapChangeListener<String, Object>(){
#Override
public void onChanged(MapChangeListener.Change<? extends String, ? extends Object> ch) {
if(ch.wasAdded()){
handleMetadata(ch.getKey(), ch.getValueAdded());
}
}
});
mediaPlayer.setValue(new MediaPlayer(media));
mediaPlayer.get().setOnError(new Runnable() {
#Override
public void run() {
String errorMessage = mediaPlayer.get().getError().getMessage();
System.out.println("MediaPlayer error: "+errorMessage);
}
});
}catch(RuntimeException e){
System.out.println("Construction error: "+e);
}
}
private void handleMetadata(String key, Object value){
if(key.equals("album")){
setAlbum(value.toString());
} else if (key.equals("artist")){
setArtist(value.toString());
} if (key.equals("title")){
setTitle(value.toString());
} if (key.equals("year")){
setYear(value.toString());
} if (key.equals("image")){
setAlbumCover((Image)value);
}
}
Then I made an AbstractView class that provides access to SongModel:
public abstract class AbstractView {
protected final SongModel songModel;
protected final Node viewNode;
public AbstractView(SongModel songModel){
this.songModel = songModel;
this.viewNode = initView();
}
public Node getViewNode() {
return viewNode;
}
protected abstract Node initView();
}
But when I try to make a MetadataView class, I run into some problems.
Heres how it looks:
public class MetadataView extends AbstractView{
public Label artist;
public Label album;
public Label title;
public Label year;
public ImageView albumCover;
public MetadataView(SongModel songModel) {
super(songModel);
}
#Override
protected Node initView() {
artist = new Label();
artist.setId("artist");
album = new Label();
album.setId("album");
title = new Label();
title.setId("title");
year = new Label();
year.setId("year");
final Reflection reflection = new Reflection();
reflection.setFraction(0.2);
final URL url = getClass().getResource("resources/defaultAlbum.png");
Image image = new Image(url.toString());
albumCover = new ImageView(image);
albumCover.setFitWidth(240);
albumCover.setPreserveRatio(true);
albumCover.setSmooth(true);
albumCover.setEffect(reflection);
final GridPane gp = new GridPane();
gp.setPadding(new Insets(10));
gp.setHgap(20);
gp.add(albumCover, 0,0,1, GridPane.REMAINING);
gp.add(title, 1,0);
gp.add(artist, 1,1);
gp.add(album, 1,2);
gp.add(year, 1,3);
final ColumnConstraints c0 = new ColumnConstraints();
final ColumnConstraints c1 = new ColumnConstraints();
c1.setHgrow(Priority.ALWAYS);
gp.getColumnConstraints().addAll(c0,c1);
final RowConstraints r0 = new RowConstraints();
r0.setValignment(VPos.TOP);
gp.getRowConstraints().addAll(r0,r0,r0,r0);
return gp;
}
}
And heres how I call it in the start method:
metaDataView = new MetadataView(songModel);
The problem is that it displays only default metadata without taking it from the songmodel class. I tried running metadata view code together with data handling in one class and everything worked, but when i try to put them in separate classes - it doesnt. Music runs just fine, its just the data thats not displaying. Could anybody tell me what am I missing? How do i make it display metadata from a SongModel class? Ive spent a lot of time on that and dont want it to go to waste.
After a day of searching I have found an answer: binds. All I had to do was to bind label property of SongModel class to label property of MetadataView class:
title.textProperty().bind(songModel.titleProperty());
artist.textProperty().bind(songModel.artistProperty());
album.textProperty().bind(songModel.albumProperty());
year.textProperty().bind(songModel.yearProperty());
albumCover.imageProperty().bind(songModel.albumCoverProperty());

How do I share a Observablelist between 2 controllers , I have tried various methods but I get null pointer exceptions

I am absolutely new to coding and am stuck with some "binding" code. I have a Record class with getters and setters, and a records Observable list which together populate a Tableview in a controller called RecordController. I want to populate another tableview on a separate AdminController file both with different view fxml's.
How can I bind the SimpleStringProperties in my Record.java class in such a manner that when they get updated by by the set method( by any UI user interaction) in RecordContoller ..the (editatble) Tableview in the AdminController gets populated with the new values.
Some bits of the code.....
public class Record {
private final SimpleStringProperty stationName;
private final SimpleStringProperty staffName;
private final SimpleStringProperty staffNumber;
private final SimpleStringProperty agencyName;
private final SimpleStringProperty aircraftType;
private final SimpleStringProperty issueDate;
private final SimpleStringProperty validTill;
public Record(String stationName, String staffName,
String staffNumber, String agencyName, String aircraftType,
String issueDate, String validTill) {
this.stationName = new SimpleStringProperty(stationName);
this.staffName = new SimpleStringProperty(staffName);
this.staffNumber = new SimpleStringProperty(staffNumber);
this.aircraftType = new SimpleStringProperty(aircraftType);
this.agencyName = new SimpleStringProperty(agencyName);
this.issueDate = new SimpleStringProperty(issueDate);
this.validTill = new SimpleStringProperty(validTill);
}
public String getStationName() {
return stationName.getValue();
}
public String getStaffName() {
return staffName.getValue();
}
public String getStaffNumber() {
return staffNumber.getValue();
}
public String getAgencyName() {
return agencyName.getValue();
}
public String getAircraftType() {
return aircraftType.getValue();
}
public String getIssueDate() {
return issueDate.getValue();
}
public String getValidTill() {
return validTill.getValue();
}
}
And the RecordController with the first Tableview.....
columnStation.setCellValueFactory(new PropertyValueFactory<>("stationName"));
columnStaffName.setCellValueFactory(new PropertyValueFactory<>("staffName"));
columnStaffNumber.setCellValueFactory(new PropertyValueFactory<>("staffNumber"));
columnAgency.setCellValueFactory(new PropertyValueFactory<>("agencyName"));
columnAircraftType.setCellValueFactory(new PropertyValueFactory<>("aircraftType"));
columnIssueDate.setCellValueFactory(new PropertyValueFactory<>("issueDate"));
columnValidTill.setCellValueFactory(new PropertyValueFactory<>("validTill"));
recordTableView.setItems(null);
recordTableView.setItems(records);
The records observable list being .....
private ObservableList<Record> records=FXCollections.observableArrayList();
The adminController for the other fxml also has another tableview which I want to populate in some way with the property variables in the Record.java (class / bean ? ).
To recover from the -1 my question received...
In case a fellow newbie stumbles on this question. The answer lies in the following post.
Passing Parameters JavaFX FXML
If you use the JavaFX properties pattern, then changes to the properties in your Record instances will be automatically reflected in the table view. So your Record class should look like
public class Record {
private final StringProperty stationName;
private final StringProperty staffName;
private final StringProperty staffNumber;
private final StringProperty agencyName;
private final StringProperty aircraftType;
private final StringProperty issueDate;
private final StringProperty validTill;
public Record(String stationName, String staffName,
String staffNumber, String agencyName, String aircraftType,
String issueDate, String validTill) {
this.stationName = new SimpleStringProperty(stationName);
this.staffName = new SimpleStringProperty(staffName);
this.staffNumber = new SimpleStringProperty(staffNumber);
this.aircraftType = new SimpleStringProperty(aircraftType);
this.agencyName = new SimpleStringProperty(agencyName);
this.issueDate = new SimpleStringProperty(issueDate);
this.validTill = new SimpleStringProperty(validTill);
}
public StringProperty stationNameProperty() {
return stationName ;
}
public final String getStationName() {
return stationNameProperty().get();
}
public final void setStationName(String stationName) {
stationNameProperty().set(stationName);
}
// similarly for other properties...
}
Note that many IDEs (e.g. Netbeans, or Eclipse with the E(fx)clipse plugin) will generate these methods for you.
Now, as long as you are changing the same Record objects that are in the table's items list, changes to those Records will automatically result in changes in the table: there is no need for additional binding.

Resources