Notify . change of ObservableProperties - javafx

I have a class with following properties :
public class Member {
private LongProperty version;
private LongProperty id;
private StringProperty name;
private StringProperty surname;
private StringProperty bornname;
private StringProperty photo;
private ObjectProperty<Age> age;
private ObjectProperty<Sex> sex;
}
The problem is, when I add Change Listener on this object :
{
private ObjectProperty<Member> member;
public void addMemberChangeListener() {
this.member.addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
(...)
}
}
}
The change listener is not notified when I change value of properties. There is other way to notify listener about changes properties that implement standard Observer pattern ?

If you want to add a single listener that is notified when any of the properties belonging to an instance of Member change, you can do something like the following:
Member member = new Member();
ChangeListener<Object> listener = (obs, oldValue, newValue) ->
System.out.println(obs + " changed from " + oldValue + " to " + newValue);
Stream.of(member.versionProperty(), member.idProperty(), member.nameProperty() /*, ... */)
.forEach(p -> p.addListener(listener));
Note that the concrete property classes contain a constructor that takes an owning bean and a name of the property, so if you want to support this kind of use, you might want to use those constructors:
public class Member {
private final LongProperty version = new SimpleLongProperty(this, "version");
private final StringProperty name = new SimpleStringProperty(this, "name");
// ...
}
and then you can leverage this in the change listener:
ChangeListener<Object> listener = (obs, oldValue, newValue) ->
System.out.println(((Property<?>)obs).getName() + " changed from " + oldValue + " to " + newValue);
Just a comment: in my experience, needing this is rare (at best). You will usually (always?) find it more convenient to register a different listener with each property, as typically the action to take when a property is changed depends on which property is changed. In other cases, there is usually a more elegant solution anyway.

The properties listener is triggered when it changes value memberProperty.setValue(otherMember), not when you modify some property of its value memberProperty.getValue().setId(0)
Try to use
PropertyChangeSupport.
Example:
public class Member
{
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private long id;
private String name;
public void addChangeListener(PropertyChangeListener listener)
{
this.pcs.addPropertyChangeListener(listener);
}
public void removeChangeListener(PropertyChangeListener listener)
{
this.pcs.removePropertyChangeListener(listener);
}
public void setId(long newId)
{
long oldId = this.id;
this.id = newId;
pcs.firePropertyChange("id", oldId, newId);
}
public void setName(String newName)
{
String oldName = this.name;
this.name = newName;
pcs.firePropertyChange("name", oldName, newName);
}
}
Use:
Member member = new Member();
member.addChangeListener((evt) ->
{
System.out.println("Changed "
+ evt.getPropertyName()
+ ": " + evt.getOldValue()
+ " -> " + evt.getNewValue());
});
member.setId(15);
member.setName("Maria");
member.setName("Ana");
Output:
Changed id: 0 -> 15
Changed name: null -> Maria
Changed name: Maria -> Ana

Related

How to bind a List<String> to a ObservableList<String>?

