Baya
Baya

Reputation: 247

Binding issue between POJO and JavaFX components

Currently, what I want to do is use JAXB generated POJO to bind every Java properties to JavaFX components. To do that, I proceeded as followed :

Here's a sample of my factory :

public static Map<Field, Node> createComponents(Object obj) throws NoSuchMethodException
{
        Map<Field, Node> map = new LinkedHashMap<Field, Node>();

        for (final Field field : obj.getClass().getDeclaredFields())
        {
            @SuppressWarnings("rawtypes")
            Class fieldType = field.getType();

            if (fieldType.equals(boolean.class) || (fieldType.equals(Boolean.class))) //Boolean
            {
                map.put(field, createBool(obj, field));
            }
            else if (fieldType.equals(int.class) || (fieldType.equals(Integer.class))) //Integer
            {
               map.put(field, createInt(obj, field));
            }
            else if (fieldType.equals(BigInteger.class)) //BigInteger
            {
               map.put(field, createBigInt(obj, field));
            }
            else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) //Long
            {
               map.put(field, createLong(obj, field));
            }
            else if (fieldType.equals(String.class)) //String
            {
               map.put(field, createString(obj, field));

            }
            ...
        }
        return map;   
} 

public static Node createBool(Object obj, final Field field) throws NoSuchMethodException
{

      System.out.println(field.getType().getSimpleName() + " spotted");
      JavaBeanBooleanProperty boolProperty = JavaBeanBooleanPropertyBuilder.create().bean(obj).name(field.getName()).build();
      boolProperty.addListener(new ChangeListener<Boolean>() {
         @Override
         public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2)
         {
            prettyPrinter(field, arg1, arg2);
         }
      });
      CheckBox cb = new CheckBox();
      cb.setText(" : " + field.getName());
      cb.selectedProperty().bindBidirectional(boolProperty);
      return cb;

}

public static Node createInt(Object obj, final Field field) throws NoSuchMethodException
{
      System.out.println(field.getType().getSimpleName() + " spotted");
      JavaBeanIntegerProperty intProperty = JavaBeanIntegerPropertyBuilder.create().bean(obj).name(field.getName()).build();
      StringProperty s = new SimpleStringProperty();
      StringConverter sc = new IntegerStringConverter();
      Bindings.bindBidirectional(s, intProperty, sc);
      s.addListener(new ChangeListener<String>() {
         @Override
         public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2)
         {
            prettyPrinter(field, arg1, arg2);
         }
      });

      TextField tf = new TextField();
      tf.textProperty().bindBidirectional(s);

      return tf;

}

So, the problem I have is : In the most case when I change, for example, a textField the POJO property doesn't notice. But in some case, when I change the order of the fields in the POJO every listener will notice any change.

Here's an example of what the GUI looks like with the followed Personne class (which currently works)

GUI Example

public class Personne
{

   private int                   taille;

   private Boolean               lol;

   private long                  pointure;

   private BigInteger            age;

   private boolean               zombified;

   private String                name;

   private PropertyChangeSupport _changeSupport = new PropertyChangeSupport(this);

   public Boolean getLol()
   {
      return this.lol;
   }

   public long getPointure()
   {
      return this.pointure;
   }

   public int getTaille()
   {
      return taille;
   }

   public boolean getZombified()
   {
      return zombified;
   }

   public BigInteger getAge()
   {
      return age;
   }

   public String getName()
   {
      return name;
   }

   public void setPointure(long pointure)
   {

      final long prev = this.pointure;
      this.pointure = pointure;
      _changeSupport.firePropertyChange("pointure", prev, pointure);
   }

   public void setTaille(int taille)
   {
      final int prev = this.taille;
      this.taille = taille;
      _changeSupport.firePropertyChange("taille", prev, taille);
   }

   public void setAge(BigInteger age)
   {
      final BigInteger prev = this.age;
      this.age = age;
      _changeSupport.firePropertyChange("age", prev, age);

   }

   public void setName(String name)
   {
      final String prev = this.name;
      this.name = name;
      _changeSupport.firePropertyChange("name", prev, name);
   }

   public void setLol(Boolean lol)
   {
      final Boolean prev = this.lol;
      this.lol = lol;
      _changeSupport.firePropertyChange("lol", prev, lol);
   }

   public void setZombified(boolean zombified)
   {
      final boolean prev = this.zombified;
      this.zombified = zombified;
      _changeSupport.firePropertyChange("zombified", prev, zombified);

   }

   public void addPropertyChangeListener(final PropertyChangeListener listener)
   {
      _changeSupport.addPropertyChangeListener(listener);
   }
}

I'm wondering how can the property order influence the binding like that. Furthermore, I noticed that if I want to return my nodes wrapped in HBox the binding doesn't work no more.

I think that I'm doing someting wrong, but I can't figure out what.

Upvotes: 0

Views: 1501

Answers (1)

yonran
yonran

Reputation: 19134

Your JavaBeanIntegerProperty and JavaBeanBooleanProperty are being garbage collected too early.

The method Property.bind(Observable) makes the property hold a strong reference to the observable, but the observable only holds a weak reference to the property! (it registers a Listener on the Observable that only has a WeakReference back to the Property). Likewise, when you call Bindings.bindBidirectional(Property, Property), both properties hold weak references to each other! This is an extremely important detail that is hard to find in the documentation.

If you only ever interact with JavaFX objects, this is not a problem because a JavaFX object naturally holds strong references to all its Properties. But if you are using JavaBeanIntegerProperty to wrap a bean property from a legacy object, then the bean does not hold a strong reference to the JavaBeanIntegerProperty, so after gc the Property will disappear and stop updating the bean! I think this is a design bug in the JavaBeanIntegerProperty.

The solution is to assign the JavaBeanIntegerProperty to a field and store it for as long as you want the binding to keep updating the bean.

Alternatively, you can write your own Property subclasses that do something to ensure that a strong reference exists from the bean to the Property (e.g. addPropertyChangeListener to the bean to listen forever).

Upvotes: 1

Related Questions