Why does the JavaBean adapter not update the JavaFX property? - javafx

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.

Related

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.

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

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.

Using find method of org.apache.commons.collections4.CollectionUtils with Predicate

I was using org.apache.commons.collections.CollectionUtils and for this version using find method was like this:
BeanPropertyValueEqualsPredicate objIdEqualsPredicate = new BeanPropertyValueEqualsPredicate("objId", objId);
myObj = (MyClass) CollectionUtils.find(myObjSet, objIdEqualsPredicate);
But with org.apache.commons.collections4.CollectionUtils, I don't know how to make it work.
Here what I do now but if there is a clear way of it, I will be glad to learn:
Predicate<MyClass> objIdEqualsPredicate = new Predicate<MyClass>() {
#Override
public boolean evaluate(MyClass obj) {
return obj.getObjId().equals(objId);
}
};
myObj = CollectionUtils.find(myObjSet, objIdEqualsPredicate);
Is there a way to filter some objects according to the their fields' values. If possible I don't want to use anonymous class for this.
Thanks.
As the common-beanutils still have commons-collections as dependency, you must implement the Predicate interface.
For example you can take the source code of BeanPropertyValueEqualsPredicate and refactor it, so your version implements the org.apache.commons.collections4.Predicate interface.
Or you write your own version. I would prefer not to use anonymous inner classes, because of the possibility to write unit tests for the predicate and reuse it.
Quick Example (not nullsafe,..)
#Test
public class CollectionsTest {
#Test
void test() {
Collection<Bean> col = new ArrayList<>();
col.add(new Bean("Foo"));
col.add(new Bean("Bar"));
Predicate<Bean> p = new FooPredicate("Bar");
Bean find = CollectionUtils.find(col, p);
Assert.assertNotNull(find);
Assert.assertEquals(find.getFoo(), "Bar");
}
private static final class FooPredicate implements Predicate<CollectionsTest.Bean> {
private String fieldValue;
public FooPredicate(final String fieldValue) {
super();
this.fieldValue = fieldValue;
}
#Override
public boolean evaluate(final Bean object) {
// return true for a match - false otherwise
return object.getFoo().equals(fieldValue);
}
}
public static class Bean {
private final String foo;
Bean(final String foo) {
super();
this.foo = foo;
}
public String getFoo() {
return foo;
}
}
}

Google App Engine Datanucleus enhencer