I have an object Bean containing a List<String>. I would like to "bind" this list to an ObservableList so when an item is added to or removed from the original list, the ObservableList is updated (which then triggers the listeners that monitor the ObservableList).
I found this question whose answer shows how to wrap a simple String into a JavaFX StringProperty using JavaBeanStringPropertyBuilder.
I tried to do the same thing but replacing the String with a List<String> as shown below:
public class Bean {
private final List<String> nameList;
private final PropertyChangeSupport propertySupport ;
public Bean() {
this.nameList = new ArrayList<>();
this.propertySupport = new PropertyChangeSupport(this);
}
public List<String> getNameList() {
return nameList;
}
public void setNameList(List<String> nameList)
{
List<String> oldList = new ArrayList<>(this.nameList);
this.nameList.clear();
this.nameList.addAll(nameList);
propertySupport.firePropertyChange("nameList", oldList, this.nameList);
}
public void addName(String name) {
List<String> oldList = new ArrayList<>(this.nameList);
this.nameList.add(name);
propertySupport.firePropertyChange("nameList", oldList, this.nameList);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
}
Bean bean = new Bean();
JavaBeanObjectProperty<List<String>> listProperty = null;
try
{
listProperty = JavaBeanObjectPropertyBuilder.create().bean(bean).name("nameList").build();
} catch (NoSuchMethodException e)
{
throw new RuntimeException(e);
}
listProperty.addListener((ObservableValue<? extends List<String>> obs, List<String> oldName, List<String> newName) ->
{
System.out.println("List changed");
});
bean.setNameList(Arrays.asList("George", "James"));
But the listener is not triggered after calling setNameList() and I don't know what I'm missing.
Could you help me please?
A change listener registered with a Property<T> will only be notified if the value of the property actually changes. That is, the set(T newValue) method is implemented something like this:
public void set(T newValue) {
T oldValue = this.get();
if (! oldValue.equals(newValue)) {
// notify change listeners...
}
}
The JavaBeanObjectPropertyBuilder is going to create a JavaBeanObjectProperty<List<String>> (an implementation of Property<List<String>>) and set its value to the result of calling bean.getNameList(). I.e. the value held internally by listProperty is a reference to bean.nameList.
The JavaBeanObjectProperty also registers a listener via the call to bean.addPropertyChangeListener(...). When
propertySupport.firePropertyChange("nameList", oldList, this.nameList);
is invoked, the internal listener in JavaBeanObjectProperty will set its own value to the new value fired by the property change support; i.e. it will call
set(bean.nameList);
However, since this is just a reference to the current value of the property, no change listener registered with listProperty will be notified (basically, no change has occurred).
To clarify, if it helps: the content of the List<String> returned by listProperty.get() will change when you call
this.nameList.clear();
and
this.nameList.addAll(nameList);
in the bean (because the listProperty references bean.nameList), but the actual list reference itself has not changed.
You can test this with, e.g.
Bean bean = new Bean();
JavaBeanObjectProperty<List<String>> listProperty = null;
try
{
listProperty = JavaBeanObjectPropertyBuilder.create().bean(bean).name("nameList").build();
} catch (NoSuchMethodException e)
{
throw new RuntimeException(e);
}
listProperty.addListener((ObservableValue<? extends List<String>> obs, List<String> oldName, List<String> newName) ->
{
System.out.println("List changed");
});
List<String> oldList = listProperty.get();
bean.setNameList(Arrays.asList("George", "James"));
List<String> newList = listProperty.get();
System.out.println(oldList);
System.out.println(newList);
System.out.println(oldList == newList);
The best fix is simply to use an ObservableList in your Bean class:
public class Bean {
private final ObservableList<String> nameList;
public Bean() {
this.nameList = FXCollections.observableArrayList<>();
}
public ObservableList<String> getNameList() {
return nameList;
}
public void addName(String name) {
this.nameList.add(name);
}
}
Note you don't lose the functionality provided by setNameList(...); you can do
bean.getNameList().setAll(...);
if you want to set the entire content of the list. If you want the same API, you can use a ListProperty instead of the ObservableList.
The test code you have then becomes
Bean bean = new Bean();
bean.getNameList().addListener((ListChangeListener.Change<? extends String> change) ->
{
System.out.println("List changed");
});
bean.getNameList().setAll("George", "James");
As stated in the comments in the question, I don't really understand having any restriction preventing the use of ObservableList in the model; indeed this is exactly the use case for which ObservableList (along with the properties and bindings API) was designed.
There is no adapter designed for use with observable lists in the same way as there are Java Bean adapters for simple properties. Thus if you really wanted to avoid use of ObservableList in your model class (which, again, doesn't really make sense to me), you would have to implement your own listener notification for the Bean:
public class Bean {
private final List<String> nameList ;
private final List<Consumer<String>> nameAddedListeners ;
private final List<Consumer<List<String>>> nameListReplacedListeners ;
public Bean() {
this.nameList = new ArrayList<>();
this.nameAddedListeners = new ArrayList<>();
this.nameListReplacedListeners = new ArrayList<>();
}
public List<String> getNameList() {
return nameList ;
}
public void setNameList(List<String> newNames) {
this.nameList.setAll(newNames);
nameListReplacedListeners.forEach(listener -> listener.accept(newNames));
}
public void addName(String name) {
this.nameList.add(name);
nameAddedListeners.forEach(listener -> listener.accept(name));
}
public void addNameListReplacedListener(Consumer<List<String>> listener) {
nameListReplacedListeners.add(listener);
}
public void addNameAddedListener(Consumer<String> listener) {
nameAddedListeners.add(listener);
}
}
Now you could do
Bean bean = new Bean();
bean.addNameListReplacedListener(list -> System.out.println("Names changed"));
bean.setNameList(List.of("George", "James"));
or you could effectively create an adapter:
Bean bean = new Bean();
ObservableList<String> names = FXCollections.observableArrayList(bean.getNameList());
bean.addNameAddedListener(names::add);
bean.addNameListReplacedListener(names::setAll);
etc.

JavaFX TreeTableView - Prevent editing of unavailable cells

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

How to build JavaFX styleable objects properly, that can be GC

I decided to refactor my application because of hugh memory leaks in the old version. For visualization objects, I decide to not more using fxml, but Styleable interface.
So I created a class Sim Photo like this :
public class SimPhoto extends Control {
private static final String DEFAULT_CLASS_NAME = "sim-photo";
private static final Double DEFAULT_STROKE_WIDTH = 0.0;
#Getter
#Setter
private static String DEFAULT_PHOTO = "";
private StyleableStringProperty imgPath;
private StyleableIntegerProperty arcHeight;
private StyleableIntegerProperty arcWidth;
private DoubleProperty strokeWidth;
private ObjectProperty<Paint> stroke;
private ObjectProperty<Paint> fill;
public SimPhoto() {
initialize();
}
public SimPhoto(#NamedArg("imgPath") String imgPath) {
this();
this.imgPathProperty().set(imgPath);
}
//Example of init properties
public final StyleableIntegerProperty arcHeightProperty() {
if (arcHeight == null) {
arcHeight = new SimpleStyleableIntegerProperty(
StyleableProperties.ARC_WIDTH,
SimPhoto.this,
"arcWidth",
0
);
}
return arcHeight;
}
public final StringProperty imgPathProperty() {
if(imgPath == null) {
imgPath = new SimpleStyleableStringProperty(
StyleableProperties.IMG_PATH,
SimPhoto.this,
"imgPath",
"");
}
return imgPath;
}
(...)
}
In my skin class, I use binding of properties from Control Class
public class SimPhotoSkin extends SkinBase<SimPhoto> {
#Getter
private Rectangle photoFond = new Rectangle();
private Rectangle photoView = new Rectangle();
private boolean invalidate = false;
private InvalidationListener invalidListener = this::invalidated;
private ChangeListener<String> pathListener = this::pathChanged;
public SimPhotoSkin(SimPhoto control) {
super(control);
initVisualization();
initListeners();
}
private void initVisualization() {
getChildren().addAll(photoFond, photoView);
if (getSkinnable().imgPathProperty() != null) {
setNewFond(getSkinnable().getImgPath());
}
}
private void initListeners() {
photoFond.widthProperty().bind(getSkinnable().widthProperty().subtract(5));
photoFond.heightProperty().bind(getSkinnable().heightProperty().subtract(5));
photoView.widthProperty().bind(photoFond.widthProperty().subtract(photoFond.strokeWidthProperty()));
photoView.heightProperty().bind(photoFond.heightProperty().subtract(photoFond.strokeWidthProperty()));
photoView.arcWidthProperty().bind(getSkinnable().arcWidthProperty());
photoView.arcHeightProperty().bind(getSkinnable().arcHeightProperty());
photoFond.arcWidthProperty().bind(getSkinnable().arcWidthProperty());
photoFond.arcHeightProperty().bind(getSkinnable().arcHeightProperty());
photoFond.fillProperty().bind(getSkinnable().fillProperty());
photoFond.strokeProperty().bind(getSkinnable().strokeProperty());
photoFond.strokeWidthProperty().bind(getSkinnable().strokeWidthProperty());
getSkinnable().imgPathProperty().addListener(pathListener);
}
private void pathChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {
(...)
}
private void setNewFond(String path) {
(...)
}
private void invalidated(Observable observable) {
invalidate = true;
}
}
I know that object cannot been GC while exist a reference to it. So I have a big problem, because event these objects are no more used, thay cannot be GC , and in my application ,when I need creating more than 300 objects at time is a big problem.
I tried to create method clean(), that will be unbind all bidnings and listeners, but it's not realy helpful. Problem still persist.
I'm thinking about any workaround like a Manager, that will store all objects in queue and while calling will return one objects disponibles or create new one.
But this is the last possibility, if I dont find any solution for my problem, and I would like avoid this.

displaying multiple components within a cell of vaadin grid

Is there any way I can display multiple components(like CheckBox, Label etc.) to a single cell of a Vaadin Grid? The Grid displays data populated dynamically by a GeneratedPropertyContainer.
Thanks in advance.
If you search the Vaadin directory you will find a few extensions, such as ComponentRenderer add-on which allow you to have such features in a somewhat painless way. Below you can see a code sample based on Vaadin v7.7.3 and v1.0.2 of the before-mentioned add-on. Please remember to update and recompile your widgetset.
public class GridWithMultiComponentRenderer extends VerticalLayout {
private static final String BUTTONS_ID = "buttons";
public GridWithMultiComponentRenderer() {
// basic grid setup
Grid grid = new Grid(new BeanItemContainer<>(Person.class));
grid.setSizeFull();
addComponent(grid);
// add the decorator
ComponentGridDecorator<Person> gridDecorator = new ComponentGridDecorator<>(grid, Person.class);
// generate the column which will display the components
gridDecorator.addComponentColumn(BUTTONS_ID, person -> new HorizontalLayout(
new Button("Get name", event -> Notification.show(person.getName())),
new Button("Get surname", event -> Notification.show(person.getSurname())),
new Button("Get age", event -> Notification.show(String.valueOf(person.getAge())))
));
// set column order
grid.setColumns("name", "surname", "age", BUTTONS_ID);
// add some dummy data
Random random = new Random();
for (int i = 0; i < 10; i++) {
gridDecorator.add(new Person("Name " + i, "Surname " + i, random.nextInt(99) + 1));
}
}
// POJO for simple binding
public static class Person {
private String name;
private String surname;
private int age;
public Person(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
Result:

Why does the JavaBean adapter not update the JavaFX property?

I'm new to JavaFX and need a bidirectional binding between a GUI (using JavaFX properties) and my old Java code (not using JavaFX properties). I tried using a JavaBean adapter as the following simple example shows:
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.adapter.JavaBeanDoubleProperty;
import javafx.beans.property.adapter.JavaBeanDoublePropertyBuilder;
public class App
{
public static void main(String[] args) throws NoSuchMethodException
{
Pojo pojo = new Pojo();
FXModel model = new FXModel();
JavaBeanDoubleProperty adapter = JavaBeanDoublePropertyBuilder
.create().bean(pojo).name("value").build();
model.valueProperty().bindBidirectional(adapter);
System.out.println("Adapter before: " + adapter.get());
System.out.println("Model before: " + model.getValue());
System.out.println("Bean before: " + pojo.getValue());
pojo.setValue(123d);
System.out.println();
System.out.println("Adapter after: " + adapter.get());
System.out.println("Model after: " + model.getValue());
System.out.println("Bean after: " + pojo.getValue());
}
public static class Pojo
{
private double value;
public double getValue()
{
return value;
}
public void setValue(double value)
{
this.value = value;
}
}
public static class FXModel
{
private final DoubleProperty value = new SimpleDoubleProperty();
public double getValue()
{
return value.get();
}
public void setValue(double value)
{
this.value.set(value);
}
public DoubleProperty valueProperty()
{
return value;
}
}
}
The generated output is
Adapter before: 0.0 Model before: 0.0 Bean before: 0.0
Adapter after: 123.0 Model after: 0.0 Bean after: 123.0
So, by setting the "normal" Java object to a new value the JavaBeanDoubleProperty is informed about the change, but the JavaFX property is not, although it is bound to the adapter. Why?
Even by adding a PropertyChangeSupport to Pojo as described here,
public static class Pojo
{
private double value;
private PropertyChangeSupport pcs;
public Pojo()
{
pcs = new PropertyChangeSupport(this);
}
public double getValue()
{
return value;
}
public void setValue(double value)
{
final double oldValue = this.value;
this.value = value;
pcs.firePropertyChange("name", oldValue, value);
}
public void addPropertyChangeListener(
final PropertyChangeListener listener)
{
pcs.addPropertyChangeListener(listener);
}
},
it does not work. (Unfortunately, a second trial using BeanPathAdapter also did not work.)
You just made a slip in Pojo.setValue(double value). This method fires a PropertyChangeEvent for the property "name". It supposed to be "value".
Just change it to
public void setValue(double value)
{
final double oldValue = this.value;
this.value = value;
pcs.firePropertyChange("value", oldValue, value);
}
and it will work.
The binding is supposed to be between the adapter and model. Do not modify the pojo values directly, the adapter will do the updating. Replacing the line:
pojo.setValue(123d);
with this,
adapter.setValue(123d);
results in the expected output with all values updated including the pojo. The same happens when you set the value of the model. The diagrams at this post may help you visualise the working.

Resources