Reputation: 298
I have a strange situation where the bidirectional binding between a ComboBox and an IntegerProperty consistently broken after a System.gc() call. Somehow, everything works as expected if I use ObjectProperty.
I attached all the code below. When the application start running, the label properly shows the corresponding selected values of ComboBox1 and ComboBox2. But once the GC button is clicked, the bidirectional binding between ComboBox1 and fubar.mID1 property is broken.
I understand the bidirectional binding is a weak one and gc cleans it once the objects is out of scope. But here none of the object went out of scope. Why?
Here is MainController.java:
package sample;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.util.StringConverter;
public class MainController {
private final Fubar fubar = new Fubar(1, 2);
@FXML
ComboBox<Integer> mComboBox1;
@FXML
ComboBox<Integer> mComboBox2;
@FXML
Label mLabel;
@FXML
public void initialize() {
mComboBox1.setConverter(new StringConverter<Integer>() {
@Override
public String toString(Integer integer) {
if (integer == null)
return "";
switch (integer) {
case 1:
return "One";
case 2:
return "Two";
default:
return "";
}
}
@Override
public Integer fromString(String s) {
if (s == null)
return 0;
switch (s) {
case "One":
return 1;
case "Two":
return 2;
default:
return 0;
}
}
});
for (int i = 0; i < 3; i++)
mComboBox1.getItems().add(i);
mComboBox1.valueProperty().bindBidirectional(fubar.getID1Property().asObject());
mComboBox2.setConverter(mComboBox1.getConverter());
for (int i = 0; i < 3; i++)
mComboBox2.getItems().add(i);
mComboBox2.valueProperty().bindBidirectional(fubar.getID2Property());
mLabel.textProperty().bind(Bindings.createStringBinding(() ->
fubar.getID1Property().get() + "/" + fubar.getID2Property().get(),
fubar.getID1Property(), fubar.getID2Property()));
}
@FXML
private void handleGC() {
System.gc();
}
}
And here is Fubar.java:
package sample;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
public class Fubar {
private final IntegerProperty mID1 = new SimpleIntegerProperty(0);
private final ObjectProperty<Integer> mID2 = new SimpleObjectProperty<>(0);
Fubar(Integer id1, Integer id2) {
mID1.set(id1);
mID2.set(id2);
}
IntegerProperty getID1Property() { return mID1; }
ObjectProperty<Integer> getID2Property() { return mID2; }
}
Here is Main.java:
package sample;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
public class Fubar {
private final IntegerProperty mID1 = new SimpleIntegerProperty(0);
private final ObjectProperty<Integer> mID2 = new SimpleObjectProperty<>(0);
Fubar(Integer id1, Integer id2) {
mID1.set(id1);
mID2.set(id2);
}
IntegerProperty getID1Property() { return mID1; }
ObjectProperty<Integer> getID2Property() { return mID2; }
}
Here is Main.java:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
primaryStage.setTitle("Main");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
and Main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.MainController">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ComboBox fx:id="mComboBox1" prefWidth="150.0" />
<ComboBox fx:id="mComboBox2" prefWidth="150.0" GridPane.rowIndex="1" />
<Button mnemonicParsing="false" onAction="#handleGC" text="GC" GridPane.rowIndex="2" />
<Label fx:id="mLabel" text="Main" GridPane.columnIndex="2" />
</children>
</GridPane>
Upvotes: 1
Views: 403
Reputation: 298
Aha, I figured it out. The problem was at the fubar.getID1Property().asObject()
. I believe java treats this asObject()
as a temporary object. The temporary object goes out of scope right away, and System.gc()
call simply clear the temporary object and destroys the binding.
To fix this problem, one need to retain the reference to this asObject()
. One way to do this in my own sample problem is to declare a field ObjectProperty<Integer> middleMan
in the controller and initialize it as
ObjectProperty<Integer> middleMan = fubar.getID1Property().asObject();
and then bind mCombobox1.valueProperty()
to this middleMan
.
Upvotes: 1