I have following class as superclass
#Entity
#MappedSuperclass
public class Location implements LocationCapable {
#Basic
private Double latitude;
#Basic
private Double longitude;
#Basic
private List<String> geocells;
#PrePersist
#Transient
private void generateGeoCells() {
geocells = GeocellManager.generateGeoCell(getLocation());
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
#Override
#Transient
#JsonIgnore
public Point getLocation() {
return new Point(latitude, longitude);
}
#Override
#Transient
#JsonIgnore
public String getKeyString() {
return latitude + ":" + longitude;
}
#Override
public List<String> getGeocells() {
return geocells;
}
public void setGeocells(List<String> geocells) {
this.geocells = geocells;
}
}
And another one which inherits from this
But when I try to run JUnit test I got this
Caused by: org.datanucleus.metadata.InvalidMetaDataException: Class Location has application-identity and no objectid-class specified yet has 0 primary key fields. Unable to use SingleFieldIdentity.
at org.datanucleus.metadata.AbstractClassMetaData.determineObjectIdClass(AbstractClassMetaData.java:1032)
at org.datanucleus.metadata.ClassMetaData.populate(ClassMetaData.java:205)
at org.datanucleus.metadata.AbstractClassMetaData.validateSuperClass(AbstractClassMetaData.java:720)
at org.datanucleus.metadata.AbstractClassMetaData.determineSuperClassName(AbstractClassMetaData.java:642)
at org.datanucleus.metadata.ClassMetaData.populate(ClassMetaData.java:193)
at org.datanucleus.metadata.MetaDataManager$1.run(MetaDataManager.java:2317)
at java.security.AccessController.doPrivileged(Native Method)
at org.datanucleus.metadata.MetaDataManager.populateAbstractClassMetaData(MetaDataManager.java:2311)
at org.datanucleus.metadata.MetaDataManager.populateFileMetaData(MetaDataManager.java:2148)
at org.datanucleus.metadata.MetaDataManager.initialiseFileMetaDataForUse(MetaDataManager.java:864)
at org.datanucleus.metadata.MetaDataManager.loadPersistenceUnit(MetaDataManager.java:794)
at org.datanucleus.jpa.EntityManagerFactoryImpl.initialisePMF(EntityManagerFactoryImpl.java:488)
at org.datanucleus.jpa.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:355)
at org.datanucleus.store.appengine.jpa.DatastoreEntityManagerFactory.<init>(DatastoreEntityManagerFactory.java:63)
at org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider.createEntityManagerFactory(DatastorePersistenceProvider.java:35)
at javax.persistence.Persistence.createFactory(Persistence.java:172)
... 67 more
Also I've tried to add in supperclass the key field annotated with #Id but it gives no result for me
You have to have an #Id field, as the message says.
Looks like it was some problem with enhancing or eclipse plugin, no metter after restart of IDE the problem dessapiared. But only this problem with ID. Actually I've faced with another very strange problem related to embedded entities. I have following domain model:
#Entity
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
#Basic
private String name;
// Entity won't persist if location is not null, requires to persist with
// further update
#Embedded
// #OneToOne(cascade = CascadeType.ALL) it works the same when I add or remove this line
private Location location;
/* getters and setters */
}
#Embeddable
public class Location implements LocationCapable {
#Basic
#NotNull
private Double latitude;
#Basic
#NotNull
private Double longitude;
#Basic
private List<String> geocells;
/* getters and setters */
}
To test it I have following JUnit test case:
#Test
public void testSave() throws Exception {
City city = new City("testCity1", new Location(1d, 1d));
cityDao.persist(city);
assertNotNull(city.getId());
}
cityDao.persist(city) does simply jpaTemplate.persist(object);
And at when I try to persist this entity I got following exception:
Caused by: java.lang.IllegalArgumentException: out of field index :-1
at com.myproject.model.Location.jdoProvideField(Location.java)
at org.datanucleus.state.JDOStateManagerImpl.provideField(JDOStateManagerImpl.java:2585)
at org.datanucleus.state.JDOStateManagerImpl.provideField(JDOStateManagerImpl.java:2555)
at org.datanucleus.store.mapped.mapping.CollectionMapping.postInsert(CollectionMapping.java:91)
at org.datanucleus.store.mapped.mapping.EmbeddedPCMapping.postInsert(EmbeddedPCMapping.java:104)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager.runPostInsertMappingCallbacks(DatastoreRelationFieldManager.java:217)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager.access$200(DatastoreRelationFieldManager.java:48)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager$1.apply(DatastoreRelationFieldManager.java:116)
at org.datanucleus.store.appengine.DatastoreRelationFieldManager.storeRelations(DatastoreRelationFieldManager.java:81)
at org.datanucleus.store.appengine.DatastoreFieldManager.storeRelations(DatastoreFieldManager.java:955)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.storeRelations(DatastorePersistenceHandler.java:546)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertPostProcess(DatastorePersistenceHandler.java:304)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObjects(DatastorePersistenceHandler.java:256)
at org.datanucleus.store.appengine.DatastorePersistenceHandler.insertObject(DatastorePersistenceHandler.java:240)
at org.datanucleus.state.JDOStateManagerImpl.internalMakePersistent(JDOStateManagerImpl.java:3185)
at org.datanucleus.state.JDOStateManagerImpl.flush(JDOStateManagerImpl.java:4513)
at org.datanucleus.ObjectManagerImpl.flushInternal(ObjectManagerImpl.java:2814)
at org.datanucleus.ObjectManagerImpl.flush(ObjectManagerImpl.java:2754)
at org.datanucleus.ObjectManagerImpl.preCommit(ObjectManagerImpl.java:2893)
at org.datanucleus.TransactionImpl.internalPreCommit(TransactionImpl.java:369)
at org.datanucleus.TransactionImpl.commit(TransactionImpl.java:256)
at org.datanucleus.jpa.EntityTransactionImpl.commit(EntityTransactionImpl.java:104)
at org.datanucleus.store.appengine.jpa.DatastoreEntityTransactionImpl.commit(DatastoreEntityTransactionImpl.java:55)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467)
... 41 more

MVMLight Messaging and Silverlight

I am trying to get a sample to work using MVVM Light and the Messaging Class. In the sample, I have a test project created from the MVVM Template for Silveright 4. I have added a button on the main page. When the button is clicked, it updates a property on the ViewModel. When the property is updated, I want to show a messagebox with the new value.
The key line of code is:
Messenger.Default.Register(this, new Action(ShowMessage));
I can get this to work in WPF, but not silverlight. It should call ShowMessage with the string parameter when the property changes, but it does not. If I use:
Messenger.Default.Send("Hello MVVM");
This works and the string is sent as a message to ShowMessage.
However, the message does not get sent if the property changes, even though the property was created with the MVVMINPC snippet and has the following line:
RaisePropertyChanged(MyPropertyPropertyName, oldValue, value, true);
This should have the same effect as Messager.Default.Send but it seems to be ignored. ThePropertyChangedEvent is indeed raised, but the messanger part seems to be disconnected.
Am I doing something wrong? Here is the full MainViewModel:
public class MainViewModel : ViewModelBase
{
public RelayCommand MyRelayCommand { get; set; }
public const string MyPropertyPropertyName = "MyProperty";
private string _myProperty = "test";
public string MyProperty
{
get
{
return _myProperty;
}
set
{
if (_myProperty == value)
{
return;
}
var oldValue = _myProperty;
_myProperty = value;
RaisePropertyChanged(MyPropertyPropertyName, oldValue, value, true);
}
}
public void DoSomething()
{
//Messenger.Default.Send("Hello MVVM"); //Works
this.MyProperty = "Hello World"; //Doesn't work.
}
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
Messenger.Default.Register(this, new Action<string>(ShowMessage));
MyRelayCommand = new RelayCommand(new Action(DoSomething));
this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(MainViewModel_PropertyChanged);
}
void MainViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
MessageBox.Show(e.PropertyName);
}
}public class MainViewModel : ViewModelBase
{
public RelayCommand MyRelayCommand { get; set; }
public const string MyPropertyPropertyName = "MyProperty";
private string _myProperty = "test";
public string MyProperty
{
get
{
return _myProperty;
}
set
{
if (_myProperty == value)
{
return;
}
var oldValue = _myProperty;
_myProperty = value;
RaisePropertyChanged(MyPropertyPropertyName, oldValue, value, true);
}
}
public void DoSomething()
{
//Messenger.Default.Send("Hello MVVM"); //Works
this.MyProperty = "Hello World"; //Doesn't work.
}
public void ShowMessage(string message)
{
MessageBox.Show(message);
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
Messenger.Default.Register(this, new Action<string>(ShowMessage));
MyRelayCommand = new RelayCommand(new Action(DoSomething));
this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(MainViewModel_PropertyChanged);
}
void MainViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
MessageBox.Show(e.PropertyName);
}
}v
OK, I found that the Register line should look like this:
Messenger.Default.Register(this, new Action<PropertyChangedMessage<string>>(ShowMessage));
The point being there are different types of messages, and you have to register the PropertyChangedMessage type to recieve property changed messages.
Then also, the Action that recieves the message needs to take the correct parameter, like this:
public void ShowMessage(PropertyChangedMessage<string> e)
{
MessageBox.Show(e.NewValue.ToString());
}

Resources